diff --git a/.changelog/1770.txt b/.changelog/1770.txt deleted file mode 100644 index f8b1c570da..0000000000 --- a/.changelog/1770.txt +++ /dev/null @@ -1,3 +0,0 @@ -```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 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/1934.txt b/.changelog/1934.txt deleted file mode 100644 index a8bc41fd50..0000000000 --- a/.changelog/1934.txt +++ /dev/null @@ -1,3 +0,0 @@ -```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 deleted file mode 100644 index 3185330864..0000000000 --- a/.changelog/1953.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: update `imageConsulDataplane` value to `hashicorp/consul-dataplane:1.1.0`. -``` \ 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/2104.txt similarity index 79% rename from .changelog/2102.txt rename to .changelog/2104.txt index 7adf361d2d..59d120f747 100644 --- a/.changelog/2102.txt +++ b/.changelog/2104.txt @@ -10,12 +10,3 @@ 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/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/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/2160.txt b/.changelog/2160.txt new file mode 100644 index 0000000000..9b970bf3f4 --- /dev/null +++ b/.changelog/2160.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/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/2226.txt b/.changelog/2226.txt new file mode 100644 index 0000000000..fcbb89a54b --- /dev/null +++ b/.changelog/2226.txt @@ -0,0 +1,3 @@ +```release-note:security +Bump `controller-runtime` to address CVEs in dependencies. +``` diff --git a/.changelog/2262.txt b/.changelog/2262.txt new file mode 100644 index 0000000000..267377ebe9 --- /dev/null +++ b/.changelog/2262.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: add consul-telemetry-gateway allow-all intention for -demo +``` \ No newline at end of file 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/2265.txt b/.changelog/2369.txt similarity index 96% rename from .changelog/2265.txt rename to .changelog/2369.txt index 1cf6813c94..35643ce272 100644 --- a/.changelog/2265.txt +++ b/.changelog/2369.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/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/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/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/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/2905.txt b/.changelog/2905.txt index eb1196fa0f..c40135215b 100644 --- a/.changelog/2905.txt +++ b/.changelog/2905.txt @@ -1,3 +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/.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/3120.txt similarity index 100% rename from .changelog/3116.txt rename to .changelog/3120.txt 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/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/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/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/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/3284.txt b/.changelog/3296.txt similarity index 97% rename from .changelog/3284.txt rename to .changelog/3296.txt index 07b896f906..2ed983ed06 100644 --- a/.changelog/3284.txt +++ b/.changelog/3296.txt @@ -1,3 +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. -``` +``` \ No newline at end of file diff --git a/.changelog/3407.txt b/.changelog/3407.txt deleted file mode 100644 index 14dc27d0ff..0000000000 --- a/.changelog/3407.txt +++ /dev/null @@ -1,13 +0,0 @@ -```release-note:feature -helm: introduces `global.metrics.datadog` overrides to streamline consul-k8s datadog integration. -helm: introduces `server.enableAgentDebug` to expose agent [`enable_debug`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#enable_debug) configuration. -helm: introduces `global.metrics.disableAgentHostName` to expose agent [`telemetry.disable_hostname`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-disable_hostname) configuration. -helm: introduces `global.metrics.enableHostMetrics` to expose agent [`telemetry.enable_host_metrics`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-enable_host_metrics) configuration. -helm: introduces `global.metrics.prefixFilter` to expose agent [`telemetry.prefix_filter`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-prefix_filter) configuration. -helm: introduces `global.metrics.datadog.dogstatsd.dogstatsdAddr` to expose agent [`telemetry.dogstatsd_addr`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-dogstatsd_addr) configuration. -helm: introduces `global.metrics.datadog.dogstatsd.dogstatsdTags` to expose agent [`telemetry.dogstatsd_tags`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-dogstatsd_tags) configuration. -helm: introduces required `ad.datadoghq.com/` annotations and `tags.datadoghq.com/` labels for integration with [Datadog Autodiscovery](https://docs.datadoghq.com/integrations/consul/?tab=containerized) and [Datadog Unified Service Tagging](https://docs.datadoghq.com/getting_started/tagging/unified_service_tagging/?tab=kubernetes#serverless-environment) for Consul. -helm: introduces automated unix domain socket hostPath mounting for containerized integration with datadog within consul-server statefulset. -helm: introduces `global.metrics.datadog.otlp` override options to allow OTLP metrics forwarding to Datadog Agent. -control-plane: adds `server-acl-init` datadog agent token creation for datadog integration. -``` \ No newline at end of file diff --git a/.changelog/3418.txt b/.changelog/3418.txt index 032356f989..69da5a06da 100644 --- a/.changelog/3418.txt +++ b/.changelog/3418.txt @@ -1,3 +1,3 @@ ```release-note:security -Upgrade OpenShift container images to use `ubi9-minimal:9.3` as the base image. -``` + Upgrade to use `ubi-minimal:9.3` for OpenShift container images. + ``` diff --git a/.changelog/3428.txt b/.changelog/3428.txt deleted file mode 100644 index 9f12d3f60e..0000000000 --- a/.changelog/3428.txt +++ /dev/null @@ -1,4 +0,0 @@ -```release-note:note -build: Releases will now also be available as Debian and RPM packages for the arm64 architecture, refer to the -[Official Packaging Guide](https://www.hashicorp.com/official-packaging-guide) for more information. -``` 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/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/.changelog/3531.txt b/.changelog/3531.txt deleted file mode 100644 index cd61ff3ce2..0000000000 --- a/.changelog/3531.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -api-gateway: Apply `connectInject.initContainer.resources` to the init container for API gateway Pods. -``` diff --git a/.changelog/3597.txt b/.changelog/3597.txt deleted file mode 100644 index bcd0dafe8b..0000000000 --- a/.changelog/3597.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -api-gateway: fix issue where external annotations and labels are being incorrectly deleted on services controlled by the API Gateway -``` \ No newline at end of file diff --git a/.changelog/3635.txt b/.changelog/3635.txt deleted file mode 100644 index c5c505c808..0000000000 --- a/.changelog/3635.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -helm: (datadog integration) updated `server-statefulset.yaml` templating to handle custom Unix Domain Socket paths. -``` \ No newline at end of file diff --git a/.changelog/3668.txt b/.changelog/3668.txt index 797590f713..25b12b0d18 100644 --- a/.changelog/3668.txt +++ b/.changelog/3668.txt @@ -1,3 +1,3 @@ ```release-note:improvement -control-plane: publish `consul-k8s-control-plane` and `consul-k8s-control-plane-fips` images to official HashiCorp AWS ECR. -``` + control-plane: publish `consul-k8s-control-plane` and `consul-k8s-control-plane-fips` images to official HashiCorp AWS ECR. + ``` diff --git a/.changelog/3675.txt b/.changelog/3675.txt deleted file mode 100644 index 987370cfa4..0000000000 --- a/.changelog/3675.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: Kubernetes v1.29 is now supported. Minimum tested version of Kubernetes is now v1.26. -``` \ No newline at end of file diff --git a/.changelog/3693.txt b/.changelog/3693.txt deleted file mode 100644 index b26e6da0a4..0000000000 --- a/.changelog/3693.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -catalog: Topology zone and region information is now read from the Kubernetes endpoints and associated node and added to registered consul services under Metadata. -``` \ No newline at end of file diff --git a/.changelog/3718.txt b/.changelog/3718.txt deleted file mode 100644 index 9e7cd4f59a..0000000000 --- a/.changelog/3718.txt +++ /dev/null @@ -1,4 +0,0 @@ -```release-note:breaking-change -api-gateway: The api-gateway stanza located under .Values.api-gateway was deprecated in -1.16.0 of Consul and is being removed as of 1.19.0 in favor of connectInject.apiGateway. -``` \ No newline at end of file diff --git a/.changelog/3779.txt b/.changelog/3779.txt deleted file mode 100644 index 946fcca208..0000000000 --- a/.changelog/3779.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -api-gateway: Fix order of initialization for creating ACL role/policy to avoid error logs in consul. -``` diff --git a/.changelog/3795.txt b/.changelog/3795.txt deleted file mode 100644 index 0515bbda23..0000000000 --- a/.changelog/3795.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -control-plane: Add support for receiving iptables configuration via CNI arguments, to support Nomad transparent proxy -``` diff --git a/.changelog/3811.txt b/.changelog/3811.txt deleted file mode 100644 index e333a9df84..0000000000 --- a/.changelog/3811.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:enhancement -api-gateway: Expose prometheus scrape metrics on api-gateway pods. -``` diff --git a/.changelog/3813.txt b/.changelog/3813.txt deleted file mode 100644 index 59ef045467..0000000000 --- a/.changelog/3813.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -control-plane: Remove anyuid Security Context Constraints (SCC) requirement in OpenShift. -``` diff --git a/.changelog/3829.txt b/.changelog/3829.txt deleted file mode 100644 index f749de0e23..0000000000 --- a/.changelog/3829.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -consul-cni: Fixed a bug where the output of `-version` did not include the version of the binary -``` diff --git a/.changelog/3846.txt b/.changelog/3846.txt deleted file mode 100644 index 84373c0ec1..0000000000 --- a/.changelog/3846.txt +++ /dev/null @@ -1,5 +0,0 @@ -```release-note:improvement -helm: only create the default Prometheus path annotation when it's not already specified within the component-specific -annotations. For example if the `client.annotations` value sets prometheus.io/path annotation, don't overwrite it with -the default value. -``` diff --git a/.changelog/3873.txt b/.changelog/3873.txt new file mode 100644 index 0000000000..e4c36d5e58 --- /dev/null +++ b/.changelog/3873.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ConfigEntries controller: Only error for config entries from different datacenters when the config entries are different +``` \ No newline at end of file diff --git a/.changelog/3878.txt b/.changelog/3878.txt deleted file mode 100644 index 81b7c842b4..0000000000 --- a/.changelog/3878.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -Add support for configuring graceful startup proxy lifecycle management settings. -``` diff --git a/.changelog/3893.txt b/.changelog/3900.txt similarity index 100% rename from .changelog/3893.txt rename to .changelog/3900.txt diff --git a/.changelog/3905.txt b/.changelog/3905.txt deleted file mode 100644 index f5aec57cb1..0000000000 --- a/.changelog/3905.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: support sync-lb-services-endpoints flag for syncCatalog -``` \ No newline at end of file diff --git a/.changelog/3935.txt b/.changelog/3935.txt new file mode 100644 index 0000000000..4c37da2a4c --- /dev/null +++ b/.changelog/3935.txt @@ -0,0 +1,5 @@ +```release-note:security +Upgrade `helm/v3` to 3.14.4. This resolves the following security vulnerabilities: +[CVE-2024-25620](https://osv.dev/vulnerability/CVE-2024-25620) +[CVE-2024-26147](https://osv.dev/vulnerability/CVE-2024-26147) +``` \ No newline at end of file diff --git a/.changelog/3980.txt b/.changelog/3980.txt new file mode 100644 index 0000000000..9b2bd21f42 --- /dev/null +++ b/.changelog/3980.txt @@ -0,0 +1,5 @@ +```release-note:security +Upgrade Go to use 1.21.10. This addresses CVEs +[CVE-2024-24787](https://nvd.nist.gov/vuln/detail/CVE-2024-24787) and +[CVE-2024-24788](https://nvd.nist.gov/vuln/detail/CVE-2024-24788) +``` diff --git a/.changelog/3994.txt b/.changelog/3994.txt new file mode 100644 index 0000000000..1605c21f51 --- /dev/null +++ b/.changelog/3994.txt @@ -0,0 +1,3 @@ +```release-note:enhancement + upgrade go version to v1.22.3. +``` diff --git a/.changelog/4016.txt b/.changelog/4016.txt new file mode 100644 index 0000000000..4bae9643c9 --- /dev/null +++ b/.changelog/4016.txt @@ -0,0 +1,3 @@ +```release-note:improvement +Bump Dockerfile base image for `consul-k8s-control-plane` to `alpine:3.19`. +``` diff --git a/.changelog/4053.txt b/.changelog/4053.txt new file mode 100644 index 0000000000..7f719d2f39 --- /dev/null +++ b/.changelog/4053.txt @@ -0,0 +1,3 @@ +```release-note:improvement + partition-init: Role no longer includes unnecessary access to Secrets resource. + ``` diff --git a/.changelog/4085.txt b/.changelog/4085.txt new file mode 100644 index 0000000000..fdf600e70c --- /dev/null +++ b/.changelog/4085.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +upgrade go version to v1.22.4. +``` diff --git a/.changelog/4091.txt b/.changelog/4091.txt new file mode 100644 index 0000000000..dc67979da8 --- /dev/null +++ b/.changelog/4091.txt @@ -0,0 +1,3 @@ +```release-note:bug +cni: fix incorrect release version due to unstable submodule pinning +``` diff --git a/.changelog/4154.txt b/.changelog/4154.txt new file mode 100644 index 0000000000..e06736fdbc --- /dev/null +++ b/.changelog/4154.txt @@ -0,0 +1,3 @@ +```release-note:security +Upgrade go version to 1.22.5 to address [CVE-2024-24791](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-24791) +``` \ No newline at end of file diff --git a/.changelog/4169.txt b/.changelog/4169.txt new file mode 100644 index 0000000000..17b09a331d --- /dev/null +++ b/.changelog/4169.txt @@ -0,0 +1,3 @@ +```release-note:security +Upgrade go-retryablehttp to v0.7.7 to address [GHSA-v6v8-xj6m-xwqh](https://github.com/advisories/GHSA-v6v8-xj6m-xwqh) +``` diff --git a/.changelog/4184.txt b/.changelog/4184.txt new file mode 100644 index 0000000000..389f31b515 --- /dev/null +++ b/.changelog/4184.txt @@ -0,0 +1,3 @@ +```release-note:improvement +* helm: Adds `webhookCertManager.resources` field which can be configured to override the `resource` settings for the `webhook-cert-manager` deployment. +``` \ No newline at end of file diff --git a/.changelog/4227.txt b/.changelog/4227.txt new file mode 100644 index 0000000000..feb7844aae --- /dev/null +++ b/.changelog/4227.txt @@ -0,0 +1,4 @@ +```release-note:bug +openshift: order SecurityContextConstraint volumes alphabetically to match OpenShift behavior. +This ensures that diff detection tools like ArgoCD consider the source and reconciled resources to be identical. +``` diff --git a/.changelog/4228.txt b/.changelog/4228.txt new file mode 100644 index 0000000000..465229e7d9 --- /dev/null +++ b/.changelog/4228.txt @@ -0,0 +1,6 @@ +```release-note:security +Upgrade Docker cli to use v.27.1. This addresses CVE +[CVE-2024-41110](https://nvd.nist.gov/vuln/detail/CVE-2024-41110)``` + +```release-note:security +Bump Go to 1.22.5 to address [CVE-2024-24791](https://nvd.nist.gov/vuln/detail/CVE-2024-24791)``` diff --git a/.changelog/4256.txt b/.changelog/4256.txt new file mode 100644 index 0000000000..d2279cfce7 --- /dev/null +++ b/.changelog/4256.txt @@ -0,0 +1,3 @@ +```release-note:improvement +config-entry: add validate_clusters to mesh config entry +``` \ No newline at end of file diff --git a/.copywrite.hcl b/.copywrite.hcl index 9263741105..f12ea14ccc 100644 --- a/.copywrite.hcl +++ b/.copywrite.hcl @@ -15,4 +15,4 @@ project { "control-plane/config/crd/external/**", ] -} +} \ No newline at end of file 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/scripts/check_skip_ci.sh b/.github/scripts/check_skip_ci.sh new file mode 100755 index 0000000000..b8785f5541 --- /dev/null +++ b/.github/scripts/check_skip_ci.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +set -euo pipefail + +# first argument is the list, second is the item to check +function contains() { + list=($1) + for item in "${list[@]}"; do + if [ "$item" == "$2" ]; then + return 0 + fi + done + return 1 +} + +# Get the list of changed files +# Using `git merge-base` ensures that we're always comparing against the correct branch point. +#For example, given the commits: +# +# A---B---C---D---W---X---Y---Z # origin/main +# \---E---F # feature/branch +# +# ... `git merge-base origin/$SKIP_CHECK_BRANCH HEAD` would return commit `D` +# `...HEAD` specifies from the common ancestor to the latest commit on the current branch (HEAD).. +files_to_check=$(git diff --name-only "$(git merge-base origin/$SKIP_CHECK_BRANCH HEAD~)"...HEAD) + +# Define the directories to check +skipped_directories=("assets" ".changelog/", "version") + +files_to_skip=("LICENSE" ".copywrite.hcl" ".gitignore") + +# Loop through the changed files and find directories/files outside the skipped ones +files_to_check_array=($files_to_check) +for file_to_check in "${files_to_check_array[@]}"; do + file_is_skipped=false + echo "checking file: $file_to_check" + + # Allow changes to: + # - This script + # - Files in the skipped directories + # - Markdown files + for dir in "${skipped_directories[@]}"; do + if [[ "$file_to_check" == */check_skip_ci.sh ]] || + [[ "$file_to_check" == "$dir"* ]] || + [[ "$file_to_check" == *.md ]] || + contains "${files_to_skip[*]}" "$file_to_check"; then + file_is_skipped=true + break + fi + done + + if [ "$file_is_skipped" != "true" ]; then + echo -e "non-skippable file changed: $file_to_check" + SKIP_CI=false + echo "Changes detected in non-documentation files - will not skip tests and build" + echo "skip-ci=false" >>"$GITHUB_OUTPUT" + exit 0 ## if file is outside of the skipped_directory exit script + fi +done + +echo "Changes detected in only documentation files - skipping tests and build" +echo "skip-ci=true" >>"$GITHUB_OUTPUT" diff --git a/.github/workflows/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 39dc4c74d9..08b3d364a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,22 +10,29 @@ on: - main # Push events to branches matching refs/heads/release/** - "release/**" - # Build on releng branches for testing build pipelines - - "releng/**" env: PKG_NAME: "consul-k8s" jobs: + conditional-skip: + uses: ./.github/workflows/reusable-conditional-skip.yml + get-go-version: + # Cascades down to test jobs + needs: [ conditional-skip ] + if: needs.conditional-skip.outputs.skip-ci != 'true' uses: ./.github/workflows/reusable-get-go-version.yml get-product-version: + # Cascades down to test jobs + needs: [ conditional-skip ] + if: needs.conditional-skip.outputs.skip-ci != 'true' runs-on: ubuntu-latest outputs: product-version: ${{ steps.get-product-version.outputs.product-version }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: get product version id: get-product-version run: | @@ -39,7 +46,7 @@ jobs: filepath: ${{ steps.generate-metadata-file.outputs.filepath }} steps: - name: "Checkout directory" - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Generate metadata file id: generate-metadata-file uses: hashicorp/actions-generate-metadata@v1 @@ -47,50 +54,42 @@ 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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 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: - # cli (We aren't build packages for the linux 32-bit platforms) + # cli - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "386", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "386", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", "skip_packaging": "true" } - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s"} - - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s", "skip_packaging": "true"} + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "386", 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" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s" } - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "386", component: "cli", pkg_name: "consul-k8s", "bin_name": "consul-k8s.exe" } - {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" } @@ -102,41 +101,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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Setup go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 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 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 - - name: Build env: GOOS: ${{ matrix.goos }} @@ -145,59 +121,63 @@ jobs: working-directory: ${{ matrix.component }} run: | mkdir -p dist out + cp $GITHUB_WORKSPACE/LICENSE dist/LICENSE.txt 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 GIT_IMPORT=github.com/hashicorp/consul-k8s/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 + - name: Upload built binaries + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 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: Copy license file + env: + LICENSE_DIR: ".release/linux/package/usr/share/doc/${{ env.PKG_NAME }}" + run: | + mkdir -p "$LICENSE_DIR" + cp $GITHUB_WORKSPACE/LICENSE "$LICENSE_DIR/LICENSE.txt" - - name: Package rpm and deb files - if: matrix.goos == 'linux' && matrix.component == 'cli' && matrix.skip_packaging != 'true' + - 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" binary: "${{ matrix.component }}/dist/${{ matrix.pkg_name }}" deb_depends: "openssl" rpm_depends: "openssl" + config_dir: ".release/linux/package/" - name: Set package names - if: matrix.goos == 'linux' && matrix.component == 'cli' && matrix.skip_packaging != 'true' + if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} run: | echo "RPM_PACKAGE=$(basename out/*.rpm)" >> $GITHUB_ENV echo "DEB_PACKAGE=$(basename out/*.deb)" >> $GITHUB_ENV - - name: Enable docker runtime emulation for testing packages - if: matrix.goos == 'linux' && matrix.component == 'cli' && matrix.skip_packaging != 'true' && matrix.goarch != 'amd64' - run: | - docker run --privileged \ - --rm \ - docker.mirror.hashicorp.services/tonistiigi/binfmt@sha256:5540f38542290735d17da57d7084f684c62336105d018c605058daf03e4c8256 --install ${{ matrix.goarch }} - - - name: Test rpm package on platforms on UBI - if: matrix.goos == 'linux' && matrix.component == 'cli' && matrix.skip_packaging != 'true' + - name: Test rpm package + if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 with: image: registry.access.redhat.com/ubi9/ubi:latest - options: -v ${{ github.workspace }}:/work --platform linux/${{matrix.goarch}} + options: -v ${{ github.workspace }}:/work run: | - dnf install -y /work/out/${{ env.RPM_PACKAGE }} + dnf install -qy openssl + 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 @@ -205,61 +185,56 @@ jobs: echo "Test PASSED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" - name: Upload rpm package - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - if: matrix.goos == 'linux' && matrix.component == 'cli' && matrix.skip_packaging != 'true' + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} with: name: ${{ env.RPM_PACKAGE }} path: out/${{ env.RPM_PACKAGE }} - name: Test debian package - if: matrix.goos == 'linux' && matrix.component == 'cli' && matrix.skip_packaging != 'true' + if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 with: image: ubuntu:latest - options: -v ${{ github.workspace }}:/work --platform linux/${{matrix.goarch}} + options: -v ${{ github.workspace }}:/work run: | - apt-get update -qq - apt-get install -y /work/out/${{ env.DEB_PACKAGE }} + apt update && apt install -y openssl + 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 fi echo "Test PASSED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" - - name: Upload debian packages - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - if: matrix.goos == 'linux' && matrix.component == 'cli' && matrix.skip_packaging != 'true' + - name: Upload debian packages + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + 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 + name: Docker ${{ matrix.arch }} default release build needs: [get-product-version, get-go-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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 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 @@ -269,12 +244,11 @@ jobs: 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/')" 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@v2 with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -285,9 +259,9 @@ 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 + bin_name: consul-k8s-control-plane workdir: control-plane tags: | docker.io/hashicorp/${{ env.repo }}-control-plane:${{ env.version }} @@ -300,51 +274,21 @@ jobs: 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 }} - 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 }} - public.ecr.aws/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 }} - build-docker-ubi: - name: Docker ${{ matrix.arch }} ${{ matrix.fips }} UBI builds + name: Docker ${{ matrix.arch }} UBI builds needs: [get-product-version, get-go-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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 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: @@ -352,21 +296,17 @@ jobs: run: | 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: | 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/')" 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@v2 with: smoke_test: | TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" @@ -392,32 +332,3 @@ jobs: 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 - tags: | - public.ecr.aws/hashicorp/${{ env.repo }}-control-plane-fips:${{ env.version }}-ubi - docker.io/hashicorp/${{ env.repo }}-control-plane-fips:${{ env.version }}-ubi - 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 }} - dev_tags: | - docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.full_dev_tag }}-ubi - docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.full_dev_tag }}-ubi-${{ github.sha }} - docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.minor_dev_tag }}-ubi - docker.io/hashicorppreview/${{ env.repo }}-control-plane-fips:${{ env.minor_dev_tag }}-ubi-${{ github.sha }} diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index 40c9b17c68..523b3b629f 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 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..85712d747e 100644 --- a/.github/workflows/jira-issues.yaml +++ b/.github/workflows/jira-issues.yaml @@ -1,5 +1,3 @@ -# Copyright (c) HashiCorp, Inc. - on: issues: types: [opened, closed, deleted, reopened] @@ -15,7 +13,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 }} @@ -47,8 +45,6 @@ jobs: # 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 }} @@ -72,14 +68,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 54a532d940..4df49f74dd 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -1,5 +1,3 @@ -# Copyright (c) HashiCorp, Inc. - on: pull_request_target: types: [opened, closed, reopened] @@ -13,7 +11,7 @@ jobs: name: Jira 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 }} @@ -39,7 +37,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 @@ -60,9 +58,7 @@ 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_10535": [{ "value": "Service Mesh" }], - "components": [{ "name": "${{ github.event.repository.name }}" }], + "customfield_10371": { "value": "GitHub" }, "labels": ${{ steps.set-ticket-labels.outputs.LABELS }} }' env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} @@ -86,14 +82,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/lint.yaml b/.github/workflows/lint.yaml index 0ee97a3d75..6901dfc5fd 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Setup go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version: ${{ needs.get-go-version.outputs.go-version }} cache: false @@ -26,7 +26,7 @@ jobs: run: echo "GOROOT=$(go env GOROOT)" >> "${GITHUB_ENV}" - name: golangci-lint-helm-gen - uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299 # v3.6.0 + uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 with: version: "v1.55.2" working-directory: hack/helm-reference-gen @@ -34,21 +34,21 @@ jobs: args: "--no-config --disable-all --enable gofmt,govet" - name: golangci-lint-control-plane - uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299 # v3.6.0 + uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 with: version: "v1.55.2" working-directory: control-plane skip-cache: true # We have seen sticky timeout bugs crop up with caching enabled, so disabling for now - name: golangci-lint-acceptance - uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299 # v3.6.0 + uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 with: version: "v1.55.2" working-directory: acceptance skip-cache: true # We have seen sticky timeout bugs crop up with caching enabled, so disabling for now - name: golangci-lint-cli - uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299 # v3.6.0 + uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 with: version: "v1.55.2" working-directory: acceptance diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index a62906a5f9..3d442eb8e7 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@25b02cc069be46d637e8fe2f1e8484008e9e9609 # v1.2.3 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: '{ "test-ce": false, "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/nightly-acceptance.yml b/.github/workflows/nightly-acceptance.yml index 6db7684bb8..73ed25cc41 100644 --- a/.github/workflows/nightly-acceptance.yml +++ b/.github/workflows/nightly-acceptance.yml @@ -16,11 +16,11 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@25b02cc069be46d637e8fe2f1e8484008e9e9609 # v1.2.3 name: cloud with: workflow: cloud.yml repo: hashicorp/consul-k8s-workflows ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' + inputs: '{ "test-ce": false, "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 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..6a5fc4668d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -10,15 +10,49 @@ env: SHA: ${{ github.event.pull_request.head.sha || github.sha }} jobs: + conditional-skip: + uses: ./.github/workflows/reusable-conditional-skip.yml + test: name: test + needs: [ conditional-skip ] + if: needs.conditional-skip.outputs.skip-ci != 'true' runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@25b02cc069be46d637e8fe2f1e8484008e9e9609 # v1.2.3 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: '{ "test-ce": false, "context":"${{ env.CONTEXT }}", "actor":"${{ github.actor }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ env.SHA }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' + + pass-required-checks-on-skip: + needs: [ conditional-skip ] + if: needs.conditional-skip.outputs.skip-ci == 'true' + runs-on: ubuntu-latest + strategy: + matrix: + include: + # The required checks that should be "passed" when the CI is skipped + - check-name: acceptance + - check-name: acceptance-cni + - check-name: acceptance-tproxy + - check-name: Unit test helm templates + - check-name: Unit test enterprise control plane + - check-name: Unit test control plane + - check-name: Unit test cli + - check-name: Unit test acceptance + steps: + - name: Update final status + uses: docker://ghcr.io/curtbushko/commit-status-action:e1d661c757934ab35c74210b4b70c44099ec747a + env: + INPUT_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + INPUT_REPOSITORY: ${{ github.repository }} + INPUT_CONTEXT: ${{ matrix.check-name }} + INPUT_STATE: success + INPUT_DESCRIPTION: "Skipped due to conditional-skip check" + INPUT_SHA: ${{ env.SHA }} + INPUT_DETAILS_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + INPUT_OWNER: "hashicorp" diff --git a/.github/workflows/reusable-conditional-skip.yml b/.github/workflows/reusable-conditional-skip.yml new file mode 100644 index 0000000000..ecba895152 --- /dev/null +++ b/.github/workflows/reusable-conditional-skip.yml @@ -0,0 +1,24 @@ +name: conditional-skip + +on: + workflow_call: + outputs: + skip-ci: + description: "Whether we should skip build and test jobs" + value: ${{ jobs.check-skip.outputs.skip-ci }} + +jobs: + check-skip: + runs-on: ubuntu-latest + name: Check whether to skip build and tests + outputs: + skip-ci: ${{ steps.check-changed-files.outputs.skip-ci }} + env: + SKIP_CHECK_BRANCH: ${{ github.head_ref || github.ref_name }} + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + fetch-depth: 0 + - name: Check changed files + id: check-changed-files + run: ./.github/scripts/check_skip_ci.sh diff --git a/.github/workflows/reusable-get-go-version.yml b/.github/workflows/reusable-get-go-version.yml index e2de0afd18..a153b24eb8 100644 --- a/.github/workflows/reusable-get-go-version.yml +++ b/.github/workflows/reusable-get-go-version.yml @@ -14,7 +14,7 @@ jobs: outputs: go-version: ${{ steps.get-go-version.outputs.go-version }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Determine Go version id: get-go-version # We use .go-version as our source of truth for current Go diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 2ce2e88d85..c2ae3f0a2b 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -16,7 +16,13 @@ concurrency: cancel-in-progress: true jobs: + conditional-skip: + uses: ./.github/workflows/reusable-conditional-skip.yml + get-go-version: + # Cascades down to test jobs + needs: [ conditional-skip ] + if: needs.conditional-skip.outputs.skip-ci != 'true' uses: ./.github/workflows/reusable-get-go-version.yml scan: @@ -29,15 +35,15 @@ jobs: && (github.actor != 'dependabot[bot]') && (github.actor != 'hc-github-team-consul-core') }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Set up Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: Clone Security Scanner repo - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: repository: hashicorp/security-scanner #TODO: replace w/ HASHIBOT_PRODSEC_GITHUB_TOKEN once provisioned @@ -58,6 +64,6 @@ jobs: cat results.sarif | jq - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@46a6823b81f2d7c67ddf123851eea88365bc8a67 # codeql-bundle-v2.13.5 + uses: github/codeql-action/upload-sarif@8fcfedf57053e09257688fce7a0beeb18b1b9ae3 # codeql-bundle-v2.17.2 with: sarif_file: results.sarif \ No newline at end of file diff --git a/.github/workflows/weekly-acceptance-1-3-x.yml b/.github/workflows/weekly-acceptance-0-49-x.yml similarity index 57% rename from .github/workflows/weekly-acceptance-1-3-x.yml rename to .github/workflows/weekly-acceptance-0-49-x.yml index 9d1a2d65a6..8b78542211 100644 --- a/.github/workflows/weekly-acceptance-1-3-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-3-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 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 env: - BRANCH: "release/1.3.x" + BRANCH: "release/0.49.x" CONTEXT: "weekly" jobs: @@ -18,11 +18,11 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@25b02cc069be46d637e8fe2f1e8484008e9e9609 # v1.2.3 name: cloud with: workflow: cloud.yml repo: hashicorp/consul-k8s-workflows ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' + inputs: '{ "test-ce": false, "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-2-x.yml b/.github/workflows/weekly-acceptance-1-0-x.yml similarity index 65% rename from .github/workflows/weekly-acceptance-1-2-x.yml rename to .github/workflows/weekly-acceptance-1-0-x.yml index 3dac6c8755..0dfeaef1a1 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,11 +19,11 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@25b02cc069be46d637e8fe2f1e8484008e9e9609 # v1.2.3 name: cloud with: workflow: cloud.yml repo: hashicorp/consul-k8s-workflows ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' + inputs: '{ "test-ce": false, "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 c3c39fef32..0693c977b9 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,11 +19,11 @@ jobs: name: cloud runs-on: ubuntu-latest steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 + - uses: benc-uk/workflow-dispatch@25b02cc069be46d637e8fe2f1e8484008e9e9609 # v1.2.3 name: cloud with: workflow: cloud.yml repo: hashicorp/consul-k8s-workflows ref: main token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' + inputs: '{ "test-ce": false, "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/.github/workflows/weekly-acceptance-1-4-x.yml b/.github/workflows/weekly-acceptance-1-4-x.yml deleted file mode 100644 index a6bbe05e6b..0000000000 --- a/.github/workflows/weekly-acceptance-1-4-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-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 }}" }' diff --git a/.gitignore b/.gitignore index a2c0031a1d..ecc38e82e0 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,3 @@ pkg/ .idea/ .vscode .bob/ -control-plane/cni/cni diff --git a/.go-version b/.go-version index f124bfa155..da9594fd66 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.21.9 +1.22.5 diff --git a/CHANGELOG.md b/CHANGELOG.md index a8fc92156d..2a98841eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,42 +1,53 @@ -## 1.4.0 (February 29, 2024) +## 1.1.14 (July 16, 2024) -> NOTE: Consul K8s 1.4.x is compatible with Consul 1.18.x and Consul Dataplane 1.4.x. Refer to our [compatibility matrix](https://developer.hashicorp.com/consul/docs/k8s/compatibility) for more info. +SECURITY: -BREAKING CHANGES: +* Upgrade go version to 1.22.5 to address [CVE-2024-24791](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-24791) [[GH-4154](https://github.com/hashicorp/consul-k8s/issues/4154)] +* Upgrade go-retryablehttp to v0.7.7 to address [GHSA-v6v8-xj6m-xwqh](https://github.com/advisories/GHSA-v6v8-xj6m-xwqh) [[GH-4169](https://github.com/hashicorp/consul-k8s/issues/4169)] -* 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. +IMPROVEMENTS: -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: +* upgrade go version to v1.22.4. [[GH-4085](https://github.com/hashicorp/consul-k8s/issues/4085)] +* partition-init: Role no longer includes unnecessary access to Secrets resource. [[GH-4053](https://github.com/hashicorp/consul-k8s/issues/4053)] - server: - extraConfig: | - {"autopilot": {"min_quorum": 0, "disable_upgrade_migration": false}} [[GH-3000](https://github.com/hashicorp/consul-k8s/issues/3000)] -* server: set `leave_on_terminate` to `true` and set the server pod disruption budget `maxUnavailable` to `1`. +BUG FIXES: -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. +* cni: fix incorrect release version due to unstable submodule pinning [[GH-4091](https://github.com/hashicorp/consul-k8s/issues/4091)] -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. +## 1.1.13 (May 24, 2024) -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. +IMPROVEMENTS: -For the vast majority of users, this change will be beneficial, however if you wish to remain with the old settings you -can set: +* upgrade go version to v1.22.3. [[GH-3994](https://github.com/hashicorp/consul-k8s/issues/3994)] +* Bump Dockerfile base image for `consul-k8s-control-plane` to `alpine:3.19`. [[GH-4016](https://github.com/hashicorp/consul-k8s/issues/4016)] - server: - extraConfig: | - {"leave_on_terminate": false} - disruptionBudget: - maxUnavailable: [[GH-3000](https://github.com/hashicorp/consul-k8s/issues/3000)] +## 1.1.12 (May 20, 2024) + +SECURITY: + +* Upgrade Go to use 1.21.10. This addresses CVEs + [CVE-2024-24787](https://nvd.nist.gov/vuln/detail/CVE-2024-24787) and + [CVE-2024-24788](https://nvd.nist.gov/vuln/detail/CVE-2024-24788) [[GH-3980](https://github.com/hashicorp/consul-k8s/issues/3980)] +* Upgrade `helm/v3` to 3.14.4. This resolves the following security vulnerabilities: + [CVE-2024-25620](https://osv.dev/vulnerability/CVE-2024-25620) + [CVE-2024-26147](https://osv.dev/vulnerability/CVE-2024-26147) [[GH-3935](https://github.com/hashicorp/consul-k8s/issues/3935)] +* Upgrade to use Go `1.21.9`. This resolves CVE + [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`http2`). [[GH-3900](https://github.com/hashicorp/consul-k8s/issues/3900)] +* Upgrade to use golang.org/x/net `v0.24.0`. This resolves CVE + [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`x/net`). [[GH-3900](https://github.com/hashicorp/consul-k8s/issues/3900)] + +IMPROVEMENTS: + +* ConfigEntries controller: Only error for config entries from different datacenters when the config entries are different [[GH-3873](https://github.com/hashicorp/consul-k8s/issues/3873)] + +## 1.1.11 (March 28, 2024) 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)] +* Update `google.golang.org/protobuf` to v1.33.0 to address [CVE-2024-24786](https://nvd.nist.gov/vuln/detail/CVE-2024-24786). [[GH-3719](https://github.com/hashicorp/consul-k8s/issues/3719)] +* Update the Consul Build Go base image to `alpine3.19`. This resolves CVEs +[CVE-2023-52425](https://nvd.nist.gov/vuln/detail/CVE-2023-52425) +[CVE-2023-52426⁠](https://nvd.nist.gov/vuln/detail/CVE-2023-52426) [[GH-3741](https://github.com/hashicorp/consul-k8s/issues/3741)] * Upgrade `helm/v3` to 3.11.3. This resolves the following security vulnerabilities: [CVE-2023-25165](https://osv.dev/vulnerability/CVE-2023-25165) [CVE-2022-23524](https://osv.dev/vulnerability/CVE-2022-23524) @@ -45,85 +56,24 @@ SECURITY: * Upgrade docker/distribution to 2.8.3+incompatible (latest) to resolve [CVE-2023-2253](https://osv.dev/vulnerability/CVE-2023-2253). [[GH-3625](https://github.com/hashicorp/consul-k8s/issues/3625)] * Upgrade docker/docker to 25.0.3+incompatible (latest) to resolve [GHSA-jq35-85cj-fj4p](https://osv.dev/vulnerability/GHSA-jq35-85cj-fj4p). [[GH-3625](https://github.com/hashicorp/consul-k8s/issues/3625)] * Upgrade filepath-securejoin to 0.2.4 (latest) to resolve [GO-2023-2048](https://osv.dev/vulnerability/GO-2023-2048). [[GH-3625](https://github.com/hashicorp/consul-k8s/issues/3625)] -* Upgrade containerd to 1.7.13 (latest) to resolve [GHSA-7ww5-4wqc-m92c](https://osv.dev/vulnerability/GO-2023-2412). [[GH-3625](https://github.com/hashicorp/consul-k8s/issues/3625)] +* Upgrade to use Go `1.21.8`. This resolves CVEs +[CVE-2024-24783](https://nvd.nist.gov/vuln/detail/CVE-2024-24783) (`crypto/x509`). +[CVE-2023-45290](https://nvd.nist.gov/vuln/detail/CVE-2023-45290) (`net/http`). +[CVE-2023-45289](https://nvd.nist.gov/vuln/detail/CVE-2023-45289) (`net/http`, `net/http/cookiejar`). +[CVE-2024-24785](https://nvd.nist.gov/vuln/detail/CVE-2024-24785) (`html/template`). +[CVE-2024-24784](https://nvd.nist.gov/vuln/detail/CVE-2024-24784) (`net/mail`). [[GH-3741](https://github.com/hashicorp/consul-k8s/issues/3741)] +* security: upgrade containerd to 1.7.13 (latest) to resolve [GHSA-7ww5-4wqc-m92c](https://osv.dev/vulnerability/GO-2023-2412). [[GH-3625](https://github.com/hashicorp/consul-k8s/issues/3625)] IMPROVEMENTS: * control-plane: publish `consul-k8s-control-plane` and `consul-k8s-control-plane-fips` images to official HashiCorp AWS ECR. [[GH-3668](https://github.com/hashicorp/consul-k8s/issues/3668)] -* helm: Kubernetes v1.29 is now supported. Minimum tested version of Kubernetes is now v1.26. [[GH-3675](https://github.com/hashicorp/consul-k8s/issues/3675)] -* 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). [[GH-3498](https://github.com/hashicorp/consul-k8s/issues/3498)] -* control-plane: Add `CaseInsensitive` flag to service-routers that allows paths and path prefixes to ignore URL upper and lower casing. [[GH-3502](https://github.com/hashicorp/consul-k8s/issues/3502)] - -BUG FIXES: - -* consul-telemetry-collector: fix args to consul-dataplane when global.acls.manageSystemACLs [[GH-3184](https://github.com/hashicorp/consul-k8s/issues/3184)] - -NOTES: - -* build: Releases will now also be available as Debian and RPM packages for the arm64 architecture, refer to the -[Official Packaging Guide](https://www.hashicorp.com/official-packaging-guide) for more information. [[GH-3428](https://github.com/hashicorp/consul-k8s/issues/3428)] - -## 1.3.3 (February 15, 2024) - -FEATURES: - -* helm: introduces `global.metrics.datadog` overrides to streamline consul-k8s datadog integration. -helm: introduces `server.enableAgentDebug` to expose agent [`enable_debug`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#enable_debug) configuration. -helm: introduces `global.metrics.disableAgentHostName` to expose agent [`telemetry.disable_hostname`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-disable_hostname) configuration. -helm: introduces `global.metrics.enableHostMetrics` to expose agent [`telemetry.enable_host_metrics`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-enable_host_metrics) configuration. -helm: introduces `global.metrics.prefixFilter` to expose agent [`telemetry.prefix_filter`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-prefix_filter) configuration. -helm: introduces `global.metrics.datadog.dogstatsd.dogstatsdAddr` to expose agent [`telemetry.dogstatsd_addr`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-dogstatsd_addr) configuration. -helm: introduces `global.metrics.datadog.dogstatsd.dogstatsdTags` to expose agent [`telemetry.dogstatsd_tags`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-dogstatsd_tags) configuration. -helm: introduces required `ad.datadoghq.com/` annotations and `tags.datadoghq.com/` labels for integration with [Datadog Autodiscovery](https://docs.datadoghq.com/integrations/consul/?tab=containerized) and [Datadog Unified Service Tagging](https://docs.datadoghq.com/getting_started/tagging/unified_service_tagging/?tab=kubernetes#serverless-environment) for Consul. -helm: introduces automated unix domain socket hostPath mounting for containerized integration with datadog within consul-server statefulset. -helm: introduces `global.metrics.datadog.otlp` override options to allow OTLP metrics forwarding to Datadog Agent. -control-plane: adds `server-acl-init` datadog agent token creation for datadog integration. [[GH-3407](https://github.com/hashicorp/consul-k8s/issues/3407)] - -IMPROVEMENTS: - -* Upgrade to use Go 1.21.7. [[GH-3591](https://github.com/hashicorp/consul-k8s/issues/3591)] -* api-gateway: Apply `connectInject.initContainer.resources` to the init container for API gateway Pods. [[GH-3531](https://github.com/hashicorp/consul-k8s/issues/3531)] -* 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). [[GH-3498](https://github.com/hashicorp/consul-k8s/issues/3498)] -* control-plane: Add `CaseInsensitive` flag to service-routers that allows paths and path prefixes to ignore URL upper and lower casing. [[GH-3502](https://github.com/hashicorp/consul-k8s/issues/3502)] -* helm: Change `/bin/sh -ec ""` to `/bin/sh -ec "exec "` in helm deployments [[GH-3548](https://github.com/hashicorp/consul-k8s/issues/3548)] - -BUG FIXES: - -* api-gateway: fix issue where external annotations and labels are being incorrectly deleted on services controlled by the API Gateway [[GH-3597](https://github.com/hashicorp/consul-k8s/issues/3597)] -* mesh-gw: update capabilities on the security context needed for the dataplane container. -Adds NET_BIND_SERVICE to capabilities.add -Adds ALL to capabilities.drop unless .Values.meshGateway.hostNetwork is true [[GH-3549](https://github.com/hashicorp/consul-k8s/issues/3549)] - -## 1.2.6 (February 15, 2024) - -FEATURES: - -* helm: introduces `global.metrics.datadog` overrides to streamline consul-k8s datadog integration. -helm: introduces `server.enableAgentDebug` to expose agent [`enable_debug`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#enable_debug) configuration. -helm: introduces `global.metrics.disableAgentHostName` to expose agent [`telemetry.disable_hostname`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-disable_hostname) configuration. -helm: introduces `global.metrics.enableHostMetrics` to expose agent [`telemetry.enable_host_metrics`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-enable_host_metrics) configuration. -helm: introduces `global.metrics.prefixFilter` to expose agent [`telemetry.prefix_filter`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-prefix_filter) configuration. -helm: introduces `global.metrics.datadog.dogstatsd.dogstatsdAddr` to expose agent [`telemetry.dogstatsd_addr`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-dogstatsd_addr) configuration. -helm: introduces `global.metrics.datadog.dogstatsd.dogstatsdTags` to expose agent [`telemetry.dogstatsd_tags`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#telemetry-dogstatsd_tags) configuration. -helm: introduces required `ad.datadoghq.com/` annotations and `tags.datadoghq.com/` labels for integration with [Datadog Autodiscovery](https://docs.datadoghq.com/integrations/consul/?tab=containerized) and [Datadog Unified Service Tagging](https://docs.datadoghq.com/getting_started/tagging/unified_service_tagging/?tab=kubernetes#serverless-environment) for Consul. -helm: introduces automated unix domain socket hostPath mounting for containerized integration with datadog within consul-server statefulset. -helm: introduces `global.metrics.datadog.otlp` override options to allow OTLP metrics forwarding to Datadog Agent. -control-plane: adds `server-acl-init` datadog agent token creation for datadog integration. [[GH-3407](https://github.com/hashicorp/consul-k8s/issues/3407)] - -IMPROVEMENTS: - -* Upgrade to use Go 1.21.7. [[GH-3591](https://github.com/hashicorp/consul-k8s/issues/3591)] -* api-gateway: Apply `connectInject.initContainer.resources` to the init container for API gateway Pods. [[GH-3531](https://github.com/hashicorp/consul-k8s/issues/3531)] -* 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). [[GH-3498](https://github.com/hashicorp/consul-k8s/issues/3498)] -* 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: Change `/bin/sh -ec ""` to `/bin/sh -ec "exec "` in helm deployments [[GH-3548](https://github.com/hashicorp/consul-k8s/issues/3548)] BUG FIXES: -* api-gateway: fix issue where external annotations and labels are being incorrectly deleted on services controlled by the API Gateway [[GH-3597](https://github.com/hashicorp/consul-k8s/issues/3597)] -* mesh-gw: update capabilities on the security context needed for the dataplane container. -Adds NET_BIND_SERVICE to capabilities.add -Adds ALL to capabilities.drop unless .Values.meshGateway.hostNetwork is true [[GH-3549](https://github.com/hashicorp/consul-k8s/issues/3549)] +* control-plane: fix an issue where ACL token cleanup did not respect a pod's GracefulShutdownPeriodSeconds and +tokens were invalidated immediately on pod entering Terminating state. [[GH-3736](https://github.com/hashicorp/consul-k8s/issues/3736)] +* control-plane: fix an issue where ACL tokens would prematurely be deleted and services would be deregistered if there +was a K8s API error fetching the pod. [[GH-3758](https://github.com/hashicorp/consul-k8s/issues/3758)] ## 1.1.10 (February 15, 2024) @@ -139,40 +89,6 @@ BUG FIXES: Adds NET_BIND_SERVICE to capabilities.add Adds ALL to capabilities.drop unless .Values.meshGateway.hostNetwork is true [[GH-3549](https://github.com/hashicorp/consul-k8s/issues/3549)] -## 1.3.2 (Jan 25, 2024) - -SECURITY: - -* Update `golang.org/x/crypto` to v0.17.0 to address [CVE-2023-48795](https://nvd.nist.gov/vuln/detail/CVE-2023-48795). [[GH-3442](https://github.com/hashicorp/consul-k8s/issues/3442)] -* Upgrade OpenShift container images to use `ubi-minimal:9.3` as the base image. [[GH-3418](https://github.com/hashicorp/consul-k8s/issues/3418)] - -IMPROVEMENTS: - -* Upgrade to use Go 1.21.6. [[GH-3478](https://github.com/hashicorp/consul-k8s/issues/3478)] -* 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. [[GH-3450](https://github.com/hashicorp/consul-k8s/issues/3450)] -* control-plane: reduce Consul Catalog API requests required for endpoints reconcile in large clusters [[GH-3322](https://github.com/hashicorp/consul-k8s/issues/3322)] - -BUG FIXES: - -* api-gateway: fix issue where deleting an http-route in a non-default namespace would not remove the route from Consul. [[GH-3440](https://github.com/hashicorp/consul-k8s/issues/3440)] - -## 1.2.5 (Jan 25, 2024) - -SECURITY: - -* Update `golang.org/x/crypto` to v0.17.0 to address [CVE-2023-48795](https://nvd.nist.gov/vuln/detail/CVE-2023-48795). [[GH-3442](https://github.com/hashicorp/consul-k8s/issues/3442)] -* Upgrade to use `ubi-minimal:9.3` for OpenShift container images. [[GH-3418](https://github.com/hashicorp/consul-k8s/issues/3418)] - -IMPROVEMENTS: - -* Upgrade to use Go 1.21.6. [[GH-3478](https://github.com/hashicorp/consul-k8s/issues/3478)] -* 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. [[GH-3450](https://github.com/hashicorp/consul-k8s/issues/3450)] -* control-plane: reduce Consul Catalog API requests required for endpoints reconcile in large clusters [[GH-3322](https://github.com/hashicorp/consul-k8s/issues/3322)] - -BUG FIXES: - -* api-gateway: fix issue where deleting an http-route in a non-default namespace would not remove the route from Consul. [[GH-3440](https://github.com/hashicorp/consul-k8s/issues/3440)] - ## 1.1.9 (Jan 25, 2024) SECURITY: @@ -186,79 +102,6 @@ IMPROVEMENTS: * 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. [[GH-3450](https://github.com/hashicorp/consul-k8s/issues/3450)] * control-plane: reduce Consul Catalog API requests required for endpoints reconcile in large clusters [[GH-3322](https://github.com/hashicorp/consul-k8s/issues/3322)] -## 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: @@ -292,66 +135,6 @@ BUG FIXES: * 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: @@ -363,171 +146,52 @@ 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.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: - -* 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)] +[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)] +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)] +## 1.1.5 (September 6, 2023) 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: - -* 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)] +* 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.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)] +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)] +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: @@ -553,93 +217,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)] -## 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: - -* 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: @@ -666,63 +243,6 @@ 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: - -* 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: @@ -763,56 +283,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)] -## 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: @@ -825,45 +295,6 @@ 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: - -* 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) BREAKING CHANGES: @@ -903,7 +334,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)] + * 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)] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 562f9c89ed..6ad56baacc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,14 +7,14 @@ 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) 1. [Webhook](#webhook) 1. [Update command.go](#update-commandgo) 1. [Generating YAML](#generating-yaml) - 1. [Updating consul-helm](#updating-helm-chart) + 1. [Updating consul-helm](#updating-consul-helm) 1. [Testing a new CRD](#testing-a-new-crd) 1. [Update Consul K8s acceptance tests](#update-consul-k8s-acceptance-tests) 1. [Adding a new ACL Token](#adding-a-new-acl-token) @@ -24,20 +24,19 @@ 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,7 +421,7 @@ 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 @@ -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 ``` @@ -1209,26 +1209,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 +1241,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 bb665fc5e7..4bc26d5eac 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION = $(shell ./control-plane/build-support/scripts/version.sh control-plane/version/version.go) +VERSION = $(shell ./control-plane/build-support/scripts/version.sh 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) @@ -19,15 +19,6 @@ 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 ./... -.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 @@ -36,7 +27,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 --os linux --arch amd64 + @$(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 @@ -44,7 +35,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 --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)' \ @@ -57,7 +48,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 --os linux --arch $(GOARCH) + @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a $(GOARCH) @docker build -t '$(DEV_IMAGE)' \ --build-arg 'GOLANG_VERSION=$(GOLANG_VERSION)' \ --build-arg 'TARGETARCH=$(GOARCH)' \ @@ -71,7 +62,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 --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 \ @@ -82,19 +73,6 @@ 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 --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 ./... @@ -145,11 +123,6 @@ 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/ -.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 @@ -190,17 +163,12 @@ 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 (works in all cases) +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) -.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) @@ -218,9 +186,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 .PHONY: get-controller-gen @@ -231,7 +197,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.14.0 ;\ rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ } CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen @@ -240,12 +206,12 @@ CONTROLLER_GEN=$(shell which controller-gen) endif .PHONY: ensure-controller-gen-version -ensure-controller-gen-version: ## Ensure controller-gen version is v0.12.1. +ensure-controller-gen-version: ## Ensure controller-gen version is v0.14.0. 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'." +ifeq (, $(shell $(CONTROLLER_GEN) --version | grep v0.14.0)) + @echo "controller-gen version is not v0.14.0, uninstall the binary and install the correct version with 'make get-controller-gen'." @echo "Found version: $(shell $(CONTROLLER_GEN) --version)" @exit 1 else @@ -259,7 +225,7 @@ ifeq (, $(shell which copywrite)) @echo "Installing copywrite" @go install github.com/hashicorp/copywrite@latest endif - @copywrite headers --spdx "MPL-2.0" + @copywrite headers --spdx "MPL-2.0" ##@ CI Targets @@ -404,7 +370,7 @@ 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) + $(error CONSUL_K8S_NEXT_RELEASE_VERSION is required) endif ifndef CONSUL_K8S_CONSUL_VERSION $(error CONSUL_K8S_CONSUL_VERSION is required) @@ -440,4 +406,4 @@ CRD_OPTIONS ?= "crd:ignoreUnexportedFields=true,allowDangerousTypes=true" # 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) + @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) \ No newline at end of file diff --git a/README.md b/README.md index e59e5b8577..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.26.x - 1.29.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..9b36143c6b 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 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..9b36143c6b 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 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..9b36143c6b 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 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 5271ee68ba..964103ed0b 100644 --- a/acceptance/ci-inputs/kind-inputs.yaml +++ b/acceptance/ci-inputs/kind-inputs.yaml @@ -1,6 +1,6 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -kindVersion: v0.22.0 -kindNodeImage: kindest/node:v1.29.2@sha256:51a1434a5397193442f0be2a297b488b6c919ce8a3931be0ce822606ea5ca245 +kindVersion: v0.19.0 +kindNodeImage: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72 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..5d42f4af66 100644 --- a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml @@ -4,9 +4,7 @@ # 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..11a158269f 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 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..197b444c84 100644 --- a/acceptance/framework/config/config.go +++ b/acceptance/framework/config/config.go @@ -92,18 +92,13 @@ type TestConfig struct { 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 +151,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 +231,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/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 2746b43348..0ec1dafd31 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -11,25 +11,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 +48,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 +61,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 +89,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 +109,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: 60 * 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 +133,32 @@ 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, c.Ctx.KubectlOptions(t), 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, c.Ctx.KubectlOptions(t), 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, c.Ctx.KubectlOptions(t), 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 +171,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 +181,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 +197,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 primarily 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 +252,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 +277,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..946a6040f1 100644 --- a/acceptance/framework/consul/cli_cluster.go +++ b/acceptance/framework/consul/cli_cluster.go @@ -45,7 +45,6 @@ type CLICluster struct { kubeConfig string kubeContext string noCleanupOnFailure bool - noCleanup bool debugDirectory string logger terratestLogger.TestLogger cli cli.CLI @@ -110,7 +109,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 +122,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 +201,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 +223,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 +243,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( @@ -263,8 +256,11 @@ func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool, release ...str c.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) { - require.NoError(r, tunnel.ForwardPortE(r)) + retry.RunWith(&retry.Counter{Wait: 1 * time.Second, Count: 3}, 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() { diff --git a/acceptance/framework/consul/cluster.go b/acceptance/framework/consul/cluster.go index 1b9a543245..734f07f36e 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, 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 fafaceaca1..ee5476be88 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -12,31 +12,21 @@ 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/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" "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/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/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" ) // HelmCluster implements Cluster and uses Helm @@ -51,17 +41,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 } @@ -73,10 +57,6 @@ 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) } @@ -118,10 +98,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, } @@ -130,12 +108,9 @@ func NewHelmCluster( func (h *HelmCluster) Create(t *testing.T) { t.Helper() - // check and remove any CRDs with finalizers - helpers.GetCRDRemoveFinalizers(t, h.helmOptions.KubectlOptions) - // 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) }) @@ -155,15 +130,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)) } @@ -178,59 +145,23 @@ func (h *HelmCluster) Destroy(t *testing.T) { h.helmOptions.ExtraArgs = map[string][]string{ "--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}) + err := helm.DeleteE(t, h.helmOptions, h.releaseName, false) 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) - 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) + require.NoError(t, err) } } } @@ -240,9 +171,7 @@ func (h *HelmCluster) Destroy(t *testing.T) { 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{}) + err := h.kubernetesClient.AppsV1().Deployments(h.helmOptions.KubectlOptions.Namespace).Delete(context.Background(), deployment.Name, metav1.DeleteOptions{}) if !errors.IsNotFound(err) { require.NoError(r, err) } @@ -299,64 +228,64 @@ func (h *HelmCluster) Destroy(t *testing.T) { // 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) } } } @@ -479,44 +408,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 @@ -533,28 +432,26 @@ 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) { - // 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{}) - if err != nil && errors.IsNotFound(err) { - 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"]) - } else if err == nil { - config.Token = string(aclSecret.Data["token"]) - } else { - require.NoError(r, err) - } - }) + // 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) + } } } - config.Address = h.CreatePortForwardTunnel(t, remotePort, release...) + config.Address = h.CreatePortForwardTunnel(t, remotePort) consulClient, err := api.NewClient(config) require.NoError(t, err) @@ -661,7 +558,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{}) @@ -672,70 +569,48 @@ 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 privileged Security Context Constraints on OpenShift. +// 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) { + const anyuidClusterRole = "system:openshift:scc:anyuid" const privilegedClusterRole = "system:openshift:scc:privileged" + anyuidRoleBinding := "anyuid-test" privilegedRoleBinding := "privileged-test" // A role binding to allow default service account in the installation namespace access to the SCCs. - // Check if this cluster role binding already exists. - _, err := client.RbacV1().RoleBindings(namespace).Get(context.Background(), privilegedRoleBinding, metav1.GetOptions{}) + { + for clusterRoleName, roleBindingName := range map[string]string{anyuidClusterRole: anyuidRoleBinding, privilegedClusterRole: privilegedRoleBinding} { + // Check if this cluster role binding already exists. + _, err := client.RbacV1().RoleBindings(namespace).Get(context.Background(), roleBindingName, metav1.GetOptions{}) + + if errors.IsNotFound(err) { + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleBindingName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.ServiceAccountKind, + Name: "default", + Namespace: namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: clusterRoleName, + }, + } - if errors.IsNotFound(err) { - roleBinding := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: privilegedRoleBinding, - }, - Subjects: []rbacv1.Subject{ - { - Kind: rbacv1.ServiceAccountKind, - Name: "default", - Namespace: namespace, - }, - }, - RoleRef: rbacv1.RoleRef{ - Kind: "ClusterRole", - Name: privilegedClusterRole, - }, + _, err = client.RbacV1().RoleBindings(namespace).Create(context.Background(), roleBinding, metav1.CreateOptions{}) + require.NoError(t, err) + } else { + require.NoError(t, err) + } } - - _, err = client.RbacV1().RoleBindings(namespace).Create(context.Background(), roleBinding, metav1.CreateOptions{}) - require.NoError(t, err) - } else { - require.NoError(t, err) } - 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,25 +636,23 @@ 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) { - _, 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) - } - }) + _, 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) + } - 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 1d2b744bea..d4ae11a655 100644 --- a/acceptance/framework/consul/helm_cluster_test.go +++ b/acceptance/framework/consul/helm_cluster_test.go @@ -7,15 +7,11 @@ import ( "testing" "github.com/gruntwork-io/terratest/modules/k8s" - "github.com/hashicorp/consul/sdk/testutil" + "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" - "sigs.k8s.io/controller-runtime/pkg/client" - runtimefake "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/acceptance/framework/config" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" ) // Test that if TestConfig has values that need to be provided @@ -83,17 +79,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/environment.go b/acceptance/framework/environment/environment.go index 9ceecf3e96..ba365436b1 100644 --- a/acceptance/framework/environment/environment.go +++ b/acceptance/framework/environment/environment.go @@ -5,18 +5,13 @@ 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 +21,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 +59,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 +76,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 +101,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 +140,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 +153,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 +163,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..3e9b733047 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. "+ @@ -118,13 +107,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, 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.") + "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. "+ @@ -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/helpers/helpers.go b/acceptance/framework/helpers/helpers.go index 0871532426..a2d98264c8 100644 --- a/acceptance/framework/helpers/helpers.go +++ b/acceptance/framework/helpers/helpers.go @@ -42,12 +42,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: 3}, 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) }) @@ -62,7 +62,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 { @@ -88,9 +88,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. @@ -100,7 +101,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 { diff --git a/acceptance/framework/k8s/deploy.go b/acceptance/framework/k8s/deploy.go index e1d9f01a80..771aab3fe1 100644 --- a/acceptance/framework/k8s/deploy.go +++ b/acceptance/framework/k8s/deploy.go @@ -16,13 +16,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 +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, 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 +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, 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 +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, 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 +72,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,22 +93,19 @@ 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 } - retrier := &retry.Counter{Count: 30, Wait: 2 * time.Second} + 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 +153,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..2b9fa577c5 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 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 +113,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 +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(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 4fe54b9d5c..586389ead5 100644 --- a/acceptance/framework/k8s/kubectl.go +++ b/acceptance/framework/k8s/kubectl.go @@ -4,7 +4,6 @@ package k8s import ( - "fmt" "strings" "testing" "time" @@ -13,15 +12,10 @@ import ( terratestLogger "github.com/gruntwork-io/terratest/modules/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "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 +29,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) @@ -103,7 +97,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 +107,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/portforward/port_forward.go b/acceptance/framework/portforward/port_forward.go index ac9795c98c..cfd35ccf66 100644 --- a/acceptance/framework/portforward/port_forward.go +++ b/acceptance/framework/portforward/port_forward.go @@ -16,7 +16,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 +27,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: 1 * time.Second, Count: 3}, 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/resource/helpers.go b/acceptance/framework/resource/helpers.go deleted file mode 100644 index 4e94f73faf..0000000000 --- a/acceptance/framework/resource/helpers.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resource - -import ( - "context" - "time" - - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/consul/sdk/testutil/retry" -) - -// ResourceTester is a helper for making assertions about resources. -type ResourceTester struct { - // resourceClient is the client to use for resource operations. - resourceClient pbresource.ResourceServiceClient - // timeout is the total time across which to apply retries. - timeout time.Duration - // wait is the wait time between retries. - wait time.Duration - // token is the token to use for requests when ACLs are enabled. - token string -} - -func NewResourceTester(resourceClient pbresource.ResourceServiceClient) *ResourceTester { - return &ResourceTester{ - resourceClient: resourceClient, - timeout: 7 * time.Second, - wait: 25 * time.Millisecond, - } -} - -func (rt *ResourceTester) retry(t testutil.TestingTB, fn func(r *retry.R)) { - t.Helper() - retryer := &retry.Timer{Timeout: rt.timeout, Wait: rt.wait} - retry.RunWith(retryer, t, fn) -} - -func (rt *ResourceTester) Context(t testutil.TestingTB) context.Context { - ctx := testutil.TestContext(t) - - if rt.token != "" { - md := metadata.New(map[string]string{ - "x-consul-token": rt.token, - }) - ctx = metadata.NewOutgoingContext(ctx, md) - } - - return ctx -} - -func (rt *ResourceTester) RequireResourceExists(t testutil.TestingTB, id *pbresource.ID) *pbresource.Resource { - t.Helper() - - rsp, err := rt.resourceClient.Read(rt.Context(t), &pbresource.ReadRequest{Id: id}) - require.NoError(t, err, "error reading %s with type %v", id.Name, id.Type) - require.NotNil(t, rsp) - return rsp.Resource -} - -func (rt *ResourceTester) RequireResourceNotFound(t testutil.TestingTB, id *pbresource.ID) { - t.Helper() - - rsp, err := rt.resourceClient.Read(rt.Context(t), &pbresource.ReadRequest{Id: id}) - require.Error(t, err) - require.Equal(t, codes.NotFound, status.Code(err)) - require.Nil(t, rsp) -} - -func (rt *ResourceTester) WaitForResourceExists(t testutil.TestingTB, id *pbresource.ID) *pbresource.Resource { - t.Helper() - - var res *pbresource.Resource - rt.retry(t, func(r *retry.R) { - res = rt.RequireResourceExists(r, id) - }) - - return res -} - -func (rt *ResourceTester) WaitForResourceNotFound(t testutil.TestingTB, id *pbresource.ID) { - t.Helper() - - rt.retry(t, func(r *retry.R) { - rt.RequireResourceNotFound(r, id) - }) -} diff --git a/acceptance/framework/vault/vault_cluster.go b/acceptance/framework/vault/vault_cluster.go index b43957c924..9b8222fced 100644 --- a/acceptance/framework/vault/vault_cluster.go +++ b/acceptance/framework/vault/vault_cluster.go @@ -6,8 +6,6 @@ package vault import ( "context" "fmt" - "os" - "strings" "testing" "time" @@ -15,13 +13,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 +44,6 @@ type VaultCluster struct { kubernetesClient kubernetes.Interface noCleanupOnFailure bool - noCleanup bool debugDirectory string logger terratestLogger.TestLogger } @@ -59,45 +54,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 +82,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 +92,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 +112,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 +169,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 +217,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 +339,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 +390,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 +412,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 967e7fdab7..49a48b6f27 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -1,151 +1,123 @@ module github.com/hashicorp/consul-k8s/acceptance -go 1.21.1 +go 1.21 -toolchain go1.22.0 - -replace ( - k8s.io/api => k8s.io/api v0.26.4 - k8s.io/apimachinery => k8s.io/apimachinery v0.26.4 - k8s.io/client-go => k8s.io/client-go v0.26.4 -) +toolchain go1.22.5 require ( github.com/google/uuid v1.3.0 - github.com/gruntwork-io/terratest v0.46.7 - github.com/hashicorp/consul-k8s/control-plane v0.0.0-20240226161840-f3842c41cb2b - github.com/hashicorp/consul/api v1.28.2 - github.com/hashicorp/consul/proto-public v0.6.0 + 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.21.3 github.com/hashicorp/consul/sdk v0.16.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 + github.com/hashicorp/hcp-sdk-go v0.74.0 github.com/hashicorp/serf v0.10.1 github.com/hashicorp/vault/api v1.12.2 github.com/stretchr/testify v1.8.4 go.opentelemetry.io/proto/otlp v1.0.0 - google.golang.org/grpc v1.58.3 google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.28.4 - k8s.io/apimachinery v0.28.4 - k8s.io/client-go v0.28.4 - 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.28.13 + k8s.io/apimachinery v0.28.13 + k8s.io/client-go v0.28.13 ) require ( github.com/armon/go-metrics v0.4.1 // 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/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // 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.2 // 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.10.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.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.16.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect + github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // 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/errors v0.20.4 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // 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/runtime v0.26.0 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/strfmt v0.21.7 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/validate v0.22.2 // 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.4 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect - github.com/gruntwork-io/go-commons v0.8.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.5.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // 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/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.13 // indirect - github.com/jmespath/go-jmespath v0.4.0 // 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.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/miekg/dns v1.1.50 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/pointerstructure v1.2.1 // 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/ulid v1.3.1 // indirect - github.com/onsi/ginkgo/v2 v2.13.0 // indirect - github.com/onsi/gomega v1.28.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // 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.4.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/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/urfave/cli v1.22.12 // indirect - go.mongodb.org/mongo-driver v1.11.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + go.mongodb.org/mongo-driver v1.12.0 // indirect go.opentelemetry.io/otel v1.19.0 // indirect go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/sdk v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect golang.org/x/crypto v0.22.0 // indirect - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect - golang.org/x/mod v0.13.0 // indirect + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect golang.org/x/net v0.24.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/oauth2 v0.14.0 // indirect + golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.14.0 // 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-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.3 // indirect gopkg.in/inf.v0 v0.9.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-20230525220651-2546d827e515 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // 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/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/acceptance/go.sum b/acceptance/go.sum index aa5ac02746..fd2ca50c22 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -5,44 +5,63 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A 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 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 v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= 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/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= +github.com/Azure/azure-sdk-for-go v46.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +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.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +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/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/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= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +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.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +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/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +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/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= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 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/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/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= +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/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= @@ -51,128 +70,153 @@ 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/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= -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/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/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= +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= 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/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.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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= 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/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= +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-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/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-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 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/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/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= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v0.0.0-20200109221225-a4f60165b7a3/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +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-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= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 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 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +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.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.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 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.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +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/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-errors/errors v1.0.1/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-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-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-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= 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-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 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.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.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= -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/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M= +github.com/go-openapi/errors v0.20.4/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.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +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.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/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/runtime v0.26.0 h1:HYOFtG00FM1UvqrcxbEJg/SwvDRvYLQKGhw2zaQjTcc= +github.com/go-openapi/runtime v0.26.0/go.mod h1:QgRGeZwrUcSHdeh4Ka9Glvo0ug1LC5WyE+EV88plZrQ= +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.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/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= +github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k= +github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew= +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-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/validate v0.22.2 h1:Lda8nadL/5kIvS5mdXCAIuZ7IVXvKFIppLnw+EZh+n0= +github.com/go-openapi/validate v0.22.2/go.mod h1:kVxh31KbfsxU8ZyoHaDbLBWU5CnMdqBUEtadQ2G4d5M= 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= @@ -180,54 +224,26 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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/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= +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/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +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= @@ -235,9 +251,6 @@ 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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -245,84 +258,79 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z 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/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 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-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/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/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/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= -github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +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/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.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/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/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= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +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/go-commons v0.8.0 h1:k/yypwrPqSeYHevLlEDmvmgQzcyTwrlZGRaxEM6G0ro= -github.com/gruntwork-io/go-commons v0.8.0/go.mod h1:gtp0yTtIBExIZp7vyIV9I0XQkVwiQZze678hvDXof78= -github.com/gruntwork-io/terratest v0.46.7 h1:oqGPBBO87SEsvBYaA0R5xOq+Lm2Xc5dmFVfxEolfZeU= -github.com/gruntwork-io/terratest v0.46.7/go.mod h1:6gI5MlLeyF+SLwqocA5GBzcTix+XiuxCy1BPwKuT+WM= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20240226161840-f3842c41cb2b h1:AdeWjUb+rxrRryC5ZHaL32oOZuxubOzV2q6oJ97UMT0= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20240226161840-f3842c41cb2b/go.mod h1:TVaSJM7vYM/mtKGpVc/Lch53lrqLI9XAXJgy/gY8v4A= -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.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8= -github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE= -github.com/hashicorp/consul/proto-public v0.6.0 h1:9qrBujmoTB5gQQ84kQO+YWvhjgYoYBNrOoHdo4cpHHM= -github.com/hashicorp/consul/proto-public v0.6.0/go.mod h1:JF6983XNCzvw4wDNOLEwLqOq2IPw7iyT+pkswHSz08U= +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-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.21.3 h1:hHfaZwkrxbU6TjnlmxtSrvH0mXGep8aCXBrJ9nNhJug= +github.com/hashicorp/consul/api v1.21.3/go.mod h1:Kms78llSVzB8FtrLan+V+TSJTnfHkRGlYbhazvGzJUs= github.com/hashicorp/consul/sdk v0.16.0 h1:SE9m0W6DEfgIVCJX7xU+iv/hUl4m/nxqMTnCdMxDpJ8= github.com/hashicorp/consul/sdk v0.16.0/go.mod h1:7pxqqhqoaPqnBnzXD1StKed62LqJeClzVsUEy85Zr0A= 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.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -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.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/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-msgpack v0.5.5/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-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-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-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 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.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= @@ -342,12 +350,13 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru 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/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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/hcp-sdk-go v0.74.0 h1:41AS+wk78UgB4wM6oAwkB7lMSmOUQTOYQ9pT2Gc2glA= +github.com/hashicorp/hcp-sdk-go v0.74.0/go.mod h1:5GwdT+HGhEQsh4n1yK+RADnQkfOo6vHgr2BpYUt2t9U= 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= @@ -356,37 +365,39 @@ 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.12.2 h1:7YkCTE5Ni90TcmYHDBExdt4WGJxhpzaHqR6uGbQb/rE= github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJdpL6HUYed8KE= +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/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.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 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/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/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +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= +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= 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/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= @@ -394,16 +405,18 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +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= +github.com/magiconair/properties v1.8.0/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.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= @@ -412,6 +425,7 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb 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= @@ -419,16 +433,15 @@ 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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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 h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg= 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.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/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= @@ -437,84 +450,88 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG 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/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= -github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= -github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/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-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= -github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +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.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= +github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= +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.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +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.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +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/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= 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/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= 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 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.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.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-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.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/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/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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/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= 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= @@ -522,26 +539,36 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb 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/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/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= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 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/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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 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= @@ -554,34 +581,38 @@ 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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/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 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/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +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/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +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/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.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.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.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.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE= +go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= 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.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= @@ -592,20 +623,20 @@ go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1 go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.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.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/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-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-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-20191206172530-e9b2fee46413/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= @@ -613,17 +644,14 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 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= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 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/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/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/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= @@ -632,26 +660,22 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk 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/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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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-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= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -662,34 +686,20 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR 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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-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-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-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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= @@ -699,198 +709,148 @@ 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.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= +golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= 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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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= 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= 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= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/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-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= 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= -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-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-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-20200622214017-ed371f2e16b4/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-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-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 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= 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-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-20190206041539-40960b6deb8e/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-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-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= 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-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-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-20191205215504-7b8c8591a921/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/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-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-20200113040837-eac381796e91/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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-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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= 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= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= 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.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= @@ -901,28 +861,8 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn 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-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-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= @@ -933,14 +873,10 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi 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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 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.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -952,9 +888,6 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -964,57 +897,87 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 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/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/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= 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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/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.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 v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 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.26.4 h1:qSG2PmtcD23BkYiWfoYAcak870eF/hE7NNYBYavTT94= -k8s.io/api v0.26.4/go.mod h1:WwKEXU3R1rgCZ77AYa7DFksd9/BAIKyOmRlbVxgvjCk= -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.4 h1:rZccKdBLg9vP6J09JD+z8Yr99Ce8gk3Lbi9TCx05Jzs= -k8s.io/apimachinery v0.26.4/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= -k8s.io/client-go v0.26.4 h1:/7P/IbGBuT73A+G97trf44NTPSNqvuBREpOfdLbHvD4= -k8s.io/client-go v0.26.4/go.mod h1:6qOItWm3EwxJdl/8p5t7FWtWUOwyMdA8N9ekbW4idpI= -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-20230525220651-2546d827e515 h1:OmK1d0WrkD3IPfkskvroRykOulHVHf0s0ZIFRjyt+UI= -k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515/go.mod h1:kzo02I3kQ4BTtEfVLaPbjvCkX97YqGve33wzlb3fofQ= -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.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= +k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= +k8s.io/api v0.28.13 h1:0Sw8MjdkmrJAF/uVv09HXSZ3cQauVyZHQWKt8hiiKo4= +k8s.io/api v0.28.13/go.mod h1:7hlRF5wArzXf0qbRRT2TMtHRa5SHBEVJhA02JpTxj9Q= +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.28.13 h1:0O2mk2i0Yi+xkron0lK//biI21F1eGXb4eXECLU5v7g= +k8s.io/apimachinery v0.28.13/go.mod h1:zUG757HaKs6Dc3iGtKjzIpBfqTM4yiRsEe3/E7NX15o= +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.28.13 h1:kHgFOxWwAsa8VxL6Oylo10V6euobub9Jo0wyEWrhrWk= +k8s.io/client-go v0.28.13/go.mod h1:IudvInbWfd+6WLreEVnBnZJCGFaSROCFbny9jFTkk7g= +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/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= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +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.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-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-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +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-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/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= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +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/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 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.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +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 9880298a2b..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) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route", Namespace: "default"}, &httpRoute) - require.NoError(r, err) - - 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 - - // 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 13cf517382..0000000000 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ /dev/null @@ -1,718 +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") - - k8s.RunKubectl(t, ctx.KubectlOptions(t), "wait", "--for=condition=available", "--timeout=5m", fmt.Sprintf("deploy/%s", "static-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.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server-tcp") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "wait", "--for=condition=available", "--timeout=5m", fmt.Sprintf("deploy/%s", "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/api-gateway_v2/api_gateway_v2_test.go b/acceptance/tests/api-gateway_v2/api_gateway_v2_test.go deleted file mode 100644 index 9016d9c004..0000000000 --- a/acceptance/tests/api-gateway_v2/api_gateway_v2_test.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package apigatewayv2 - -import ( - "context" - "encoding/base64" - "fmt" - "strconv" - "testing" - "time" - - "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" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/types" -) - -// Test that api gateway basic functionality works in a default installation and a secure installation for V2. -func TestAPIGateway_V2_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", - "global.experiments[0]": "resource-apis", - } - - 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-v2") - 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-v2") - }) - - // 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-v2/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-v2/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") - - 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-v2/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-v2/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 meshv2beta1.APIGateway - 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 - checkV2StatusCondition(r, gateway.APIGatewayStatus.Conditions, trueV2Condition("Accepted", "Accepted")) - checkV2StatusCondition(r, gateway.APIGatewayStatus.Conditions, trueV2Condition("ConsulAccepted", "Accepted")) - require.Len(r, gateway.APIGatewayStatus.Listeners, 3) - - require.EqualValues(r, 1, gateway.APIGatewayStatus.Listeners[0].AttachedRoutes) - checkV2StatusCondition(r, gateway.APIGatewayStatus.Listeners[0].Conditions, trueV2Condition("Accepted", "Accepted")) - checkV2StatusCondition(r, gateway.APIGatewayStatus.Listeners[0].Conditions, falseV2Condition("Conflicted", "NoConflicts")) - checkV2StatusCondition(r, gateway.APIGatewayStatus.Listeners[0].Conditions, trueV2Condition("ResolvedRefs", "ResolvedRefs")) - require.EqualValues(r, 1, gateway.APIGatewayStatus.Listeners[1].AttachedRoutes) - checkV2StatusCondition(r, gateway.APIGatewayStatus.Listeners[1].Conditions, trueV2Condition("Accepted", "Accepted")) - checkV2StatusCondition(r, gateway.APIGatewayStatus.Listeners[1].Conditions, falseV2Condition("Conflicted", "NoConflicts")) - checkV2StatusCondition(r, gateway.APIGatewayStatus.Listeners[1].Conditions, trueV2Condition("ResolvedRefs", "ResolvedRefs")) - require.EqualValues(r, 1, gateway.APIGatewayStatus.Listeners[2].AttachedRoutes) - checkV2StatusCondition(r, gateway.APIGatewayStatus.Listeners[2].Conditions, trueV2Condition("Accepted", "Accepted")) - checkV2StatusCondition(r, gateway.APIGatewayStatus.Listeners[2].Conditions, falseV2Condition("Conflicted", "NoConflicts")) - checkV2StatusCondition(r, gateway.APIGatewayStatus.Listeners[2].Conditions, trueV2Condition("ResolvedRefs", "ResolvedRefs")) - - // check that we have an address to use - require.Len(r, gateway.APIGatewayStatus.Addresses, 1) - // now we know we have an address, set it so we can use it - gatewayAddress = gateway.APIGatewayStatus.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 meshv2beta1.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]) - - // tcp route checks - var tcpRoute meshv2beta1.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]) - - // TODO check values actually created in the resource API - - // finally we check that we can actually route to the service via the gateway - k8sOptions := ctx.KubectlOptions(t) - targetTCPAddress := fmt.Sprintf("http://%s:81", gatewayAddress) - - // 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 tcp") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetTCPAddress) - - }) - } -} diff --git a/acceptance/tests/api-gateway_v2/helpers.go b/acceptance/tests/api-gateway_v2/helpers.go deleted file mode 100644 index 124fa20450..0000000000 --- a/acceptance/tests/api-gateway_v2/helpers.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package apigatewayv2 - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - corev1 "k8s.io/api/core/v1" - "math/big" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -const ( - StaticClientName = "static-client" - gatewayClassControllerName = "mesh.consul.hashicorp.com/gateway-controller" - //TODO these values will likely need to be update to their V2 values for the test to pass. - gatewayClassFinalizer = "gateway-exists-finalizer.consul.hashicorp.com" - gatewayFinalizer = "gateway-finalizer.consul.hashicorp.com" -) - -type certificateInfo struct { - Cert *x509.Certificate - PrivateKey *rsa.PrivateKey - CertPEM []byte - PrivateKeyPEM []byte -} - -func checkV2StatusCondition(t require.TestingT, conditions []meshv2beta1.Condition, toCheck meshv2beta1.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 trueV2Condition(conditionType, reason string) meshv2beta1.Condition { - return meshv2beta1.Condition{ - Type: meshv2beta1.ConditionType(conditionType), - Reason: reason, - Status: corev1.ConditionTrue, - } -} - -func falseV2Condition(conditionType, reason string) meshv2beta1.Condition { - return meshv2beta1.Condition{ - Type: meshv2beta1.ConditionType(conditionType), - Reason: reason, - Status: corev1.ConditionFalse, - } -} - -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, - } -} diff --git a/acceptance/tests/api-gateway_v2/main_test.go b/acceptance/tests/api-gateway_v2/main_test.go deleted file mode 100644 index 47f4c3b3cf..0000000000 --- a/acceptance/tests/api-gateway_v2/main_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package apigatewayv2 - -import ( - "fmt" - "os" - "testing" - - testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" -) - -var suite testsuite.Suite - -func TestMain(m *testing.M) { - runTests := os.Getenv("TEST_APIGW_V2") - if runTests != "TRUE" { - fmt.Println("skipping") - os.Exit(0) - } - suite = testsuite.NewSuite(m) - os.Exit(suite.Run()) -} diff --git a/acceptance/tests/cli/cli_install_test.go b/acceptance/tests/cli/cli_install_test.go index cfee1560ae..eacccb1fdc 100644 --- a/acceptance/tests/cli/cli_install_test.go +++ b/acceptance/tests/cli/cli_install_test.go @@ -52,8 +52,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 +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(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) @@ -121,7 +121,7 @@ func TestInstall(t *testing.T) { logger.Log(t, string(proxyOut)) } - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + connHelper.TestConnectionSuccess(t) connHelper.TestConnectionFailureWhenUnhealthy(t) }) } 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/observability_test.go b/acceptance/tests/cloud/observability_test.go index af553b3ecb..3016ebb8b0 100644 --- a/acceptance/tests/cloud/observability_test.go +++ b/acceptance/tests/cloud/observability_test.go @@ -115,7 +115,7 @@ func TestObservabilityCloud(t *testing.T) { 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") + k8s.DeployKustomize(t, options, cfg.NoCleanupOnFailure, 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 { @@ -217,16 +217,12 @@ func TestObservabilityCloud(t *testing.T) { consulCluster.Create(t) logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, options, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + k8s.DeployKustomize(t, options, cfg.NoCleanupOnFailure, 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) + 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()) diff --git a/acceptance/tests/cloud/remote_dev_test.go b/acceptance/tests/cloud/remote_dev_test.go index 457dc4f269..aa7dbe70c7 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, cfg.NoCleanup, func() { + 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.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + 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/config-entries/config_entries_namespaces_test.go b/acceptance/tests/config-entries/config_entries_namespaces_test.go index aa74bdc2b5..05ea9e5235 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, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, func() { k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ns", KubeNS) }) @@ -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(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 +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: 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 +159,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 +216,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 +233,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 +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") - 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 +281,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 +331,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 +345,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 +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") - 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 +383,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 +417,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..d3c0d410a0 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -9,11 +9,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 +16,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 +84,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 +104,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 +178,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 +216,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 +283,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 +315,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 +358,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/connect/connect_external_servers_test.go b/acceptance/tests/connect/connect_external_servers_test.go index c0a61f160f..c95d791773 100644 --- a/acceptance/tests/connect/connect_external_servers_test.go +++ b/acceptance/tests/connect/connect_external_servers_test.go @@ -75,11 +75,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 +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, "-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..04021ec391 100644 --- a/acceptance/tests/connect/connect_inject_namespaces_test.go +++ b/acceptance/tests/connect/connect_inject_namespaces_test.go @@ -101,12 +101,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 +147,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 +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, "-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 +305,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 +315,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..3f0fb83939 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -51,45 +51,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 +84,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 +209,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 +269,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 +299,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 95502c9869..a67deac328 100644 --- a/acceptance/tests/connect/connect_proxy_lifecycle_test.go +++ b/acceptance/tests/connect/connect_proxy_lifecycle_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,7 +19,6 @@ import ( "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" ) @@ -40,19 +40,19 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { for _, testCfg := range []LifecycleShutdownConfig{ {secure: false, helmValues: map[string]string{ helmDrainListenersKey: "true", - helmGracePeriodSecondsKey: "5", + helmGracePeriodSecondsKey: "15", }}, {secure: true, helmValues: map[string]string{ helmDrainListenersKey: "true", - helmGracePeriodSecondsKey: "5", + helmGracePeriodSecondsKey: "15", }}, {secure: false, helmValues: map[string]string{ helmDrainListenersKey: "false", - helmGracePeriodSecondsKey: "5", + helmGracePeriodSecondsKey: "15", }}, {secure: true, helmValues: map[string]string{ helmDrainListenersKey: "false", - helmGracePeriodSecondsKey: "5", + helmGracePeriodSecondsKey: "15", }}, {secure: false, helmValues: map[string]string{ helmDrainListenersKey: "false", @@ -79,8 +79,8 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { gracePeriodSeconds, err = strconv.ParseInt(val, 10, 64) require.NoError(t, err) } else { - // 5s should be a good amount of time to confirm the pod doesn't terminate - gracePeriodSeconds = 5 + // Half of the helm default to speed tests up + gracePeriodSeconds = 15 } name := fmt.Sprintf("secure: %t, drainListeners: %t, gracePeriodSeconds: %d", testCfg.secure, drainListenersEnabled, gracePeriodSeconds) @@ -110,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) @@ -121,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 @@ -139,8 +139,8 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { require.Len(t, pods.Items, 1) clientPodName := pods.Items[0].Name - // We should terminate the pods shortly after envoy gracefully shuts down in our 5s test cases. - var terminationGracePeriod int64 = 6 + // We should terminate the pods shortly after envoy gracefully shuts down in our 15s test cases. + var terminationGracePeriod int64 = 16 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) @@ -163,14 +163,22 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { case <-gracePeriodTimer.C: break gracePeriodLoop default: - output, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), args...) - require.NoError(t, err) - require.True(t, !strings.Contains(output, "curl: (7) Failed to connect")) - - // If listener draining is disabled, ensure inbound - // requests are accepted during grace period. + retrier := &retry.Counter{Count: 3, Wait: 1 * time.Second} + retry.RunWith(retrier, t, func(r *retry.R) { + output, err := k8s.RunKubectlAndGetOutputE(r, ctx.KubectlOptions(t), args...) + if err != nil { + r.Errorf(err.Error()) + return + } + require.Condition(r, func() bool { + return !strings.Contains(output, "curl: (7) Failed to connect") + }, fmt.Sprintf("Error: %s", output)) + }) + + // If listener draining is enabled, ensure inbound + // requests are rejected during grace period. if !drainListenersEnabled { - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + connHelper.TestConnectionSuccess(t) } // TODO: check that the connection is unsuccessful when drainListenersEnabled is true // dans note: I found it isn't sufficient to use the existing TestConnectionFailureWithoutIntention @@ -181,7 +189,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 @@ -197,12 +205,12 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { // We wait an arbitrarily long time here. With the deployment rollout creating additional endpoints reconciles, // This can cause the re-queued reconcile used to come back and clean up the service registration to be re-re-queued at // 2-3X the intended grace period. - retry.RunWith(&retry.Timer{Timeout: time.Duration(30) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { + retry.RunWith(&retry.Timer{Timeout: time.Duration(60) * time.Second, Wait: 2 * time.Second}, t, func(r *retry.R) { for _, name := range []string{ "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) @@ -216,171 +224,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/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..15b7d580be 100644 --- a/acceptance/tests/consul-dns/consul_dns_test.go +++ b/acceptance/tests/consul-dns/consul_dns_test.go @@ -62,10 +62,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 +74,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/example/example_test.go b/acceptance/tests/example/example_test.go index 07b04d6097..b324ac31fe 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, 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..f92fff8a59 100644 --- a/acceptance/tests/example/main_test.go +++ b/acceptance/tests/example/main_test.go @@ -2,7 +2,6 @@ // 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-v2/apigateway.yaml b/acceptance/tests/fixtures/bases/api-gateway-v2/apigateway.yaml deleted file mode 100644 index c4fa0d6364..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway-v2/apigateway.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: mesh.consul.hashicorp.com/v2beta1 -kind: APIGateway -metadata: - name: gateway -spec: - gatewayClassName: gateway-class - listeners: - - protocol: TCP - port: 81 - name: tcp - allowedRoutes: - namespaces: - from: "All" diff --git a/acceptance/tests/fixtures/bases/api-gateway-v2/certificate.yaml b/acceptance/tests/fixtures/bases/api-gateway-v2/certificate.yaml deleted file mode 100644 index d35dc559e2..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway-v2/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-v2/gatewayclass.yaml b/acceptance/tests/fixtures/bases/api-gateway-v2/gatewayclass.yaml deleted file mode 100644 index 583ffc210a..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway-v2/gatewayclass.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: mesh.consul.hashicorp.com/v2beta1 -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-v2/gatewayclassconfig.yaml b/acceptance/tests/fixtures/bases/api-gateway-v2/gatewayclassconfig.yaml deleted file mode 100644 index 049cdd708f..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway-v2/gatewayclassconfig.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: mesh.consul.hashicorp.com/v2beta1 -kind: GatewayClassConfig -metadata: - name: gateway-class-config \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/api-gateway-v2/kustomization.yaml b/acceptance/tests/fixtures/bases/api-gateway-v2/kustomization.yaml deleted file mode 100644 index 871949a1ab..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway-v2/kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - gatewayclassconfig.yaml - - gatewayclass.yaml - - apigateway.yaml - - tcproute.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/api-gateway-v2/tcproute.yaml b/acceptance/tests/fixtures/bases/api-gateway-v2/tcproute.yaml deleted file mode 100644 index c06b0e4ee0..0000000000 --- a/acceptance/tests/fixtures/bases/api-gateway-v2/tcproute.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: mesh.consul.hashicorp.com/v2beta1 -kind: TCPRoute -metadata: - name: tcp-route -spec: - parentRefs: - - name: gateway \ No newline at end of file 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 index 78547d5118..7278557cdb 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/achooo/fakeserver:latest + image: docker.io/chaapppie/fakeserver:latest ports: - containerPort: 443 name: https 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/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..1217d857c8 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/kustomization.yaml @@ -2,15 +2,12 @@ # 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/servicedefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml index d0d1fe73bb..1c459bab5a 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml @@ -31,18 +31,6 @@ 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 @@ -53,4 +41,4 @@ spec: required: false arguments: payloadPassthrough: false - arn: arn:aws:lambda:us-east-1:111111111111:function:lambda-1234 \ No newline at end of file + arn: arn:aws:lambda:us-east-1:111111111111:function:lambda-1234 diff --git a/acceptance/tests/fixtures/bases/crds-oss/exportedservices.yaml b/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml similarity index 65% rename from acceptance/tests/fixtures/bases/crds-oss/exportedservices.yaml rename to acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml index 51d69ae709..704ea9ee19 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/exportedservices.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/serviceexports.yaml @@ -2,12 +2,12 @@ # SPDX-License-Identifier: MPL-2.0 apiVersion: consul.hashicorp.com/v1alpha1 -kind: ExportedServices +kind: ServiceExports metadata: - name: default + name: exports spec: services: - name: frontend + namespace: frontend consumers: - - peer: peerName - - samenessGroup: groupName \ No newline at end of file + - partition: other \ 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 fc236966d6..a71da92e35 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 \ No newline at end of file + service: bar 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/multiport-app/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/multiport-app/anyuid-scc-rolebinding.yaml new file mode 100644 index 0000000000..5c2e0dcfa2 --- /dev/null +++ b/acceptance/tests/fixtures/bases/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/multiport-app/kustomization.yaml b/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml index ecd2015a34..fb792d63a7 100644 --- a/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/multiport-app/kustomization.yaml @@ -7,4 +7,5 @@ resources: - secret.yaml - serviceaccount.yaml - psp-rolebinding.yaml - - privileged-scc-rolebinding.yaml + - anyuid-scc-rolebinding.yaml + - privileged-scc-rolebinding.yaml \ No newline at end of file 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-server-tcp/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml similarity index 70% rename from acceptance/tests/fixtures/bases/static-server-tcp/psp-rolebinding.yaml rename to acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml index f4f008dbea..b80bc5c562 100644 --- a/acceptance/tests/fixtures/bases/static-server-tcp/psp-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-client/anyuid-scc-rolebinding.yaml @@ -4,11 +4,11 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: static-server-tcp + name: static-client-openshift-anyuid roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: test-psp + name: system:openshift:scc:anyuid subjects: - kind: ServiceAccount - name: static-server-tcp \ No newline at end of file + name: static-client \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-client/kustomization.yaml b/acceptance/tests/fixtures/bases/static-client/kustomization.yaml index 929d64ac24..9aa0009dc4 100644 --- a/acceptance/tests/fixtures/bases/static-client/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-client/kustomization.yaml @@ -6,4 +6,5 @@ resources: - service.yaml - serviceaccount.yaml - psp-rolebinding.yaml - - privileged-scc-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/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml similarity index 70% rename from acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml rename to acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml index 623a388d20..2be7cf13db 100644 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/anyuid-scc-rolebinding.yaml @@ -4,11 +4,11 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: multiport + name: static-server-openshift-anyuid roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: test-psp + name: system:openshift:scc:anyuid subjects: - kind: ServiceAccount - name: multiport + name: static-server \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml b/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml index 6d7daa8f88..da166af201 100644 --- a/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-server-https/kustomization.yaml @@ -7,4 +7,5 @@ resources: - service.yaml - serviceaccount.yaml - psp-rolebinding.yaml - - privileged-scc-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/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 946e8d6b68..0000000000 --- a/acceptance/tests/fixtures/bases/static-server-tcp/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 - - servicedefaults.yaml - - psp-rolebinding.yaml - - privileged-scc-rolebinding.yaml 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/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/v2-multiport-app/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml similarity index 70% rename from acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml rename to acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml index f4f734813e..2be7cf13db 100644 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml +++ b/acceptance/tests/fixtures/bases/static-server/anyuid-scc-rolebinding.yaml @@ -4,11 +4,11 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: multiport-openshift-privileged + name: static-server-openshift-anyuid roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: system:openshift:scc:privileged + name: system:openshift:scc:anyuid subjects: - kind: ServiceAccount - name: multiport + name: static-server \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/static-server/kustomization.yaml b/acceptance/tests/fixtures/bases/static-server/kustomization.yaml index 929d64ac24..9aa0009dc4 100644 --- a/acceptance/tests/fixtures/bases/static-server/kustomization.yaml +++ b/acceptance/tests/fixtures/bases/static-server/kustomization.yaml @@ -6,4 +6,5 @@ resources: - service.yaml - serviceaccount.yaml - psp-rolebinding.yaml - - privileged-scc-rolebinding.yaml + - anyuid-scc-rolebinding.yaml + - privileged-scc-rolebinding.yaml \ No newline at end of file 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/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 ecd2015a34..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - deployment.yaml - - service.yaml - - secret.yaml - - serviceaccount.yaml - - psp-rolebinding.yaml - - privileged-scc-rolebinding.yaml 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..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,9 +1,8 @@ # 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 + - ../../../bases/exportedservices-default + +patchesStrategicMerge: +- 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 f3d0bca3ce..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,9 +1,8 @@ # 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 + - ../../../bases/exportedservices-default + +patchesStrategicMerge: +- 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 77c6bd3fae..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,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/exportedservices-secondary -patches: -- path: patch.yaml + - ../../../bases/exportedservices-secondary + +patchesStrategicMerge: +- 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 77c6bd3fae..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,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/exportedservices-secondary -patches: -- path: patch.yaml + - ../../../bases/exportedservices-secondary + +patchesStrategicMerge: +- 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 f3d0bca3ce..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,9 +1,8 @@ # 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 + - ../../../bases/exportedservices-default + +patchesStrategicMerge: +- 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 f3d0bca3ce..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default/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/exportedservices-default -patches: -- path: patch.yaml + - ../../../bases/exportedservices-default + +patchesStrategicMerge: +- 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 f3d0bca3ce..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,9 +1,8 @@ # 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 + - ../../../bases/exportedservices-default + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml b/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml index dd39ab3626..f9f8aad4bf 100644 --- a/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml +++ b/acceptance/tests/fixtures/cases/crds-ent/exportedservices.yaml @@ -10,6 +10,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..14f6c765d8 100644 --- a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml @@ -1,9 +1,6 @@ # 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..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,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-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml index 564d02a68f..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-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-multi-dc/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml index 564d02a68f..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,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-namespaces/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml index 564d02a68f..97a00c6466 100644 --- a/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-namespaces/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 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..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,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-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml index 0ae44380dd..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,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/ns-default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml index 0ae44380dd..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,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/ns-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml index 0ae44380dd..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,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-peers/default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml index 0ae44380dd..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,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-peers/default/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml index 0ae44380dd..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,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-peers/non-default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml index 0ae44380dd..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,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-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml index 564d02a68f..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-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-server-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml index bd2c22ff5f..bc50c78adf 100644 --- a/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-server-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-server -patches: -- path: patch.yaml + - ../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file 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..ec4878df04 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, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, 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.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 +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, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, 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.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..b5df6287b6 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.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/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 e44a2296af..0000000000 --- a/acceptance/tests/mesh_v2/mesh_inject_test.go +++ /dev/null @@ -1,154 +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", - // 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/metrics_test.go b/acceptance/tests/metrics/metrics_test.go index ec2c4c48dc..acfe465b00 100644 --- a/acceptance/tests/metrics/metrics_test.go +++ b/acceptance/tests/metrics/metrics_test.go @@ -71,15 +71,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 +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.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 +132,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 +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, "-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..89833ec2cc 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, %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..aa73c17047 100644 --- a/acceptance/tests/partitions/partitions_connect_test.go +++ b/acceptance/tests/partitions/partitions_connect_test.go @@ -30,11 +30,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 +108,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 +129,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 +146,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 +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, 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 +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, 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 +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, 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 +403,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 +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.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 +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, 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 +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, 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 +576,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..5d427a59d2 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, 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 +241,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/peering_connect_namespaces_test.go b/acceptance/tests/peering/peering_connect_namespaces_test.go index 473e4976be..622e547091 100644 --- a/acceptance/tests/peering/peering_connect_namespaces_test.go +++ b/acceptance/tests/peering/peering_connect_namespaces_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "strconv" - "sync" "testing" "time" @@ -117,77 +116,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 +198,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 +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, 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 +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, 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 +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, 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 +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, 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..5182fb199f 100644 --- a/acceptance/tests/peering/peering_connect_test.go +++ b/acceptance/tests/peering/peering_connect_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "strconv" - "sync" "testing" "time" @@ -62,7 +61,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 +78,65 @@ 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, + "terminatingGateways.enabled": "true", + "terminatingGateways.gateways[0].name": "terminating-gateway", + "terminatingGateways.gateways[0].replicas": "1", + } - 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 +163,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 +179,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") }) @@ -215,13 +197,13 @@ func TestPeering_Connect(t *testing.T) { 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() { + 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) 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) }) @@ -230,23 +212,23 @@ func TestPeering_Connect(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 { - 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 +258,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") }) @@ -331,13 +313,13 @@ func TestPeering_Connect(t *testing.T) { } 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() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, 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") + k8s.DeployKustomize(t, externalServerOpts, cfg.NoCleanupOnFailure, cfg.DebugDirectory, "../fixtures/bases/static-server") // Prevent dialing the server directly through the sidecar. terminatinggateway.CreateMeshConfigEntry(t, staticServerPeerClient, "") @@ -361,14 +343,14 @@ func TestPeering_Connect(t *testing.T) { // 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() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, 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) + k8s.CheckStaticServerConnectionFailing(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 deleted file mode 100644 index 542a215839..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: 10, 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/segments_test.go b/acceptance/tests/segments/segments_test.go index 4725fb477c..53e8d7cdcf 100644 --- a/acceptance/tests/segments/segments_test.go +++ b/acceptance/tests/segments/segments_test.go @@ -4,17 +4,11 @@ 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 @@ -70,118 +64,12 @@ func TestSegments_MeshWithAgentfulClients(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) }) } } - -// 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/snapshot_agent_k8s_secret_test.go b/acceptance/tests/snapshot-agent/snapshot_agent_k8s_secret_test.go index e5f1e785af..b5613fe76d 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(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..10cceb5952 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(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/sync_catalog_namespaces_test.go b/acceptance/tests/sync/sync_catalog_namespaces_test.go index 67123d6e4f..7634220b6b 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, 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 8bc6480d8d..f135efa29f 100644 --- a/acceptance/tests/sync/sync_catalog_test.go +++ b/acceptance/tests/sync/sync_catalog_test.go @@ -51,7 +51,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) @@ -86,8 +86,6 @@ func TestSyncCatalog(t *testing.T) { // 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) { - t.Skip("TODO(fails): NET-8594") - cfg := suite.Config() if cfg.EnableCNI { t.Skipf("skipping because -enable-cni is set and sync catalog is already tested with regular tproxy") @@ -121,19 +119,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/namespace_test.go b/acceptance/tests/tenancy_v2/namespace_test.go deleted file mode 100644 index cb19565472..0000000000 --- a/acceptance/tests/tenancy_v2/namespace_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package tenancy_v2 - -import ( - "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/acceptance/framework/resource" - "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" -) - -// TestTenancy_Namespace_Mirrored tests consul namespaces are created/deleted -// to mirror k8s namespaces in the default partition. -func TestTenancy_Namespace_Mirrored(t *testing.T) { - cfg := suite.Config() - cfg.SkipWhenCNI(t) - ctx := suite.Environment().DefaultContext(t) - - serverHelmValues := map[string]string{ - "server.enabled": "true", - "global.experiments[0]": "resource-apis", - "global.experiments[1]": "v2tenancy", - // 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) - - logger.Log(t, "creating namespace ns1 in k8s") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "create", "namespace", "ns1") - - logger.Log(t, "waiting for namespace ns1 to be created in consul") - serverResourceClient := serverCluster.ResourceClient(t, false) - rtest := resource.NewResourceTester(serverResourceClient) - rtest.WaitForResourceExists(t, &pbresource.ID{ - Name: "ns1", - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{ - Partition: "default", - }, - }) - - logger.Log(t, "deleting namespace ns1 in k8s") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "namespace", "ns1") - - logger.Log(t, "waiting for namespace ns1 to be deleted in consul") - rtest.WaitForResourceNotFound(t, &pbresource.ID{ - Name: "ns1", - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{ - Partition: "default", - }, - }) -} 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/terminating_gateway_destinations_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go index 67097b7648..a48a47372c 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go @@ -72,7 +72,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.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 @@ -90,7 +90,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.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) diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go index ee51a64c0d..e4d403dc17 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, cfg.NoCleanup, func() { + helpers.Cleanup(t, cfg.NoCleanupOnFailure, 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.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) @@ -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.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 { @@ -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, 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,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.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) @@ -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.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 { diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_test.go index acd0232227..d4d4192144 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_test.go @@ -49,7 +49,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.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) @@ -69,7 +69,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.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 { 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_test.go b/acceptance/tests/vault/vault_test.go index 9dab0a3e71..47d58b68c5 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.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..c3a3ae4034 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.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 e8ae7ba70b..d8c00b732a 100644 --- a/acceptance/tests/vault/vault_wan_fed_test.go +++ b/acceptance/tests/vault/vault_wan_fed_test.go @@ -31,7 +31,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") } @@ -329,8 +328,8 @@ func TestVault_WANFederationViaGateways(t *testing.T) { } srvCAAuthRoleConfigSecondary.ConfigureK8SAuthRole(t, vaultClient) - // Move Vault CA secret from primary to secondary so that we can mount it to pods in the - // secondary cluster. + // // Move Vault CA secret from primary to secondary so that we can mount it to pods in the + // // secondary cluster. vaultCASecretName := vault.CASecretName(vaultReleaseName) logger.Logf(t, "retrieving Vault CA secret %s from the primary cluster and applying to the secondary", vaultCASecretName) vaultCASecret, err := primaryCtx.KubernetesClient(t).CoreV1().Secrets(ns).Get(context.Background(), vaultCASecretName, metav1.GetOptions{}) @@ -492,18 +491,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 +517,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/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..5d6200721c 100644 --- a/acceptance/tests/wan-federation/wan_federation_test.go +++ b/acceptance/tests/wan-federation/wan_federation_test.go @@ -8,35 +8,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 +47,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 +77,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 +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"] = primaryDatacenter + secondaryHelmValues["global.federation.primaryDatacenter"] = "dc1" } if cfg.UseKind { @@ -154,7 +154,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 +184,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") + k8s.CheckStaticServerConnectionSuccessful(t, primaryHelper.KubectlOptsForApp(t), 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 - - // 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..ffe8a457b3 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -3,8 +3,8 @@ apiVersion: v2 name: consul -version: 1.5.0-dev -appVersion: 1.19-dev +version: 1.1.14-dev +appVersion: 1.17-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.19-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.17-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.1.14-dev - name: consul-dataplane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.5-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.3-dev - name: envoy image: envoyproxy/envoy:v1.25.11 artifacthub.io/license: MPL-2.0 diff --git a/charts/consul/README.md b/charts/consul/README.md index a0a9929ed4..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.26.x - 1.29.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/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index f830e18c26..e96dccdc78 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. */}} @@ -151,29 +167,6 @@ is passed to consul as a -config-file param on command line. [ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /consul/extra-config/extra-from-values.json {{- end -}} -{{/* -Cleanup server.extraConfig entries to avoid conflicting entries: - - server.enableAgentDebug: - - `enable_debug` should not exist in extraConfig - - metrics.disableAgentHostName: - - if global.metrics.enabled and global.metrics.enableAgentMetrics are enabled, `disable_hostname` should not exist in extraConfig - - metrics.enableHostMetrics: - - if global.metrics.enabled and global.metrics.enableAgentMetrics are enabled, `enable_host_metrics` should not exist in extraConfig - - metrics.prefixFilter - - if global.metrics.enabled and global.metrics.enableAgentMetrics are enabled, `prefix_filter` should not exist in extraConfig - - metrics.datadog.enabled: - - if global.metrics.datadog.enabled and global.metrics.datadog.dogstatsd.enabled, `dogstatsd_tags` and `dogstatsd_addr` should not exist in extraConfig - -Usage: {{ template "consul.validateExtraConfig" . }} -*/}} -{{- define "consul.validateExtraConfig" -}} -{{- if (contains "enable_debug" .Values.server.extraConfig) }}{{ fail "The enable_debug key is present in extra-from-values.json. Use server.enableAgentDebug to set this value." }}{{- end }} -{{- if (contains "disable_hostname" .Values.server.extraConfig) }}{{ fail "The disable_hostname key is present in extra-from-values.json. Use global.metrics.disableAgentHostName to set this value." }}{{- end }} -{{- if (contains "enable_host_metrics" .Values.server.extraConfig) }}{{ fail "The enable_host_metrics key is present in extra-from-values.json. Use global.metrics.enableHostMetrics to set this value." }}{{- end }} -{{- if (contains "prefix_filter" .Values.server.extraConfig) }}{{ fail "The prefix_filter key is present in extra-from-values.json. Use global.metrics.prefix_filter to set this value." }}{{- end }} -{{- if (and .Values.global.metrics.enabled .Values.global.metrics.enableAgentMetrics) }}{{- if (and .Values.global.metrics.datadog.dogstatsd.enabled) }}{{- if (contains "dogstatsd_tags" .Values.server.extraConfig) }}{{ fail "The dogstatsd_tags key is present in extra-from-values.json. Use global.metrics.datadog.dogstatsd.dogstatsdTags to set this value." }}{{- end }}{{- end }}{{- if (and .Values.global.metrics.datadog.dogstatsd.enabled) }}{{- if (contains "dogstatsd_addr" .Values.server.extraConfig) }}{{ fail "The dogstatsd_addr key is present in extra-from-values.json. Use global.metrics.datadog.dogstatsd.dogstatsd_addr to set this value." }}{{- end }}{{- end }}{{- end }} -{{- end -}} - {{/* Create chart name and version as used by the chart label. */}} @@ -189,27 +182,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" -}} @@ -386,7 +376,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 -}} @@ -417,7 +407,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 }} @@ -451,10 +441,10 @@ 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." }} +{{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." }} +{{fail "When telemetryCollector.cloud.clientSecret.secretName is set, telemetryCollector.cloud.clientId.secretName must also be set."}} {{- end }} {{- end }} @@ -493,192 +483,3 @@ Usage: {{ template "consul.validateTelemetryCollectorResourceId" . }} {{- 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 - -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 }} -{{- end }} - -{{/* -Validation for Consul Metrics configuration: - -Fail if metrics.enabled=true and metrics.disableAgentHostName=true, but metrics.enableAgentMetrics=false - - metrics.enabled = true - - metrics.enableAgentMetrics = false - - metrics.disableAgentHostName = true - -Fail if metrics.enableAgentMetrics=true and metrics.disableAgentHostName=true, but metrics.enabled=false - - metrics.enabled = false - - metrics.enableAgentMetrics = true - - metrics.disableAgentHostName = true - -Fail if metrics.enabled=true and metrics.enableHostMetrics=true, but metrics.enableAgentMetrics=false - - metrics.enabled = true - - metrics.enableAgentMetrics = false - - metrics.enableHostMetrics = true - -Fail if metrics.enableAgentMetrics=true and metrics.enableHostMetrics=true, but metrics.enabled=false - - metrics.enabled = false - - metrics.enableAgentMetrics = true - - metrics.enableHostMetrics = true - -Usage: {{ template "consul.validateMetricsConfig" . }} - -*/}} - -{{- define "consul.validateMetricsConfig" -}} -{{- if and (not .Values.global.metrics.enableAgentMetrics) (and .Values.global.metrics.disableAgentHostName .Values.global.metrics.enabled )}} -{{fail "When enabling metrics (global.metrics.enabled) and disabling hostname emission from metrics (global.metrics.disableAgentHostName), global.metrics.enableAgentMetrics must be set to true"}} -{{- end }} -{{- if and (not .Values.global.metrics) (and .Values.global.metrics.disableAgentHostName .Values.global.metrics.enableAgentMetrics )}} -{{fail "When enabling Consul agent metrics (global.metrics.enableAgentMetrics) and disabling hostname emission from metrics (global.metrics.disableAgentHostName), global metrics enablement (global.metrics.enabled) must be set to true"}} -{{- end }} -{{- if and (not .Values.global.metrics.enableAgentMetrics) (and .Values.global.metrics.disableAgentHostName .Values.global.metrics.enabled )}} -{{fail "When disabling hostname emission from metrics (global.metrics.disableAgentHostName) and enabling global metrics (global.metrics.enabled), Consul agent metrics must be enabled(global.metrics.enableAgentMetrics=true)"}} -{{- end }} -{{- if and (not .Values.global.metrics.enabled) (and .Values.global.metrics.disableAgentHostName .Values.global.metrics.enableAgentMetrics)}} -{{fail "When enabling Consul agent metrics (global.metrics.enableAgentMetrics) and disabling hostname metrics emission (global.metrics.disableAgentHostName), global metrics must be enabled (global.metrics.enabled)."}} -{{- end }} -{{- end -}} - -{{/* -Validation for Consul Datadog Integration deployment: - -Fail if Datadog integration enabled and Consul server agent telemetry is not enabled. - - global.metrics.datadog.enabled=true - - global.metrics.enableAgentMetrics=false || global.metrics.enabled=false - -Fail if Consul OpenMetrics (Prometheus) and DogStatsD metrics are both enabled and configured. - - global.metrics.datadog.dogstatsd.enabled (scrapes `/v1/agent/metrics?format=prometheus` via the `use_prometheus_endpoint` option) - - global.metrics.datadog.openMetricsPrometheus.enabled (scrapes `/v1/agent/metrics?format=prometheus`) - - see https://docs.datadoghq.com/integrations/consul/?tab=host#host for recommendation to not have both - -Fail if Datadog OTLP forwarding is enabled and Consul Telemetry Collection is not enabled. - - global.metrics.datadog.otlp.enabled=true - - telemetryCollector.enabled=false - -Fail if Consul Open Telemetry collector forwarding protocol is not one of either "http" or "grpc" - - global.metrics.datadog.otlp.protocol!="http" || global.metrics.datadog.otlp.protocol!="grpc" - -Usage: {{ template "consul.validateDatadogConfiguration" . }} - -*/}} - -{{- define "consul.validateDatadogConfiguration" -}} -{{- if and .Values.global.metrics.datadog.enabled (or (not .Values.global.metrics.enableAgentMetrics) (not .Values.global.metrics.enabled) )}} -{{fail "When enabling datadog metrics collection, the /v1/agent/metrics is required to be accessible, therefore global.metrics.enableAgentMetrics and global.metrics.enabled must be also be enabled."}} -{{- end }} -{{- if and .Values.global.metrics.datadog.dogstatsd.enabled .Values.global.metrics.datadog.openMetricsPrometheus.enabled }} -{{fail "You must have one of DogStatsD (global.metrics.datadog.dogstatsd.enabled) or OpenMetrics (global.metrics.datadog.openMetricsPrometheus.enabled) enabled, not both as this is an unsupported configuration." }} -{{- end }} -{{- if and .Values.global.metrics.datadog.otlp.enabled (not .Values.telemetryCollector.enabled) }} -{{fail "Cannot enable Datadog OTLP metrics collection (global.metrics.datadog.otlp.enabled) without consul-telemetry-collector. Ensure Consul OTLP collection is enabled (telemetryCollector.enabled) and configured." }} -{{- end }} -{{- if and (ne ( lower .Values.global.metrics.datadog.otlp.protocol) "http") (ne ( lower .Values.global.metrics.datadog.otlp.protocol) "grpc") }} -{{fail "Valid values for global.metrics.datadog.otlp.protocol must be one of either \"http\" or \"grpc\"." }} -{{- end }} -{{- end -}} - -{{/* -Sets the dogstatsd_addr field of the agent configuration dependent on the -socket transport type being used: - - "UDS" (Unix Domain Socket): prefixes "unix://" to URL and appends path to socket (i.e., unix:///var/run/datadog/dsd.socket) - - "UDP" (User Datagram Protocol): adds no prefix and appends dogstatsd port number to hostname/IP (i.e., 172.20.180.10:8125) -- global.metrics.enableDatadogIntegration.dogstatsd configuration - -Usage: {{ template "consul.dogstatsdAaddressInfo" . }} -*/}} - -{{- define "consul.dogstatsdAaddressInfo" -}} -{{- if (and .Values.global.metrics.datadog.enabled .Values.global.metrics.datadog.dogstatsd.enabled) }} - "dogstatsd_addr": "{{- if eq .Values.global.metrics.datadog.dogstatsd.socketTransportType "UDS" }}unix://{{ .Values.global.metrics.datadog.dogstatsd.dogstatsdAddr }}{{- else }}{{ .Values.global.metrics.datadog.dogstatsd.dogstatsdAddr | trimAll "\"" }}{{- if ne ( .Values.global.metrics.datadog.dogstatsd.dogstatsdPort | int ) 0 }}:{{ .Values.global.metrics.datadog.dogstatsd.dogstatsdPort | toString }}{{- end }}{{- end }}",{{- end }} -{{- end -}} - -{{/* -Configures the metrics prefixing that's required to either allow or dissallow certaing RPC or gRPC server calls: - -Usage: {{ template "consul.prefixFilter" . }} -*/}} -{{- define "consul.prefixFilter" -}} -{{- $allowList := .Values.global.metrics.prefixFilter.allowList }} -{{- $blockList := .Values.global.metrics.prefixFilter.blockList }} -{{- if and (not (empty $allowList)) (not (empty $blockList)) }} - "prefix_filter": [{{- range $index, $value := concat $allowList $blockList -}} - "{{- if (has $value $allowList) }}{{ printf "+%s" ($value | trimAll "\"") }}{{- else }}{{ printf "-%s" ($value | trimAll "\"") }}{{- end }}"{{- if lt $index (sub (len (concat $allowList $blockList)) 1) -}},{{- end -}} - {{- end -}}], -{{- else if not (empty $allowList) }} - "prefix_filter": [{{- range $index, $value := $allowList -}} - "{{ printf "+%s" ($value | trimAll "\"") }}"{{- if lt $index (sub (len $allowList) 1) -}},{{- end -}} - {{- end -}}], -{{- else if not (empty $blockList) }} - "prefix_filter": [{{- range $index, $value := $blockList -}} - "{{ printf "-%s" ($value | trimAll "\"") }}"{{- if lt $index (sub (len $blockList) 1) -}},{{- end -}} - {{- end -}}], -{{- end }} -{{- end -}} - -{{/* -Retrieves the global consul/consul-enterprise version string for use with labels or tags. -Requirements for valid labels: - - a valid label must be an empty string or consist of - => alphanumeric characters - => '-', '_' or '.' - => must start and end with an alphanumeric character - (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is - '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?') - -Usage: {{ template "consul.versionInfo" }} -*/}} -{{- define "consul.versionInfo" -}} -{{- $imageVersion := regexSplit ":" .Values.global.image -1 }} -{{- $versionInfo := printf "%s" (index $imageVersion 1 ) | trimSuffix "\"" }} -{{- $sanitizedVersion := "" }} -{{- $pattern := "^([A-Za-z0-9][-A-Za-z0-9_.]*[A-Za-z0-9])?$" }} -{{- if not (regexMatch $pattern $versionInfo) -}} - {{- $sanitizedVersion = regexReplaceAll "[^A-Za-z0-9-_.]|sha256" $versionInfo "" }} - {{- $sanitizedVersion = printf "%s" (trimSuffix "-" (trimPrefix "-" $sanitizedVersion)) -}} -{{- else }} - {{- $sanitizedVersion = $versionInfo }} -{{- end -}} -{{- printf "%s" $sanitizedVersion | quote }} -{{- end -}} \ No newline at end of file diff --git a/charts/consul/templates/api-gateway-controller-clusterrole.yaml b/charts/consul/templates/api-gateway-controller-clusterrole.yaml new file mode 100644 index 0000000000..eac2bd1f69 --- /dev/null +++ b/charts/consul/templates/api-gateway-controller-clusterrole.yaml @@ -0,0 +1,265 @@ +{{- if .Values.apiGateway.enabled }} +# The ClusterRole to enable the API Gateway controller to access required api endpoints. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "consul.fullname" . }}-api-gateway-controller + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: api-gateway-controller +rules: +- apiGroups: + - api-gateway.consul.hashicorp.com + resources: + - gatewayclassconfigs + verbs: + - get + - list + - update + - watch +- apiGroups: + - api-gateway.consul.hashicorp.com + resources: + - gatewayclassconfigs/finalizers + verbs: + - update +- apiGroups: + - api-gateway.consul.hashicorp.com + resources: + - meshservices + verbs: + - get + - list + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - list + - update +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - configmaps/status + verbs: + - get + - patch + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - list + - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - create + - get + - list + - update + - watch +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - create + - get + - list + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - referencegrants + verbs: + - get + - list + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - referencepolicies + verbs: + - get + - list + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses/finalizers + verbs: + - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses/status + verbs: + - get + - patch + - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - gateways + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - gateways/finalizers + verbs: + - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - gateways/status + verbs: + - get + - patch + - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - httproutes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - httproutes/finalizers + verbs: + - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - httproutes/status + verbs: + - get + - patch + - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - tcproutes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - tcproutes/finalizers + verbs: + - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - tcproutes/status + verbs: + - get + - patch + - update +{{- if .Values.global.enablePodSecurityPolicies }} +- apiGroups: + - policy + resources: + - podsecuritypolicies + verbs: + - use +- apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + - rolebindings + verbs: + - create + - get + - list + - watch +{{- end }} +{{- end }} diff --git a/charts/consul/templates/server-clusterrolebinding.yaml b/charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml similarity index 57% rename from charts/consul/templates/server-clusterrolebinding.yaml rename to charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml index 854fda870e..d083a08129 100644 --- a/charts/consul/templates/server-clusterrolebinding.yaml +++ b/charts/consul/templates/api-gateway-controller-clusterrolebinding.yaml @@ -1,18 +1,20 @@ +{{- if .Values.apiGateway.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: {{ template "consul.fullname" . }}-server + name: {{ template "consul.fullname" . }}-api-gateway-controller labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} - component: server + component: api-gateway-controller roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: {{ template "consul.fullname" . }}-server + name: {{ template "consul.fullname" . }}-api-gateway-controller subjects: - kind: ServiceAccount - name: {{ template "consul.fullname" . }}-server + name: {{ template "consul.fullname" . }}-api-gateway-controller namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml new file mode 100644 index 0000000000..e1fc004b95 --- /dev/null +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -0,0 +1,305 @@ +{{- 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 }} +{{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} +{{ template "consul.validateRequiredCloudSecretsExist" . }} +{{ template "consul.validateCloudSecretKeys" . }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "consul.fullname" . }}-api-gateway-controller + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + 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: + matchLabels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: api-gateway-controller + template: + metadata: + annotations: + consul.hashicorp.com/connect-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" + "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 .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 }}" + "vault.hashicorp.com/ca-cert": "/vault/custom/{{ .Values.global.secretsBackend.vault.ca.secretKey }}" + {{- end }} + {{- end }} + labels: + app: {{ template "consul.name" . }} + 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: + - name: api-gateway-controller + image: {{ .Values.apiGateway.image }} + ports: + - containerPort: 9090 + name: sds + protocol: TCP + env: + {{- 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 + valueFrom: + fieldRef: + fieldPath: status.hostIP + {{- 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 }} + - name: CONSUL_HTTP_ADDR + {{- if .Values.client.enabled }} + {{/* + We use client agent nodes if we have them to support backwards compatibility for Consul API Gateway + v0.4 and older, which requires connectivity between the registered Consul agent node and a + deployment for health checking (originating from the Consul node). Always leveraging the agents in + the case that they're explicitly opted into allows us to support users with agent node + + "externalServers" configuration upgrading a Helm chart without upgrading API gateways. + */}} + {{- if .Values.global.tls.enabled }} + value: $(HOST_IP):8501 + {{- else }} + value: $(HOST_IP):8500 + {{- end }} + {{- else if .Values.externalServers.enabled }} + {{/* + "externalServers" specified and running in "agentless" mode, this will only work with + Consul API Gateway v0.5 or newer + */}} + value: {{ first .Values.externalServers.hosts }}:{{ .Values.externalServers.httpsPort }} + {{- else }} + {{/* + We have local network connectivity between deployments and the internal cluster, this + should be supported in all versions of Consul API Gateway + */}} + {{- if .Values.global.tls.enabled }} + value: {{ template "consul.fullname" . }}-server:8501 + {{- else }} + value: {{ template "consul.fullname" . }}-server:8500 + {{- end }} + {{- end }} + - name: CONSUL_HTTP_SSL + value: "{{ .Values.global.tls.enabled }}" + {{- if and (not .Values.client.enabled) .Values.externalServers.enabled .Values.externalServers.tlsServerName }} + - name: CONSUL_TLS_SERVER_NAME + value: {{ .Values.externalServers.tlsServerName }} + {{- end }} + {{- if .Values.global.adminPartitions.enabled }} + - name: CONSUL_PARTITION + value: {{ .Values.global.adminPartitions.name }} + {{- if .Values.global.acls.manageSystemACLs }} + - name: CONSUL_LOGIN_PARTITION + value: {{ .Values.global.adminPartitions.name }} + {{- end }} + {{- end }} + {{- if not .Values.client.enabled }} + - name: CONSUL_DYNAMIC_SERVER_DISCOVERY + value: "true" + {{- end }} + command: + - "/bin/sh" + - "-ec" + - | + exec consul-api-gateway server \ + -sds-server-host {{ template "consul.fullname" . }}-api-gateway-controller.{{ .Release.Namespace }}.svc \ + -k8s-namespace {{ .Release.Namespace }} \ + {{- if .Values.global.enableConsulNamespaces }} + {{- if .Values.connectInject.consulNamespaces.consulDestinationNamespace }} + -consul-destination-namespace={{ .Values.connectInject.consulNamespaces.consulDestinationNamespace }} \ + {{- end }} + {{- if .Values.connectInject.consulNamespaces.mirroringK8S }} + -mirroring-k8s=true \ + {{- if .Values.connectInject.consulNamespaces.mirroringK8SPrefix }} + -mirroring-k8s-prefix={{ .Values.connectInject.consulNamespaces.mirroringK8SPrefix }} \ + {{- end }} + {{- end }} + {{- end }} + {{- if and .Values.global.federation.enabled .Values.global.federation.primaryDatacenter }} + -primary-datacenter={{ .Values.global.federation.primaryDatacenter }} \ + {{- end }} + -log-level {{ default .Values.global.logLevel .Values.apiGateway.logLevel }} \ + -log-json={{ .Values.global.logJSON }} + volumeMounts: + {{- if .Values.global.acls.manageSystemACLs }} + - name: consul-bin + mountPath: /consul-bin + {{- end }} + {{- 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 + {{- else }} + - name: consul-ca-cert + {{- end }} + mountPath: /consul/tls/ca + readOnly: true + {{- end }} + {{- end }} + - mountPath: /consul/login + name: consul-data + readOnly: true + {{- if .Values.apiGateway.resources }} + resources: + {{- toYaml .Values.apiGateway.resources | nindent 12 }} + {{- end }} + {{- if .Values.global.acls.manageSystemACLs }} + lifecycle: + preStop: + exec: + command: ["/consul-bin/consul", "logout" ] + {{- end }} + volumes: + {{- if .Values.global.acls.manageSystemACLs }} + - name: consul-bin + emptyDir: { } + {{- 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 }} + {{- if .Values.global.tls.enableAutoEncrypt }} + - name: consul-auto-encrypt-ca-cert + emptyDir: + medium: "Memory" + {{- end }} + {{- end }} + - name: consul-data + emptyDir: + medium: "Memory" + {{- if or .Values.global.acls.manageSystemACLs (and .Values.global.tls.enabled .Values.global.tls.enableAutoEncrypt) }} + initContainers: + {{- if .Values.global.acls.manageSystemACLs }} + - name: copy-consul-bin + image: {{ .Values.global.image | quote }} + command: + - cp + - /bin/consul + - /consul-bin/consul + volumeMounts: + - name: consul-bin + mountPath: /consul-bin + {{- if .Values.apiGateway.initCopyConsulContainer }} + {{- if .Values.apiGateway.initCopyConsulContainer.resources }} + resources: {{ toYaml .Values.apiGateway.initCopyConsulContainer.resources | nindent 12 }} + {{- end }} + {{- end }} + {{- end }} + {{- if (and .Values.global.tls.enabled .Values.global.tls.enableAutoEncrypt) }} + {{- include "consul.getAutoEncryptClientCA" . | nindent 6 }} + {{- end }} + {{- if .Values.global.acls.manageSystemACLs }} + - name: api-gateway-controller-acl-init + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + 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: + - mountPath: /consul/login + name: consul-data + readOnly: false + {{- 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 + readOnly: true + {{- end }} + {{- end }} + command: + - "/bin/sh" + - "-ec" + - | + exec 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 }} \ + {{- else }} + -auth-method-name={{ template "consul.fullname" . }}-k8s-component-auth-method \ + {{- end }} + -log-level={{ default .Values.global.logLevel .Values.apiGateway.logLevel }} \ + -log-json={{ .Values.global.logJSON }} + resources: + requests: + memory: "25Mi" + cpu: "50m" + limits: + memory: "25Mi" + cpu: "50m" + {{- end }} + {{- end }} + {{- if .Values.apiGateway.controller.priorityClassName }} + priorityClassName: {{ .Values.apiGateway.controller.priorityClassName | quote }} + {{- end }} + {{- if .Values.apiGateway.controller.nodeSelector }} + nodeSelector: + {{ tpl .Values.apiGateway.controller.nodeSelector . | indent 8 | trim }} + {{- end }} + {{- if .Values.apiGateway.controller.tolerations }} + tolerations: + {{ tpl .Values.apiGateway.controller.tolerations . | indent 8 | trim }} + {{- end }} +{{- end }} diff --git a/charts/consul/templates/gateway-resources-podsecuritypolicy.yaml b/charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml similarity index 63% rename from charts/consul/templates/gateway-resources-podsecuritypolicy.yaml rename to charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml index da5299194c..390d084303 100644 --- a/charts/consul/templates/gateway-resources-podsecuritypolicy.yaml +++ b/charts/consul/templates/api-gateway-controller-podsecuritypolicy.yaml @@ -1,22 +1,30 @@ -{{- if (and .Values.global.enablePodSecurityPolicies .Values.connectInject.enabled)}} +{{- if and .Values.apiGateway.enabled .Values.global.enablePodSecurityPolicies }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: - name: {{ template "consul.fullname" . }}-gateway-resources + name: {{ template "consul.fullname" . }}-api-gateway-controller namespace: {{ .Release.Namespace }} labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} - component: gateway-resources + component: api-gateway-controller 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 @@ -28,5 +36,5 @@ spec: rule: 'RunAsAny' fsGroup: rule: 'RunAsAny' - readOnlyRootFilesystem: false + readOnlyRootFilesystem: true {{- end }} diff --git a/charts/consul/templates/api-gateway-controller-service.yaml b/charts/consul/templates/api-gateway-controller-service.yaml new file mode 100644 index 0000000000..aa79ff9fc3 --- /dev/null +++ b/charts/consul/templates/api-gateway-controller-service.yaml @@ -0,0 +1,27 @@ +{{- if .Values.apiGateway.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "consul.fullname" . }}-api-gateway-controller + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: api-gateway-controller + annotations: + {{- if .Values.apiGateway.controller.service.annotations }} + {{ tpl .Values.apiGateway.controller.service.annotations . | nindent 4 | trim }} + {{- end }} +spec: + ports: + - name: sds + port: 9090 + protocol: TCP + targetPort: 9090 + selector: + app: {{ template "consul.name" . }} + release: "{{ .Release.Name }}" + component: api-gateway-controller +{{- end }} diff --git a/charts/consul/templates/api-gateway-controller-serviceaccount.yaml b/charts/consul/templates/api-gateway-controller-serviceaccount.yaml new file mode 100644 index 0000000000..98292a8dbe --- /dev/null +++ b/charts/consul/templates/api-gateway-controller-serviceaccount.yaml @@ -0,0 +1,23 @@ +{{- if .Values.apiGateway.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "consul.fullname" . }}-api-gateway-controller + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: api-gateway-controller + {{- if .Values.apiGateway.serviceAccount.annotations }} + annotations: + {{ tpl .Values.apiGateway.serviceAccount.annotations . | nindent 4 | trim }} + {{- end }} +{{- with .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range . }} + - name: {{ .name }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/consul/templates/api-gateway-gatewayclass.yaml b/charts/consul/templates/api-gateway-gatewayclass.yaml new file mode 100644 index 0000000000..d9ba85e633 --- /dev/null +++ b/charts/consul/templates/api-gateway-gatewayclass.yaml @@ -0,0 +1,18 @@ +{{- if (and .Values.apiGateway.enabled .Values.apiGateway.managedGatewayClass.enabled) }} +apiVersion: gateway.networking.k8s.io/v1alpha2 +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: api-gateway.consul.hashicorp.com + kind: GatewayClassConfig + name: consul-api-gateway +{{- end }} diff --git a/charts/consul/templates/api-gateway-gatewayclassconfig.yaml b/charts/consul/templates/api-gateway-gatewayclassconfig.yaml new file mode 100644 index 0000000000..ba0e6c63db --- /dev/null +++ b/charts/consul/templates/api-gateway-gatewayclassconfig.yaml @@ -0,0 +1,84 @@ +{{- if (and .Values.apiGateway.enabled .Values.apiGateway.managedGatewayClass.enabled) }} +apiVersion: api-gateway.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: + consul: + {{- if .Values.client.enabled }} + {{/* + We use client agent nodes if we have them to support backwards compatibility in <=0.4 releases which + require connectivity between the registered Consul agent node and a deployment for health checking + (originating from the Consul node). Always leveraging the agents in the case that they're explicitly + opted into allows us to support users with agent node + "externalServers" configuration upgrading a + helm chart without upgrading api gateways. Otherwise, using "externalServers" when provided + without local agents will break gateways <=0.4. + */}} + address: $(HOST_IP) + {{- else if .Values.externalServers.enabled }} + {{/* + "externalServers" specified and running in "agentless" mode, this will only work 0.5+ + */}} + address: {{ first .Values.externalServers.hosts }} + {{- else }} + {{/* + We have local network connectivity between deployments and the internal cluster, this + should be supported in all versions of api-gateway + */}} + address: {{ template "consul.fullname" . }}-server.{{ .Release.Namespace }}.svc + {{- end }} + authentication: + {{- if .Values.global.acls.manageSystemACLs }} + managed: true + method: {{ template "consul.fullname" . }}-k8s-auth-method + {{- if .Values.global.enablePodSecurityPolicies }} + podSecurityPolicy: {{ template "consul.fullname" . }}-api-gateway + {{- end }} + {{- end }} + {{- if .Values.global.tls.enabled }} + scheme: https + {{- else }} + scheme: http + {{- end }} + ports: + {{- if .Values.externalServers.enabled }} + grpc: {{ .Values.externalServers.grpcPort }} + http: {{ .Values.externalServers.httpsPort }} + {{- else }} + grpc: 8502 + {{- if .Values.global.tls.enabled }} + http: 8501 + {{- else }} + http: 8500 + {{- end }} + {{- end }} + {{- with .Values.apiGateway.managedGatewayClass.deployment }} + deployment: + {{- toYaml . | nindent 4 }} + {{- end }} + image: + consulAPIGateway: {{ .Values.apiGateway.image }} + envoy: {{ .Values.apiGateway.imageEnvoy }} + {{- 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 }} + useHostPorts: {{ .Values.apiGateway.managedGatewayClass.useHostPorts }} + logLevel: {{ default .Values.global.logLevel .Values.apiGateway.managedGatewayClass.logLevel }} +{{- end }} diff --git a/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml b/charts/consul/templates/api-gateway-podsecuritypolicy.yaml similarity index 59% rename from charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml rename to charts/consul/templates/api-gateway-podsecuritypolicy.yaml index ffbad130cc..48f826f995 100644 --- a/charts/consul/templates/gateway-cleanup-podsecuritypolicy.yaml +++ b/charts/consul/templates/api-gateway-podsecuritypolicy.yaml @@ -1,25 +1,38 @@ -{{- if (and .Values.connectInject.enabled .Values.global.enablePodSecurityPolicies)}} +{{- if and .Values.apiGateway.enabled .Values.global.enablePodSecurityPolicies }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: - name: {{ template "consul.fullname" . }}-gateway-cleanup + name: {{ template "consul.fullname" . }}-api-gateway namespace: {{ .Release.Namespace }} labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} - component: gateway-cleanup + component: api-gateway-controller 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' + allowedCapabilities: + - NET_BIND_SERVICE hostNetwork: false hostIPC: false hostPID: false + hostPorts: + - max: 65535 + min: 1025 runAsUser: rule: 'RunAsAny' seLinux: @@ -28,5 +41,5 @@ spec: rule: 'RunAsAny' fsGroup: rule: 'RunAsAny' - readOnlyRootFilesystem: false + readOnlyRootFilesystem: true {{- end }} diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index cf0cb1d686..0f43c3e585 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -86,16 +86,13 @@ 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 }} {{- if .Values.client.annotations }} {{- tpl .Values.client.annotations . | nindent 8 }} {{- end }} {{- if (and .Values.global.metrics.enabled .Values.global.metrics.enableAgentMetrics) }} "prometheus.io/scrape": "true" - {{- if not (hasKey (default "" .Values.client.annotations | fromYaml) "prometheus.io/path")}} "prometheus.io/path": "/v1/agent/metrics" - {{- end }} "prometheus.io/port": "8500" {{- end }} spec: diff --git a/charts/consul/templates/client-securitycontextconstraints.yaml b/charts/consul/templates/client-securitycontextconstraints.yaml index 07e7711384..c14dd1c991 100644 --- a/charts/consul/templates/client-securitycontextconstraints.yaml +++ b/charts/consul/templates/client-securitycontextconstraints.yaml @@ -13,6 +13,7 @@ metadata: annotations: kubernetes.io/description: {{ template "consul.fullname" . }}-client are the security context constraints required to run the consul client. +# Iff. allowHostDirVolumePlugin is true, hostPath must be included in volumes (see below). {{- if .Values.client.dataDirectoryHostPath }} allowHostDirVolumePlugin: true {{- else }} @@ -44,13 +45,17 @@ supplementalGroups: type: MustRunAs users: [] volumes: +# This list must be in alphabetical order to match the post-reconcile order enforced by OpenShift admission hooks. +# Furthermore, hostPath must be included explicitly if allowHostDirVolumePlugin is true, as it will otherwise be +# added by OpenShift. It must be excluded if allowHostDirVolumePlugin is false per OpenShift requirements. +# This avoids false positives in change detection by third-party diff tools (e.g. ArgoCD) that respect list order. - configMap - downwardAPI - emptyDir -- persistentVolumeClaim -- projected -- secret {{- if .Values.client.dataDirectoryHostPath }} - hostPath {{- end }} +- persistentVolumeClaim +- projected +- secret {{- 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/cni-securitycontextconstraints.yaml b/charts/consul/templates/cni-securitycontextconstraints.yaml index 2c09dba9b8..cb60104cf0 100644 --- a/charts/consul/templates/cni-securitycontextconstraints.yaml +++ b/charts/consul/templates/cni-securitycontextconstraints.yaml @@ -13,6 +13,7 @@ metadata: annotations: kubernetes.io/description: {{ template "consul.fullname" . }}-cni are the security context constraints required to run consul-cni. +# Iff. allowHostDirVolumePlugin is true, hostPath must be included in volumes (see below). allowHostDirVolumePlugin: true allowHostIPC: false allowHostNetwork: false @@ -40,11 +41,15 @@ supplementalGroups: type: MustRunAs users: [] volumes: +# This list must be in alphabetical order to match the post-reconcile order enforced by OpenShift admission hooks. +# Furthermore, hostPath must be included explicitly if allowHostDirVolumePlugin is true, as it will otherwise be +# added by OpenShift. It must be excluded if allowHostDirVolumePlugin is false per OpenShift requirements. +# This avoids false positives in change detection by third-party diff tools (e.g. ArgoCD) that respect list order. - configMap - downwardAPI - emptyDir +- hostPath - persistentVolumeClaim - projected - secret -- hostPath {{- end }} 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 fe07c2581a..a468b6e663 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -14,7 +14,6 @@ {{- $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 +52,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 +152,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 }} @@ -260,10 +252,8 @@ spec: -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-startup-grace-period-seconds={{ .Values.connectInject.sidecarProxy.lifecycle.defaultStartupGracePeriodSeconds }} \ -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-lifecycle-graceful-startup-path="{{ .Values.connectInject.sidecarProxy.lifecycle.defaultGracefulStartupPath }}" \ -default-sidecar-proxy-startup-failure-seconds={{ .Values.connectInject.sidecarProxy.defaultStartupFailureSeconds }} \ -default-sidecar-proxy-liveness-failure-seconds={{ .Values.connectInject.sidecarProxy.defaultLivenessFailureSeconds }} \ {{- if .Values.connectInject.initContainer }} 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 a01d40c027..0000000000 --- a/charts/consul/templates/crd-apigateways.yaml +++ /dev/null @@ -1,302 +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 - 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: - addresses: - items: - properties: - type: - default: IPAddress - type: string - value: - type: string - required: - - type - - value - type: object - type: array - listeners: - items: - properties: - attachedRoutes: - format: int32 - type: integer - name: - type: string - 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 - required: - - attachedRoutes - - name - type: object - type: array - 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 - 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 6613e3da7e..01bb50c748 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -1,22 +1,25 @@ {{- 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.14.0 + 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,33 +36,65 @@ spec: jsonPath: .metadata.creationTimestamp name: Age type: date - name: v2 + 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 - 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' + 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' + 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: - 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: @@ -68,8 +103,9 @@ spec: 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' + 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 diff --git a/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml b/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml deleted file mode 100644 index 41023c19dc..0000000000 --- a/charts/consul/templates/crd-gatewayclassconfigs-v1.yaml +++ /dev/null @@ -1,218 +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 - metrics: - description: Metrics defines how to configure the metrics for a gateway. - properties: - enabled: - description: Enable metrics for this class of gateways. If unspecified, - will inherit behavior from the global Helm configuration. - type: boolean - path: - description: The path used for metrics. - type: string - port: - description: The port used for metrics. - format: int32 - maximum: 65535 - minimum: 1024 - 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 - 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 70763f9104..0000000000 --- a/charts/consul/templates/crd-gatewayclasses.yaml +++ /dev/null @@ -1,122 +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 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: - 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 - required: - - name - type: object - required: - - controllerName - - parametersRef - 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 8766c8edbe..0000000000 --- a/charts/consul/templates/crd-grpcroutes.yaml +++ /dev/null @@ -1,606 +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. \n For more details on potential values of this - field, see documentation for Service.ServicePort." - 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 - 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. - For north/south this is TBD. \n For more details - on potential values of this field, see documentation - for Service.ServicePort." - 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 - 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 c829bf1fc3..0000000000 --- a/charts/consul/templates/crd-httproutes.yaml +++ /dev/null @@ -1,662 +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. \n For more details on potential values of this - field, see documentation for Service.ServicePort." - 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 - 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. - For north/south this is TBD. \n For more details - on potential values of this field, see documentation - for Service.ServicePort." - 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 - 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..4704086426 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -41,14 +42,19 @@ spec: description: IngressGateway is the Schema for the ingressgateways 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' + 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' + 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 @@ -59,64 +65,68 @@ spec: description: Defaults is default configuration for all upstream services properties: maxConcurrentRequests: - description: The maximum number of concurrent requests that will - be allowed at a single point in time. Use this to limit HTTP/2 - traffic, since HTTP/2 has many requests per connection. + description: |- + The maximum number of concurrent requests that + will be allowed at a single point in time. Use this to limit HTTP/2 traffic, + since HTTP/2 has many requests per connection. format: int32 type: integer maxConnections: - description: The maximum number of connections a service instance - will be allowed to establish against the given upstream. Use - this to limit HTTP/1.1 traffic, since HTTP/1.1 has a request - per connection. + description: |- + The maximum number of connections a service instance + will be allowed to establish against the given upstream. Use this to limit + HTTP/1.1 traffic, since HTTP/1.1 has a request per connection. format: int32 type: integer maxPendingRequests: - description: The maximum number of requests that will be queued + description: |- + The maximum number of requests that will be queued 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. + 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. + 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. + 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. + 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. + 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. + 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 - listen on, and what services to associated to those ports. + description: |- + Listeners declares what ports the ingress gateway should listen on, and + what services to associated to those ports. items: description: IngressListener manages the configuration for a listener on a specific port. @@ -126,110 +136,119 @@ spec: should listen for traffic. type: integer protocol: - description: 'Protocol declares what type of traffic this listener - is expected to receive. Depending on the protocol, a listener - might support multiplexing services over a single port, or - additional discovery chain features. The current supported - values are: (tcp | http | http2 | grpc).' + description: |- + Protocol declares what type of traffic this listener is expected to + receive. Depending on the protocol, a listener might support multiplexing + services over a single port, or additional discovery chain features. The + current supported values are: (tcp | http | http2 | grpc). type: string services: - description: Services declares the set of services to which - the listener forwards traffic. For "tcp" protocol listeners, - only a single service is allowed. For "http" listeners, multiple - services can be declared. + description: |- + Services declares the set of services to which the listener forwards + traffic. + For "tcp" protocol listeners, only a single service is allowed. + For "http" listeners, multiple services can be declared. items: - description: IngressService manages configuration for services - that are exposed to ingress traffic. + description: |- + IngressService manages configuration for services that are exposed to + ingress traffic. properties: hosts: - description: "Hosts is a list of hostnames which should - be associated to this service on the defined listener. - Only allowed on layer 7 protocols, this will be used - to route traffic to the service by matching the Host - header of the HTTP request. \n If a host is provided - for a service that also has a wildcard specifier defined, - the host will override the wildcard-specifier-provided - \".*\" domain for that listener. \n This - cannot be specified when using the wildcard specifier, - \"*\", or when using a \"tcp\" listener." + description: |- + Hosts is a list of hostnames which should be associated to this service on + the defined listener. Only allowed on layer 7 protocols, this will be used + to route traffic to the service by matching the Host header of the HTTP + request. + + + If a host is provided for a service that also has a wildcard specifier + defined, the host will override the wildcard-specifier-provided + ".*" domain for that listener. + + + This cannot be specified when using the wildcard specifier, "*", or when + using a "tcp" listener. items: type: string type: array maxConcurrentRequests: - description: The maximum number of concurrent requests - that will be allowed at a single point in time. Use - this to limit HTTP/2 traffic, since HTTP/2 has many - requests per connection. + description: |- + The maximum number of concurrent requests that + will be allowed at a single point in time. Use this to limit HTTP/2 traffic, + since HTTP/2 has many requests per connection. format: int32 type: integer maxConnections: - description: The maximum number of connections a service - instance will be allowed to establish against the given - upstream. Use this to limit HTTP/1.1 traffic, since - HTTP/1.1 has a request per connection. + description: |- + The maximum number of connections a service instance + will be allowed to establish against the given upstream. Use this to limit + HTTP/1.1 traffic, since HTTP/1.1 has a request per connection. format: int32 type: integer maxPendingRequests: - description: The maximum number of requests that will - be queued while waiting for a connection to be established. + description: |- + The maximum number of requests that will be queued + while waiting for a connection to be established. format: int32 type: integer name: - description: "Name declares the service to which traffic - should be forwarded. \n This can either be a specific - service, or the wildcard specifier, \"*\". If the wildcard - specifier is provided, the listener must be of \"http\" - protocol and means that the listener will forward traffic - to all services. \n A name can be specified on multiple - listeners, and will be exposed on both of the listeners." + description: |- + Name declares the service to which traffic should be forwarded. + + + This can either be a specific service, or the wildcard specifier, + "*". If the wildcard specifier is provided, the listener must be of "http" + protocol and means that the listener will forward traffic to all services. + + + A name can be specified on multiple listeners, and will be exposed on both + of the listeners. type: string namespace: - description: Namespace is the namespace where the service - is located. Namespacing is a Consul Enterprise feature. + description: |- + Namespace is the namespace where the service is located. + Namespacing is a Consul Enterprise feature. type: string partition: - description: Partition is the admin-partition where the - service is located. Partitioning is a Consul Enterprise - feature. + description: |- + Partition is the admin-partition where the 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. + 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. + 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. + 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 + 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. + 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. + description: |- + MaxFailures is the count of consecutive failures that results in a host + being removed from the pool. format: int32 type: integer type: object @@ -239,50 +258,52 @@ spec: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that - should be appended to the request or response (i.e. - allowing duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that - should be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that - should be added to the request or response, overwriting - any existing header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting any existing header values of the same name. type: object type: object responseHeaders: - description: HTTPHeaderModifiers is a set of rules for - HTTP header modification that should be performed by - proxies as the request passes through them. It can operate - on either request or response headers depending on the - context in which it is used. + description: |- + HTTPHeaderModifiers is a set of rules for HTTP header modification that + should be performed by proxies as the request passes through them. It can + operate on either request or response headers depending on the context in + which it is used. properties: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that - should be appended to the request or response (i.e. - allowing duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that - should be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that - should be added to the request or response, overwriting - any existing header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting any existing header values of the same name. type: object type: object tls: @@ -299,10 +320,9 @@ spec: from the SDS service. type: string clusterName: - description: ClusterName is the SDS cluster name - to connect to, to retrieve certificates. This - cluster must be specified in the Gateway's bootstrap - configuration. + description: |- + ClusterName is the SDS cluster name to connect to, to retrieve certificates. + This cluster must be specified in the Gateway's bootstrap configuration. type: string type: object type: object @@ -312,9 +332,9 @@ spec: description: TLS config for this listener. properties: cipherSuites: - description: Define a subset of cipher suites to restrict - Only applicable to connections negotiated via TLS 1.2 - or earlier. + description: |- + Define a subset of cipher suites to restrict + Only applicable to connections negotiated via TLS 1.2 or earlier. items: type: string type: array @@ -332,24 +352,23 @@ spec: service. type: string clusterName: - description: ClusterName is the SDS cluster name to - connect to, to retrieve certificates. This cluster - must be specified in the Gateway's bootstrap configuration. + description: |- + ClusterName is the SDS cluster name to connect to, to retrieve certificates. + This cluster must be specified in the Gateway's bootstrap configuration. type: string type: object tlsMaxVersion: - description: TLSMaxVersion sets the default maximum TLS - version supported. Must be greater than or equal to `TLSMinVersion`. - One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or - `TLSv1_3`. If unspecified, Envoy will default to TLS 1.3 - as a max version for incoming connections. + description: |- + TLSMaxVersion sets the default maximum TLS version supported. Must be greater than or equal to `TLSMinVersion`. + One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. + If unspecified, Envoy will default to TLS 1.3 as a max version for incoming connections. type: string tlsMinVersion: - description: TLSMinVersion sets the default minimum TLS - version supported. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, - `TLSv1_2`, or `TLSv1_3`. If unspecified, Envoy v1.22.0 - and newer will default to TLS 1.2 as a min version, while - older releases of Envoy default to TLS 1.0. + description: |- + TLSMinVersion sets the default minimum TLS version supported. + One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. + If unspecified, Envoy v1.22.0 and newer will default to TLS 1.2 as a min version, + while older releases of Envoy default to TLS 1.0. type: string required: - enabled @@ -360,8 +379,9 @@ spec: description: TLS holds the TLS configuration for this gateway. properties: cipherSuites: - description: Define a subset of cipher suites to restrict Only - applicable to connections negotiated via TLS 1.2 or earlier. + description: |- + Define a subset of cipher suites to restrict + Only applicable to connections negotiated via TLS 1.2 or earlier. items: type: string type: array @@ -378,24 +398,23 @@ spec: when fetching the certificate from the SDS service. type: string clusterName: - description: ClusterName is the SDS cluster name to connect - to, to retrieve certificates. This cluster must be specified - in the Gateway's bootstrap configuration. + description: |- + ClusterName is the SDS cluster name to connect to, to retrieve certificates. + This cluster must be specified in the Gateway's bootstrap configuration. type: string type: object tlsMaxVersion: - description: TLSMaxVersion sets the default maximum TLS version - supported. Must be greater than or equal to `TLSMinVersion`. + description: |- + TLSMaxVersion sets the default maximum TLS version supported. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. - If unspecified, Envoy will default to TLS 1.3 as a max version - for incoming connections. + If unspecified, Envoy will default to TLS 1.3 as a max version for incoming connections. type: string tlsMinVersion: - description: TLSMinVersion sets the default minimum TLS version - supported. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, - or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer will default - to TLS 1.2 as a min version, while older releases of Envoy default - to TLS 1.0. + description: |- + TLSMinVersion sets the default minimum TLS version supported. + One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. + If unspecified, Envoy v1.22.0 and newer will default to TLS 1.2 as a min version, + while older releases of Envoy default to TLS 1.0. type: string required: - enabled @@ -407,8 +426,9 @@ spec: 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' + 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 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..c5d0a0443d 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -39,25 +40,25 @@ spec: description: Mesh is the Schema for the mesh 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' + 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' + 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: 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: @@ -71,80 +72,73 @@ spec: mesh. properties: peerThroughMeshGateways: - description: PeerThroughMeshGateways determines whether peering - traffic between control planes should flow through mesh gateways. - If enabled, Consul servers will advertise mesh gateway addresses - as their own. Additionally, mesh gateways will configure themselves - to expose the local servers using a peering-specific SNI. + description: |- + PeerThroughMeshGateways determines whether peering traffic between + control planes should flow through mesh gateways. If enabled, + Consul servers will advertise mesh gateway addresses as their own. + Additionally, mesh gateways will configure themselves to expose + the local servers using a peering-specific SNI. type: boolean type: object tls: description: TLS defines the TLS configuration for the service mesh. properties: incoming: - description: Incoming defines the TLS configuration for inbound - mTLS connections targeting the public listener on Connect and - TerminatingGateway proxy kinds. + description: |- + Incoming defines the TLS configuration for inbound mTLS connections targeting + the public listener on Connect and TerminatingGateway proxy kinds. properties: cipherSuites: - description: CipherSuites sets the default list of TLS cipher - suites to support when negotiating connections using TLS - 1.2 or earlier. If unspecified, Envoy will use a default - server cipher list. The list of supported cipher suites - can be seen in https://github.com/hashicorp/consul/blob/v1.11.2/types/tls.go#L154-L169 - and is dependent on underlying support in Envoy. Future - releases of Envoy may remove currently-supported but insecure - cipher suites, and future releases of Consul may add new - supported cipher suites if any are added to Envoy. + description: |- + CipherSuites sets the default list of TLS cipher suites to support when negotiating connections using TLS 1.2 or earlier. + If unspecified, Envoy will use a default server cipher list. The list of supported cipher suites can be seen in + https://github.com/hashicorp/consul/blob/v1.11.2/types/tls.go#L154-L169 and is dependent on underlying support in Envoy. + Future releases of Envoy may remove currently-supported but insecure cipher suites, + and future releases of Consul may add new supported cipher suites if any are added to Envoy. items: type: string type: array tlsMaxVersion: - description: TLSMaxVersion sets the default maximum TLS version - supported. Must be greater than or equal to `TLSMinVersion`. + description: |- + TLSMaxVersion sets the default maximum TLS version supported. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. - If unspecified, Envoy will default to TLS 1.3 as a max version - for incoming connections. + If unspecified, Envoy will default to TLS 1.3 as a max version for incoming connections. type: string tlsMinVersion: - description: TLSMinVersion sets the default minimum TLS version - supported. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, - or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer will - default to TLS 1.2 as a min version, while older releases - of Envoy default to TLS 1.0. + description: |- + TLSMinVersion sets the default minimum TLS version supported. + One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. + If unspecified, Envoy v1.22.0 and newer will default to TLS 1.2 as a min version, + while older releases of Envoy default to TLS 1.0. type: string type: object outgoing: - description: Outgoing defines the TLS configuration for outbound - mTLS connections dialing upstreams from Connect and IngressGateway - proxy kinds. + description: |- + Outgoing defines the TLS configuration for outbound mTLS connections dialing upstreams + from Connect and IngressGateway proxy kinds. properties: cipherSuites: - description: CipherSuites sets the default list of TLS cipher - suites to support when negotiating connections using TLS - 1.2 or earlier. If unspecified, Envoy will use a default - server cipher list. The list of supported cipher suites - can be seen in https://github.com/hashicorp/consul/blob/v1.11.2/types/tls.go#L154-L169 - and is dependent on underlying support in Envoy. Future - releases of Envoy may remove currently-supported but insecure - cipher suites, and future releases of Consul may add new - supported cipher suites if any are added to Envoy. + description: |- + CipherSuites sets the default list of TLS cipher suites to support when negotiating connections using TLS 1.2 or earlier. + If unspecified, Envoy will use a default server cipher list. The list of supported cipher suites can be seen in + https://github.com/hashicorp/consul/blob/v1.11.2/types/tls.go#L154-L169 and is dependent on underlying support in Envoy. + Future releases of Envoy may remove currently-supported but insecure cipher suites, + and future releases of Consul may add new supported cipher suites if any are added to Envoy. items: type: string type: array tlsMaxVersion: - description: TLSMaxVersion sets the default maximum TLS version - supported. Must be greater than or equal to `TLSMinVersion`. + description: |- + TLSMaxVersion sets the default maximum TLS version supported. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. - If unspecified, Envoy will default to TLS 1.3 as a max version - for incoming connections. + If unspecified, Envoy will default to TLS 1.3 as a max version for incoming connections. type: string tlsMinVersion: - description: TLSMinVersion sets the default minimum TLS version - supported. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, - or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer will - default to TLS 1.2 as a min version, while older releases - of Envoy default to TLS 1.0. + description: |- + TLSMinVersion sets the default minimum TLS version supported. + One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. + If unspecified, Envoy v1.22.0 and newer will default to TLS 1.2 as a min version, + while older releases of Envoy default to TLS 1.0. type: string type: object type: object @@ -153,13 +147,21 @@ spec: to proxies in "transparent" mode. Added in v1.10.0. properties: meshDestinationsOnly: - description: MeshDestinationsOnly determines whether sidecar proxies - operating in "transparent" mode can proxy traffic to IP addresses - not registered in Consul's catalog. If enabled, traffic will - only be proxied to upstreams with service registrations in the - catalog. + description: |- + MeshDestinationsOnly determines whether sidecar proxies operating in "transparent" mode can proxy traffic + to IP addresses not registered in Consul's catalog. If enabled, traffic will only be proxied to upstreams + with service registrations in the catalog. type: boolean type: object + validateClusters: + description: |- + ValidateClusters controls whether the clusters the route table refers to are validated. The default value is + false. When set to false and a route refers to a cluster that does not exist, the route table loads and routing + to a non-existent cluster results in a 404. When set to true and the route is set to a cluster that do not exist, + the route table will not load. For more information, refer to + [HTTP route configuration in the Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route.proto#envoy-v3-api-field-config-route-v3-routeconfiguration-validate-clusters) + for more details. + type: boolean type: object status: properties: @@ -167,8 +169,9 @@ spec: 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' + 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 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..1a2275b8d2 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -41,14 +42,19 @@ spec: description: PeeringAcceptor is the Schema for the peeringacceptors 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' + 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' + 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 @@ -84,8 +90,9 @@ spec: 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' + 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 diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index 09991d2091..9065d922f9 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -41,14 +42,19 @@ spec: description: PeeringDialer is the Schema for the peeringdialers 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' + 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' + 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 @@ -84,8 +90,9 @@ spec: 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' + 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 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..18d0889d9e 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -41,14 +42,19 @@ spec: description: ProxyDefaults is the Schema for the proxydefaults 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' + 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' + 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 @@ -60,37 +66,40 @@ spec: configuration. properties: disableListenerLogs: - description: DisableListenerLogs turns off just listener logs - for connections rejected by Envoy because they don't have a - matching listener filter. + 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.' + 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.' + 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" + 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 + 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: @@ -114,9 +123,9 @@ spec: for Envoy. properties: checks: - description: Checks defines whether paths associated with Consul - checks will be exposed. This flag triggers exposing all HTTP - and GRPC check paths registered for the service. + description: |- + Checks defines whether paths associated with Consul checks will be exposed. + This flag triggers exposing all HTTP and GRPC check paths registered for the service. type: boolean paths: description: Paths is the list of paths exposed through the proxy. @@ -135,87 +144,49 @@ spec: ie. "/metrics". type: string protocol: - description: Protocol describes the upstream's service protocol. + description: |- + Protocol describes the upstream's service protocol. Valid values are "http" and "http2", defaults to "http". type: string 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. properties: mode: - description: Mode is the mode that should be used for the upstream - connection. One of none, local, or remote. + description: |- + Mode is the mode that should be used for the upstream connection. + One of none, local, or remote. type: string type: object mode: - description: 'Mode can be one of "direct" or "transparent". "transparent" - represents that inbound and outbound application traffic is being - captured and redirected through the proxy. This mode does not enable - the traffic redirection itself. Instead it signals Consul to configure - Envoy as if traffic is already being redirected. "direct" represents - that the proxy''s listeners must be dialed directly by the local - application and other proxies. Note: This cannot be set using the - CRD and should be set using annotations on the services that are - part of the mesh.' + description: |- + Mode can be one of "direct" or "transparent". "transparent" represents that inbound and outbound + application traffic is being captured and redirected through the proxy. This mode does not + enable the traffic redirection itself. Instead it signals Consul to configure Envoy as if + traffic is already being redirected. "direct" represents that the proxy's listeners must be + dialed directly by the local application and other proxies. + Note: This cannot be set using the 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 - CRD and should be set using annotations on the services that are - part of the mesh.' + description: |- + TransparentProxy controls configuration specific to proxies in transparent mode. + Note: This cannot be set using the CRD and should be set using annotations on the + services that are part of the mesh. properties: dialedDirectly: - description: DialedDirectly indicates whether transparent proxies - can dial this proxy instance directly. The discovery chain is - not considered when dialing a service instance directly. This - setting is useful when addressing stateful services, such as - a database cluster with a leader node. + description: |- + DialedDirectly indicates whether transparent proxies can dial this proxy instance directly. + The discovery chain is not considered when dialing a service instance directly. + This setting is useful when addressing stateful services, such as a database cluster with a leader node. type: boolean outboundListenerPort: - description: OutboundListenerPort is the port of the listener - where outbound application traffic is being redirected to. + description: |- + OutboundListenerPort is the port of the listener where outbound application + traffic is being redirected to. type: integer type: object type: object @@ -225,8 +196,9 @@ spec: 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' + 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 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..18dae35d72 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -41,14 +42,19 @@ spec: description: ServiceDefaults is the Schema for the servicedefaults 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' + 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' + 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 @@ -56,27 +62,29 @@ 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. + 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 - configured in "transparent" mode. Destinations live outside of Consul's - catalog, and because of this, they do not require an artificial - node to be created. + description: |- + Destination is an address(es)/port combination that represents an endpoint + outside the mesh. This is only valid when the mesh is configured in "transparent" + mode. Destinations live outside of Consul's catalog, and because of this, they + do not require an artificial node to be created. properties: addresses: - description: Addresses is a list of IPs and/or hostnames that - can be dialed and routed through a terminating gateway. + description: |- + Addresses is a list of IPs and/or hostnames that can be dialed + and routed through a terminating gateway. items: type: string type: array port: - description: Port is the port that can be dialed on any of the - addresses in this Destination. + description: |- + Port is the port that can be dialed on any of the addresses in this + Destination. format: int32 type: integer type: object @@ -101,9 +109,9 @@ spec: for Envoy. properties: checks: - description: Checks defines whether paths associated with Consul - checks will be exposed. This flag triggers exposing all HTTP - and GRPC check paths registered for the service. + description: |- + Checks defines whether paths associated with Consul checks will be exposed. + This flag triggers exposing all HTTP and GRPC check paths registered for the service. type: boolean paths: description: Paths is the list of paths exposed through the proxy. @@ -122,202 +130,130 @@ spec: ie. "/metrics". type: string protocol: - description: Protocol describes the upstream's service protocol. + description: |- + Protocol describes the upstream's service protocol. Valid values are "http" and "http2", defaults to "http". type: string type: object type: array type: object externalSNI: - description: ExternalSNI is an optional setting that allows for the - TLS SNI value to be changed to a non-connect value when federating - with an external system. + description: |- + ExternalSNI is an optional setting that allows for the TLS SNI value + to be changed to a non-connect value when federating 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: |- + 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: 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 + 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 - inbound connections to each service instance. Defaults to 0 (using - consul's default) if not set. + description: |- + MaxInboundConnections is the maximum number of concurrent inbound connections to + each service instance. Defaults to 0 (using consul's default) if not set. type: integer meshGateway: description: MeshGateway controls the default mesh gateway configuration for this service. properties: mode: - description: Mode is the mode that should be used for the upstream - connection. One of none, local, or remote. + description: |- + Mode is the mode that should be used for the upstream connection. + One of none, local, or remote. type: string type: object mode: - description: 'Mode can be one of "direct" or "transparent". "transparent" - represents that inbound and outbound application traffic is being - captured and redirected through the proxy. This mode does not enable - the traffic redirection itself. Instead it signals Consul to configure - Envoy as if traffic is already being redirected. "direct" represents - that the proxy''s listeners must be dialed directly by the local - application and other proxies. Note: This cannot be set using the - 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.' + description: |- + Mode can be one of "direct" or "transparent". "transparent" represents that inbound and outbound + application traffic is being captured and redirected through the proxy. This mode does not + enable the traffic redirection itself. Instead it signals Consul to configure Envoy as if + traffic is already being redirected. "direct" represents that the proxy's listeners must be + dialed directly by the local application and other proxies. + Note: This cannot be set using the CRD and should be set using annotations on the + services that are part of the mesh. 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. + 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 - CRD and should be set using annotations on the services that are - part of the mesh.' + description: |- + TransparentProxy controls configuration specific to proxies in transparent mode. + Note: This cannot be set using the CRD and should be set using annotations on the + services that are part of the mesh. properties: dialedDirectly: - description: DialedDirectly indicates whether transparent proxies - can dial this proxy instance directly. The discovery chain is - not considered when dialing a service instance directly. This - setting is useful when addressing stateful services, such as - a database cluster with a leader node. + description: |- + DialedDirectly indicates whether transparent proxies can dial this proxy instance directly. + The discovery chain is not considered when dialing a service instance directly. + This setting is useful when addressing stateful services, such as a database cluster with a leader node. type: boolean outboundListenerPort: - description: OutboundListenerPort is the port of the listener - where outbound application traffic is being redirected to. + description: |- + OutboundListenerPort is the port of the listener where outbound application + traffic is being redirected to. type: integer type: object upstreamConfig: - description: UpstreamConfig controls default configuration settings - that apply across all upstreams, and per-upstream configuration - overrides. Note that per-upstream configuration applies across all - federated datacenters to the pairing of source and upstream destination - services. + description: |- + UpstreamConfig controls default configuration settings that apply across all upstreams, + and per-upstream configuration overrides. Note that per-upstream configuration applies + across all federated datacenters to the pairing of source and upstream destination services. properties: defaults: - description: Defaults contains default configuration for all upstreams - of a given service. The name field must be empty. + description: |- + Defaults contains default configuration for all upstreams of a given + service. The name field must be empty. properties: connectTimeoutMs: - description: ConnectTimeoutMs is the number of milliseconds - to timeout making a new connection to this upstream. Defaults - to 5000 (5 seconds) if not set. + description: |- + ConnectTimeoutMs is the number of milliseconds to timeout making a new + connection to this upstream. Defaults to 5000 (5 seconds) if not set. type: integer envoyClusterJSON: - description: 'EnvoyClusterJSON is a complete override ("escape - hatch") for the upstream''s cluster. The Connect client - TLS certificate and context will be injected overriding - any TLS settings present. Note: This escape hatch is NOT - compatible with the discovery chain and will be ignored - if a discovery chain is active.' + description: |- + EnvoyClusterJSON is a complete override ("escape hatch") for the upstream's + cluster. The Connect client TLS certificate and context will be injected + overriding any TLS settings present. + Note: This escape hatch is NOT compatible with the discovery chain and + will be ignored if a discovery chain is active. type: string envoyListenerJSON: - description: 'EnvoyListenerJSON is a complete override ("escape - hatch") for the upstream''s listener. Note: This escape - hatch is NOT compatible with the discovery chain and will - be ignored if a discovery chain is active.' + description: |- + EnvoyListenerJSON is a complete override ("escape hatch") for the upstream's + listener. + Note: This escape hatch is NOT compatible with the discovery chain and + will be ignored if a discovery chain is active. type: string limits: - description: Limits are the set of limits that are applied - to the proxy for a specific upstream of a service instance. + description: |- + Limits are the set of limits that are applied to the proxy for a specific upstream of a + service instance. properties: maxConcurrentRequests: - description: MaxConcurrentRequests is the maximum number - of in-flight requests that will be allowed to the upstream - cluster at a point in time. This is mostly applicable - to HTTP/2 clusters since all HTTP/1.1 requests are limited - by MaxConnections. + description: |- + MaxConcurrentRequests is the maximum number of in-flight requests that will be allowed + to the upstream cluster at a point in time. This is mostly applicable to HTTP/2 + clusters since all HTTP/1.1 requests are limited by MaxConnections. type: integer maxConnections: - description: MaxConnections is the maximum number of connections - the local proxy can make to the upstream service. + description: |- + MaxConnections is the maximum number of connections the local proxy can + make to the upstream service. type: integer maxPendingRequests: - description: MaxPendingRequests is the maximum number - of requests that will be queued waiting for an available - connection. This is mostly applicable to HTTP/1.1 clusters - since all HTTP/2 requests are streamed over a single + description: |- + MaxPendingRequests is the maximum number of requests that will be queued + waiting for an available connection. This is mostly applicable to HTTP/1.1 + clusters since all HTTP/2 requests are streamed over a single connection. type: integer type: object @@ -326,8 +262,9 @@ spec: are configured and used. properties: mode: - description: Mode is the mode that should be used for - the upstream connection. One of none, local, or remote. + description: |- + Mode is the mode that should be used for the upstream connection. + One of none, local, or remote. type: string type: object name: @@ -343,42 +280,40 @@ spec: config entry. type: string passiveHealthCheck: - description: PassiveHealthCheck configuration determines how - upstream proxy instances will be monitored for removal from - the load balancing pool. + 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. + 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. + 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. + 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. + 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. + description: |- + MaxFailures is the count of consecutive failures that results in a host + being removed from the pool. format: int32 type: integer type: object @@ -387,59 +322,61 @@ spec: config entry. type: string protocol: - description: Protocol describes the upstream's service protocol. - Valid values are "tcp", "http" and "grpc". Anything else - is treated as tcp. This enables protocol aware features - like per-request metrics and connection pooling, tracing, + description: |- + Protocol describes the upstream's service protocol. Valid values are "tcp", + "http" and "grpc". Anything else is treated as tcp. This enables protocol + aware features like per-request metrics and connection pooling, tracing, routing etc. type: string type: object overrides: - description: Overrides is a slice of per-service configuration. - The name field is required. + description: |- + Overrides is a slice of per-service configuration. The name field is + required. items: properties: connectTimeoutMs: - description: ConnectTimeoutMs is the number of milliseconds - to timeout making a new connection to this upstream. Defaults - to 5000 (5 seconds) if not set. + description: |- + ConnectTimeoutMs is the number of milliseconds to timeout making a new + connection to this upstream. Defaults to 5000 (5 seconds) if not set. type: integer envoyClusterJSON: - description: 'EnvoyClusterJSON is a complete override ("escape - hatch") for the upstream''s cluster. The Connect client - TLS certificate and context will be injected overriding - any TLS settings present. Note: This escape hatch is NOT - compatible with the discovery chain and will be ignored - if a discovery chain is active.' + description: |- + EnvoyClusterJSON is a complete override ("escape hatch") for the upstream's + cluster. The Connect client TLS certificate and context will be injected + overriding any TLS settings present. + Note: This escape hatch is NOT compatible with the discovery chain and + will be ignored if a discovery chain is active. type: string envoyListenerJSON: - description: 'EnvoyListenerJSON is a complete override ("escape - hatch") for the upstream''s listener. Note: This escape - hatch is NOT compatible with the discovery chain and will - be ignored if a discovery chain is active.' + description: |- + EnvoyListenerJSON is a complete override ("escape hatch") for the upstream's + listener. + Note: This escape hatch is NOT compatible with the discovery chain and + will be ignored if a discovery chain is active. type: string limits: - description: Limits are the set of limits that are applied - to the proxy for a specific upstream of a service instance. + description: |- + Limits are the set of limits that are applied to the proxy for a specific upstream of a + service instance. properties: maxConcurrentRequests: - description: MaxConcurrentRequests is the maximum number - of in-flight requests that will be allowed to the - upstream cluster at a point in time. This is mostly - applicable to HTTP/2 clusters since all HTTP/1.1 requests - are limited by MaxConnections. + description: |- + MaxConcurrentRequests is the maximum number of in-flight requests that will be allowed + to the upstream cluster at a point in time. This is mostly applicable to HTTP/2 + clusters since all HTTP/1.1 requests are limited by MaxConnections. type: integer maxConnections: - description: MaxConnections is the maximum number of - connections the local proxy can make to the upstream - service. + description: |- + MaxConnections is the maximum number of connections the local proxy can + make to the upstream service. type: integer maxPendingRequests: - description: MaxPendingRequests is the maximum number - of requests that will be queued waiting for an available - connection. This is mostly applicable to HTTP/1.1 - clusters since all HTTP/2 requests are streamed over - a single connection. + description: |- + MaxPendingRequests is the maximum number of requests that will be queued + waiting for an available connection. This is mostly applicable to HTTP/1.1 + clusters since all HTTP/2 requests are streamed over a single + connection. type: integer type: object meshGateway: @@ -447,8 +384,9 @@ spec: are configured and used. properties: mode: - description: Mode is the mode that should be used for - the upstream connection. One of none, local, or remote. + description: |- + Mode is the mode that should be used for the upstream connection. + One of none, local, or remote. type: string type: object name: @@ -464,43 +402,40 @@ spec: config entry. type: string passiveHealthCheck: - description: PassiveHealthCheck configuration determines - how upstream proxy instances will be monitored for removal - from the load balancing pool. + 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. + 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. + 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. + 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. + 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. + description: |- + MaxFailures is the count of consecutive failures that results in a host + being removed from the pool. format: int32 type: integer type: object @@ -509,10 +444,10 @@ spec: config entry. type: string protocol: - description: Protocol describes the upstream's service protocol. - Valid values are "tcp", "http" and "grpc". Anything else - is treated as tcp. This enables protocol aware features - like per-request metrics and connection pooling, tracing, + description: |- + Protocol describes the upstream's service protocol. Valid values are "tcp", + "http" and "grpc". Anything else is treated as tcp. This enables protocol + aware features like per-request metrics and connection pooling, tracing, routing etc. type: string type: object @@ -525,8 +460,9 @@ spec: 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' + 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 diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index 75299f016e..8efe21bbd9 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -41,14 +42,19 @@ spec: description: ServiceIntentions is the Schema for the serviceintentions 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' + 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' + 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 @@ -60,75 +66,38 @@ spec: the authorization granted to. properties: name: - 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. + 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 namespace: - description: Namespace specifies the namespace the config entry - will apply to. This may be set to the wildcard character (*) - to match all services in all namespaces that don't otherwise - have intentions defined. + description: |- + Namespace specifies the namespace the config entry will apply to. + This may be set to the wildcard character (*) to match all services + in all namespaces that don't otherwise 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 - 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. + description: |- + Sources is the list of all intention sources and the authorization granted to those sources. + 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. items: properties: action: - description: 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. + description: |- + 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. type: string description: description: Description for the intention. This is not used by Consul, but is presented in API responses to assist tooling. type: string name: - description: Name is the source of the intention. This is the - name of a Consul service. The service doesn't need to be registered. + description: |- + Name is the source of the intention. This is the name of a + Consul service. The service doesn't need to be registered. type: string namespace: description: Namespace is the namespace for the Name parameter. @@ -137,34 +106,32 @@ 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 - that extend the intention match criteria. Permission precedence - is applied top to bottom. For any given request the first - permission to match in the list is terminal and stops further - evaluation. As with L4 intentions, traffic that fails to match - any of the provided permissions in this intention will be - subject to the default intention behavior is defined by the - default ACL policy. This should be omitted for an L4 intention + description: |- + Permissions is the list of all additional L7 attributes that extend the intention match criteria. + Permission precedence is applied top to bottom. For any given request the first permission to match + in the list is terminal and stops further evaluation. As with L4 intentions, traffic that fails to + match any of the provided permissions in this intention will be subject to the default intention + behavior is defined by the default ACL policy. This should be omitted for an L4 intention as it is mutually exclusive with the Action field. items: properties: action: - description: Action is one of "allow" or "deny" for the - action that should be taken if this permission matches - a request. + description: |- + Action is one of "allow" or "deny" for the action that + should be taken if this permission matches a request. type: string http: description: HTTP is a set of HTTP-specific authorization criteria. properties: header: - description: Header is a set of criteria that can - match on HTTP request headers. If more than one - is configured all must match for the overall match - to apply. + description: |- + Header is a set of criteria that can match on HTTP request headers. + If more than one is configured all must match for the overall match to apply. items: properties: exact: @@ -198,10 +165,9 @@ spec: type: object type: array methods: - description: Methods is a list of HTTP methods for - which this match applies. If unspecified all HTTP - methods are matched. If provided the names must - be a valid method. + description: |- + Methods is a list of HTTP methods for which this match applies. If unspecified + all HTTP methods are matched. If provided the names must be a valid method. items: type: string type: array @@ -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 @@ -271,8 +195,9 @@ spec: 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' + 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 diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 6d89125216..aa13fd07f2 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -41,14 +42,19 @@ spec: description: ServiceResolver is the Schema for the serviceresolvers 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' + 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' + 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 @@ -56,12 +62,14 @@ spec: description: ServiceResolverSpec defines the desired state of ServiceResolver. properties: connectTimeout: - description: ConnectTimeout is the timeout for establishing new network - connections to this service. + description: |- + ConnectTimeout is the timeout for establishing new network connections + to this service. type: string defaultSubset: - description: DefaultSubset is the subset to use when no explicit subset - is requested. If empty the unnamed subset is used. + description: |- + DefaultSubset is the subset to use when no explicit subset is requested. + If empty the unnamed subset is used. type: string failover: additionalProperties: @@ -73,38 +81,20 @@ spec: type: string type: array namespace: - description: Namespace is the namespace to resolve the requested - 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. + description: |- + Namespace is the namespace to resolve the requested service from to form + the failover group of instances. If empty the current namespace is used. type: string service: - description: Service is the service to resolve instead of the - default as the failover group of instances during failover. + description: |- + Service is the service to resolve instead of the default as the failover + group of instances during failover. type: string serviceSubset: - description: ServiceSubset is the named subset of the requested - service to resolve as the failover group of instances. If - empty the default subset for the requested service is used. + description: |- + ServiceSubset is the named subset of the requested service to resolve as + the failover group of instances. If empty the default subset for the + requested service is used. type: string targets: description: Targets specifies a fixed list of failover targets @@ -138,21 +128,25 @@ spec: type: object type: array type: object - description: Failover controls when and how to reroute traffic to - an alternate pool of service instances. The map is keyed by the - service subset it applies to and the special string "*" is a wildcard - that applies to any subset not otherwise specified here. + description: |- + Failover controls when and how to reroute traffic to an alternate pool of + service instances. + The map is keyed by the service subset it applies to and the special + string "*" is a wildcard that applies to any subset not otherwise + specified here. type: object loadBalancer: - description: LoadBalancer determines the load balancing policy and - configuration for services issuing requests to this upstream service. + description: |- + LoadBalancer determines the load balancing policy and configuration for services + issuing requests to this upstream service. properties: hashPolicies: - description: HashPolicies is a list of hash policies to use for - hashing load balancing algorithms. Hash policies are evaluated - individually and combined such that identical lists result in - the same hash. If no hash policies are present, or none are - successfully evaluated, then a random backend host will be selected. + description: |- + HashPolicies is a list of hash policies to use for hashing load balancing algorithms. + Hash policies are evaluated individually and combined such that identical lists + result in the same hash. + If no hash policies are present, or none are successfully evaluated, + then a random backend host will be selected. items: properties: cookieConfig: @@ -172,26 +166,27 @@ spec: type: string type: object field: - description: Field is the attribute type to hash on. Must - be one of "header", "cookie", or "query_parameter". Cannot - be specified along with sourceIP. + description: |- + Field is the attribute type to hash on. + Must be one of "header", "cookie", or "query_parameter". + Cannot be specified along with sourceIP. type: string fieldValue: - description: FieldValue is the value to hash. ie. header - name, cookie name, URL query parameter name Cannot be - specified along with sourceIP. + description: |- + FieldValue is the value to hash. + ie. header name, cookie name, URL query parameter name + Cannot be specified along with sourceIP. type: string sourceIP: - description: SourceIP determines whether the hash should - be of the source IP rather than of a field and field value. + description: |- + SourceIP determines whether the hash should be of the source IP rather than of a field and field value. Cannot be specified along with field or fieldValue. type: boolean terminal: - description: Terminal will short circuit the computation - of the hash when multiple hash policies are present. If - a hash is computed when a Terminal policy is evaluated, - then that hash will be used and subsequent hash policies - will be ignored. + description: |- + Terminal will short circuit the computation of the hash when multiple hash policies are present. + If a hash is computed when a Terminal policy is evaluated, + then that hash will be used and subsequent hash policies will be ignored. type: boolean type: object type: array @@ -225,81 +220,74 @@ 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 - redirect EXCEPT when the redirect has already been applied. When - substituting the supplied redirect, all other fields besides Kind, - Name, and Redirect will be ignored. + description: |- + Redirect when configured, all attempts to resolve the service this + resolver defines will be substituted for the supplied redirect + EXCEPT when the redirect has already been applied. + When substituting the supplied redirect, all other fields besides + Kind, Name, and Redirect will be ignored. properties: datacenter: - description: Datacenter is the datacenter to resolve the service - from instead of the current one. + description: |- + Datacenter is the datacenter to resolve the service from instead of the + current one. type: string namespace: - description: Namespace is the Consul namespace to resolve the - service from instead of the current namespace. If empty the - current namespace is assumed. + description: |- + Namespace is the Consul namespace to resolve the service from instead of + the current namespace. If empty the current namespace is assumed. type: string partition: - description: Partition is the Consul partition to resolve the - service from instead of the current partition. If empty the - current partition is assumed. + description: |- + Partition is the Consul partition to resolve the service from instead of + the current partition. If empty the current partition is assumed. type: string peer: - 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. + description: |- + Peer is the name of the cluster peer 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. type: string serviceSubset: - description: ServiceSubset is a named subset of the given service - to resolve instead of one defined as that service's DefaultSubset - If empty the default subset is used. + description: |- + ServiceSubset is a named subset of the given service to resolve instead + of one defined as that service's DefaultSubset 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. + description: |- + RequestTimeout is the timeout for receiving an HTTP response from this + service before the connection is terminated. type: string subsets: additionalProperties: properties: filter: - description: Filter is the filter expression to be used for - selecting instances of the requested service. If empty all - healthy instances are returned. This expression can filter - on the same selectors as the Health API endpoint. + description: |- + Filter is the filter expression to be used for selecting instances of the + requested service. If empty all healthy instances are returned. This + expression can filter on the same selectors as the Health API endpoint. type: string onlyPassing: - description: OnlyPassing specifies the behavior of the resolver's - health check interpretation. If this is set to false, instances - with checks in the passing as well as the warning states will - be considered healthy. If this is set to true, only instances - with checks in the passing state will be considered healthy. + description: |- + OnlyPassing specifies the behavior of the resolver's health check + interpretation. If this is set to false, instances with checks in the + passing as well as the warning states will be considered healthy. If this + is set to true, only instances with checks in the passing state will be + considered healthy. type: boolean type: object - description: Subsets is map of subset name to subset definition for - all usable named subsets of this service. The map key is the name - of the subset and all names must be valid DNS subdomain elements. - This may be empty, in which case only the unnamed default subset - will be usable. + description: |- + Subsets is map of subset name to subset definition for all usable named + subsets of this service. The map key is the name of the subset and all + names must be valid DNS subdomain elements. + This may be empty, in which case only the unnamed default subset will + be usable. type: object type: object status: @@ -308,8 +296,9 @@ spec: 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' + 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 diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index c7924081fd..338165ab8f 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -41,14 +42,19 @@ spec: description: ServiceRouter is the Schema for the servicerouters 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' + 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' + 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 @@ -56,10 +62,11 @@ spec: description: ServiceRouterSpec defines the desired state of ServiceRouter. properties: routes: - description: Routes are the list of routes to consider when processing - L7 requests. The first route to match in the list is terminal and - stops further evaluation. Traffic that fails to match any of the - provided routes will be routed to the default service. + description: |- + Routes are the list of routes to consider when processing L7 requests. + The first route to match in the list is terminal and stops further + evaluation. Traffic that fails to match any of the provided routes will + be routed to the default service. items: properties: destination: @@ -67,13 +74,14 @@ spec: request(s) to a service. properties: idleTimeout: - description: IdleTimeout is total amount of time permitted + 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 - empty the current namespace is assumed. + description: |- + Namespace is the Consul namespace to resolve the service from instead of + the current namespace. If empty the current namespace is assumed. type: string numRetries: description: NumRetries is the number of times to retry @@ -81,13 +89,14 @@ spec: format: int32 type: integer partition: - description: Partition is the Consul partition to resolve - the service from instead of the current partition. If - empty the current partition is assumed. + description: |- + Partition is the Consul partition to resolve the service from instead of + the current partition. If empty the current partition is assumed. type: string prefixRewrite: - description: PrefixRewrite defines how to rewrite the HTTP - request path before proxying it to its final destination. + description: |- + PrefixRewrite defines how to rewrite the HTTP request path before proxying + it to its final destination. This requires that either match.http.pathPrefix or match.http.pathExact be configured on this route. type: string @@ -97,61 +106,63 @@ spec: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that - should be appended to the request or response (i.e. - allowing duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that - should be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that - should be added to the request or response, overwriting - any existing header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting any existing header values of the same name. type: object type: object requestTimeout: - description: RequestTimeout is the total amount of time - permitted for the entire downstream request (and retries) - to be processed. + description: |- + RequestTimeout is the total amount of time permitted for the entire + downstream request (and retries) to be processed. type: string responseHeaders: - description: HTTPHeaderModifiers is a set of rules for HTTP - header modification that should be performed by proxies - as the request passes through them. It can operate on - either request or response headers depending on the context - in which it is used. + description: |- + HTTPHeaderModifiers is a set of rules for HTTP header modification that + should be performed by proxies as the request passes through them. It can + operate on either request or response headers depending on the context in + which it is used. properties: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that - should be appended to the request or response (i.e. - allowing duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that - should be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that - should be added to the request or response, overwriting - any existing header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting 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' + 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 @@ -167,32 +178,29 @@ spec: type: integer type: array service: - description: Service is the service to resolve instead of - the default service. If empty then the default service - name is used. + description: |- + Service is the service to resolve instead of the default service. + If empty then the default service name is used. type: string serviceSubset: - description: ServiceSubset is a named subset of the given - service to resolve instead of the one defined as that - service's DefaultSubset. If empty, the default subset - is used. + description: |- + ServiceSubset is a named subset of the given service to resolve instead + of the one defined as that service's DefaultSubset. + If empty, the default subset is used. type: string type: object match: - description: Match is a set of criteria that can match incoming - L7 requests. If empty or omitted it acts as a catch-all. + description: |- + Match is a set of criteria that can match incoming L7 requests. + If empty or omitted it acts as a catch-all. properties: 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 - all must match for the overall match to apply. + description: |- + Header is a set of criteria that can match on HTTP request headers. + If more than one is configured all must match for the overall match to apply. items: properties: exact: @@ -227,9 +235,9 @@ spec: type: object type: array methods: - description: Methods is a list of HTTP methods for which - this match applies. If unspecified all http methods - are matched. + description: |- + Methods is a list of HTTP methods for which this match applies. + If unspecified all http methods are matched. items: type: string type: array @@ -246,10 +254,9 @@ spec: on the HTTP request path. type: string queryParam: - description: QueryParam is a set of criteria that can - match on HTTP query parameters. If more than one is - configured all must match for the overall match to - apply. + description: |- + QueryParam is a set of criteria that can match on HTTP query parameters. + If more than one is configured all must match for the overall match to apply. items: properties: exact: @@ -261,8 +268,9 @@ spec: to match on. type: string present: - description: Present will match if the query parameter - with the given name is present with any value. + description: |- + Present will match if the query parameter with the given name is present + with any value. type: boolean regex: description: Regex will match if the query parameter @@ -283,8 +291,9 @@ spec: 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' + 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 diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index 8d5ed58023..8d6a5fd4c0 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -41,14 +42,19 @@ spec: description: ServiceSplitter is the Schema for the servicesplitters 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' + 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' + 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 @@ -56,20 +62,20 @@ spec: description: ServiceSplitterSpec defines the desired state of ServiceSplitter. properties: splits: - description: Splits defines how much traffic to send to which set - of service instances during a traffic split. The sum of weights - across all splits must add up to 100. + description: |- + Splits defines how much traffic to send to which set of service instances during a traffic split. + The sum of weights across all splits must add up to 100. items: properties: namespace: - description: Namespace is the Consul namespace to resolve the - service from instead of the current namespace. If empty the - current namespace is assumed. + description: |- + Namespace is the Consul namespace to resolve the service from instead of + the current namespace. If empty the current namespace is assumed. type: string partition: - description: Partition is the Consul partition to resolve the - service from instead of the current partition. If empty the - current partition is assumed. + description: |- + Partition is the Consul partition to resolve the service from instead of + the current partition. If empty the current partition is assumed. type: string requestHeaders: description: Allow HTTP header manipulation to be configured. @@ -77,50 +83,52 @@ spec: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that should - be appended to the request or response (i.e. allowing - duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that should - be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that should - be added to the request or response, overwriting any existing - header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting any existing header values of the same name. type: object type: object responseHeaders: - description: HTTPHeaderModifiers is a set of rules for HTTP - header modification that should be performed by proxies as - the request passes through them. It can operate on either - request or response headers depending on the context in which - it is used. + description: |- + HTTPHeaderModifiers is a set of rules for HTTP header modification that + should be performed by proxies as the request passes through them. It can + operate on either request or response headers depending on the context in + which it is used. properties: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that should - be appended to the request or response (i.e. allowing - duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that should - be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that should - be added to the request or response, overwriting any existing - header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting any existing header values of the same name. type: object type: object service: @@ -128,13 +136,13 @@ spec: default. type: string serviceSubset: - description: ServiceSubset is a named subset of the given service - to resolve instead of one defined as that service's DefaultSubset. - If empty the default subset is used. + description: |- + ServiceSubset is a named subset of the given service to resolve instead of one defined + as that service's DefaultSubset. If empty the default subset is used. type: string weight: - description: Weight is a value between 0 and 100 reflecting - what portion of traffic should be directed to this split. + description: |- + Weight is a value between 0 and 100 reflecting what portion of traffic should be directed to this split. The smallest representable weight is 1/10000 or .01%. type: number type: object @@ -146,8 +154,9 @@ spec: 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' + 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 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 c0e87a9c3c..0000000000 --- a/charts/consul/templates/crd-tcproutes.yaml +++ /dev/null @@ -1,267 +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. \n For more details on potential values of this - field, see documentation for Service.ServicePort." - 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 - 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. - For north/south this is TBD. \n For more details - on potential values of this field, see documentation - for Service.ServicePort." - 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 - 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 cd53122e9d..ed328ff528 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -1,16 +1,17 @@ {{- 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.14.0 + 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: @@ -42,14 +43,19 @@ spec: 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' + 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' + 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 @@ -64,22 +70,19 @@ spec: gateway. properties: caFile: - description: CAFile is the optional path to a CA certificate - to use for TLS connections from the gateway to the linked - service. + description: |- + CAFile is the optional path to a CA certificate to use for TLS connections + from the gateway to the linked service. type: string certFile: - description: CertFile is the optional path to a client certificate - to use for TLS connections from the gateway to the linked - service. + description: |- + CertFile is the optional path to a client certificate to use for TLS connections + from the gateway to the linked service. type: string - disableAutoHostRewrite: - description: DisableAutoHostRewrite disables terminating gateways - auto host rewrite feature when set to true. - type: boolean keyFile: - description: KeyFile is the optional path to a private key to - use for TLS connections from the gateway to the linked service. + description: |- + KeyFile is the optional path to a private key to use for TLS connections + from the gateway to the linked service. type: string name: description: Name is the name of the service, as defined in @@ -101,8 +104,9 @@ spec: 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' + 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 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 87727f4fbf..0000000000 --- a/charts/consul/templates/crd-trafficpermissions.yaml +++ /dev/null @@ -1,265 +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: - headers: - items: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - type: array - 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 - headers: - items: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - type: array - 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 aff6b5a934..28e24e8212 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/datadog-agent-role.yaml b/charts/consul/templates/datadog-agent-role.yaml deleted file mode 100644 index 191e6433c6..0000000000 --- a/charts/consul/templates/datadog-agent-role.yaml +++ /dev/null @@ -1,38 +0,0 @@ -{{- if .Values.global.metrics.datadog.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ template "consul.fullname" . }}-datadog-metrics - namespace: {{ .Release.Namespace }} - labels: - app: datadog - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: agent -{{- if (or (and .Values.global.openshift.enabled .Values.server.exposeGossipAndRPCPorts) .Values.global.enablePodSecurityPolicies) }} -{{- if .Values.global.enablePodSecurityPolicies }} -rules: - - apiGroups: ["policy"] - resources: ["podsecuritypolicies"] - resourceNames: - - {{ template "consul.fullname" . }}-datadog-metrics - verbs: - - use -{{- end }} -{{- if (and .Values.global.openshift.enabled .Values.server.exposeGossipAndRPCPorts ) }} - - apiGroups: ["security.openshift.io"] - resources: ["securitycontextconstraints"] - resourceNames: - - {{ template "consul.fullname" . }}-datadog-metrics - verbs: - - use -{{- end }} -{{- else}} -rules: - - apiGroups: [ "" ] - resources: [ "secrets" ] - resourceNames: - - {{ .Release.Namespace }}-datadog-agent-metrics-acl-token - verbs: [ "get", "watch", "list" ] -{{- end }} -{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/datadog-agent-rolebinding.yaml b/charts/consul/templates/datadog-agent-rolebinding.yaml deleted file mode 100644 index 5fc3fdf545..0000000000 --- a/charts/consul/templates/datadog-agent-rolebinding.yaml +++ /dev/null @@ -1,26 +0,0 @@ -{{- if .Values.global.metrics.datadog.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ template "consul.fullname" . }}-datadog-metrics - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: agent -subjects: - - kind: ServiceAccount - apiGroup: "" - name: datadog-agent - namespace: datadog - - kind: ServiceAccount - apiGroup: "" - name: datadog-cluster-agent - namespace: datadog -roleRef: - kind: Role - name: {{ template "consul.fullname" . }}-datadog-metrics - apiGroup: "" -{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/enterprise-license-job.yaml b/charts/consul/templates/enterprise-license-job.yaml index 8db9500a22..47aad0e599 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-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 d00f9b3e86..0000000000 --- a/charts/consul/templates/gateway-resources-configmap.yaml +++ /dev/null @@ -1,195 +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) (or .Values.meshGateway.enabled .Values.connectInject.apiGateway.managedGatewayClass) }} - config.yaml: | - gatewayClassConfigs: - {{- if .Values.meshGateway.enabled }} - - 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 }} - {{- end }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass }} - - apiVersion: mesh.consul.hashicorp.com/v2beta1 - metadata: - name: consul-api-gateway - kind: GatewayClassConfig - spec: - labels: - set: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: api-gateway - {{- if .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service }} - annotations: - service: - {{ fromYamlArray .Values.connectInject.apiGateway.managedGatewayClass.copyAnnotations.service.annotations | toYaml }} - {{- end}} - {{- end}} - deployment: - {{- if .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector }} - nodeSelector: - {{ fromYamlArray .Values.connectInject.apiGateway.managedGatewayClass.nodeSelector | toYaml }} - {{- end }} - initContainer: - {{- if .Values.connectInject.apiGateway.managedGatewayClass.mapPrivilegedContainerPorts }} - portModifier: {{ .Values.connectInject.apiGateway.managedGatewayClass.mapPrivilegedContainerPorts }} - {{- end }} - consul: - logging: - level: {{ .Values.global.logLevel }} - container: - {{- if .Values.connectInject.apiGateway.managedGatewayClass.mapPrivilegedContainerPorts }} - portModifier: {{ .Values.connectInject.apiGateway.managedGatewayClass.mapPrivilegedContainerPorts }} - {{- end }} - consul: - logging: - level: {{ .Values.global.logLevel }} - replicas: - default: {{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances }} - min: {{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.minInstances }} - max: {{ .Values.connectInject.apiGateway.managedGatewayClass.deployment.maxInstances }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.tolerations }} - tolerations: - {{ fromYamlArray .Values.connectInject.apiGateway.managedGatewayClass.tolerations | toYaml }} - {{- end }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.service }} - service: - annotations: - set: {{ toYaml .Values.connectInject.apiGateway.managedGatewayClass.service.annotations }} - {{- end }} - type: {{ .Values.connectInject.apiGateway.managedGatewayClass.serviceType }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.serviceAccount }} - serviceAccount: - annotations: - set: {{ toYaml .Values.connectInject.apiGateway.managedGatewayClass.serviceAccount.annotations }} - {{- end }} - {{- end }} - {{- if .Values.meshGateway.enabled }} - meshGateways: - - apiVersion: mesh.consul.hashicorp.com/v2beta1 - kind: MeshGateway - metadata: - name: mesh-gateway - namespace: {{ .Release.Namespace }} - annotations: - "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 }} -{{- end }} diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml deleted file mode 100644 index e43efc8a9a..0000000000 --- a/charts/consul/templates/gateway-resources-job.yaml +++ /dev/null @@ -1,110 +0,0 @@ -{{- if .Values.apiGateway}}{{fail "[DEPRECATED and REMOVED] the apiGateway stanza is no longer supported as of Consul 1.19.0. Use connectInject.apiGateway instead."}}{{- end -}} -{{- 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.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 }} - {{- if (ne (.Values.connectInject.apiGateway.managedGatewayClass.metrics.enabled | toString) "-") }} - - -enable-metrics={{ .Values.connectInject.apiGateway.managedGatewayClass.metrics.enabled | toString }} - {{- end }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.metrics.path }} - - -metrics-path={{ .Values.connectInject.apiGateway.managedGatewayClass.metrics.path }} - {{- end }} - {{- if .Values.connectInject.apiGateway.managedGatewayClass.metrics.port }} - - -metrics-port={{ .Values.connectInject.apiGateway.managedGatewayClass.metrics.port }} - {{- 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-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 cea13c77fe..af30061c78 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 508ab64eff..1d01033f2d 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 }} @@ -132,9 +131,7 @@ spec: {{- end }} {{- if (and $root.Values.global.metrics.enabled $root.Values.global.metrics.enableGatewayMetrics) }} "prometheus.io/scrape": "true" - {{- if not (hasKey (default "" $defaults.annotations | fromYaml) "prometheus.io/path")}} "prometheus.io/path": "/metrics" - {{- end }} "prometheus.io/port": "20200" {{- end }} {{- if $defaults.annotations }} 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 3d75d55613..17ba313796 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 }}" @@ -78,9 +76,7 @@ spec: {{- end }} {{- if (and .Values.global.metrics.enabled .Values.global.metrics.enableGatewayMetrics) }} "prometheus.io/scrape": "true" - {{- if not (hasKey (default "" .Values.meshGateway.annotations | fromYaml) "prometheus.io/path")}} "prometheus.io/path": "/metrics" - {{- end }} "prometheus.io/port": "20200" {{- end }} {{- if .Values.meshGateway.annotations }} @@ -320,4 +316,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 21ad2930b8..88c6501051 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/partition-init-role.yaml b/charts/consul/templates/partition-init-role.yaml index c13a5378eb..340c59ed95 100644 --- a/charts/consul/templates/partition-init-role.yaml +++ b/charts/consul/templates/partition-init-role.yaml @@ -15,12 +15,6 @@ metadata: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-delete-policy": before-hook-creation rules: - - apiGroups: [""] - resources: - - secrets - verbs: - - create - - get {{- if .Values.connectInject.enabled }} - apiGroups: [""] resources: 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 ca10cb3e34..7d56116d8d 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 }} @@ -192,10 +191,6 @@ 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 }} \ @@ -273,10 +268,6 @@ spec: -create-enterprise-license-token=true \ {{- end }} - {{- if (and (not .Values.global.metrics.datadog.dogstatsd.enabled) .Values.global.metrics.datadog.enabled .Values.global.acls.manageSystemACLs) }} - -create-dd-agent-token=true \ - {{- end }} - {{- if .Values.server.snapshotAgent.enabled }} -snapshot-agent=true \ {{- end }} @@ -304,6 +295,10 @@ spec: -partition-token-file=/vault/secrets/partition-token \ {{- end }} + {{- if .Values.apiGateway.enabled }} + -api-gateway-controller=true \ + {{- end }} + {{- if .Values.global.enableConsulNamespaces }} -enable-namespaces=true \ {{- /* syncCatalog must be enabled to set sync flags */}} 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-config-configmap.yaml b/charts/consul/templates/server-config-configmap.yaml index 8c74364a29..7a188db335 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 @@ -30,15 +29,7 @@ data: {{- if .Values.server.logLevel }} "log_level": "{{ .Values.server.logLevel | upper }}", {{- end }} - "enable_debug": {{ .Values.server.enableAgentDebug }}, "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, @@ -57,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 }} @@ -193,13 +179,7 @@ data: telemetry-config.json: |- { "telemetry": { - "prometheus_retention_time": "{{ .Values.global.metrics.agentMetricsRetentionTime }}", - "disable_hostname": {{ .Values.global.metrics.disableAgentHostName }},{{ template "consul.prefixFilter" . }} - "enable_host_metrics": {{ .Values.global.metrics.enableHostMetrics }}{{- if .Values.global.metrics.datadog.dogstatsd.enabled }},{{ template "consul.dogstatsdAaddressInfo" . }} - {{- if .Values.global.metrics.datadog.dogstatsd.enabled }} - "dogstatsd_tags": {{ .Values.global.metrics.datadog.dogstatsd.dogstatsdTags | toJson }} - {{- end }} - {{- end }} + "prometheus_retention_time": "{{ .Values.global.metrics.agentMetricsRetentionTime }}" } } {{- end }} 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 b1028e754a..b64aae7d4e 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -19,9 +19,6 @@ {{- end -}} {{ template "consul.validateRequiredCloudSecretsExist" . }} {{ template "consul.validateCloudSecretKeys" . }} -{{ template "consul.validateMetricsConfig" . }} -{{ template "consul.validateDatadogConfiguration" . }} -{{ template "consul.validateExtraConfig" . }} # StatefulSet to run the actual Consul server cluster. apiVersion: apps/v1 kind: StatefulSet @@ -65,11 +62,6 @@ spec: release: {{ .Release.Name }} component: server hasDNS: "true" - {{- if .Values.global.metrics.datadog.enabled }} - "tags.datadoghq.com/version": {{ template "consul.versionInfo" . }} - "tags.datadoghq.com/env": {{ template "consul.name" . }} - "tags.datadoghq.com/service": "consul-server" - {{- end }} {{- if .Values.server.extraLabels }} {{- toYaml .Values.server.extraLabels | nindent 8 }} {{- end }} @@ -126,17 +118,13 @@ 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 }} {{- if .Values.server.annotations }} {{- tpl .Values.server.annotations . | nindent 8 }} {{- end }} {{- if (and .Values.global.metrics.enabled .Values.global.metrics.enableAgentMetrics) }} - {{- if not .Values.global.metrics.datadog.openMetricsPrometheus.enabled }} "prometheus.io/scrape": "true" - {{- if not (hasKey (default "" .Values.server.annotations | fromYaml) "prometheus.io/path")}} "prometheus.io/path": "/v1/agent/metrics" - {{- end }} {{- if .Values.global.tls.enabled }} "prometheus.io/port": "8501" "prometheus.io/scheme": "https" @@ -145,67 +133,6 @@ spec: "prometheus.io/scheme": "http" {{- end }} {{- end }} - {{- if .Values.global.metrics.datadog.enabled }} - "ad.datadoghq.com/tolerate-unready": "true" - "ad.datadoghq.com/consul.logs": {{ .Values.global.metrics.datadog.dogstatsd.dogstatsdTags | toJson | replace "[" "[{" | replace "]" "}]" | replace ":" "\": \"" | join "\",\"" | squote }} - {{- if .Values.global.metrics.datadog.openMetricsPrometheus.enabled }} - "ad.datadoghq.com/consul.checks": | - { - "openmetrics": { - "init_config": {}, - "instances": [ - { - {{- if .Values.global.tls.enabled }} - "openmetrics_endpoint": "https://consul-server.{{ .Release.Namespace }}.svc:8501/v1/agent/metrics?format=prometheus", - "tls_cert": "/etc/datadog-agent/conf.d/consul.d/certs/tls.crt", - "tls_private_key": "/etc/datadog-agent/conf.d/consul.d/certs/tls.key", - "tls_ca_cert": "/etc/datadog-agent/conf.d/consul.d/ca/tls.crt", - {{- else }} - "openmetrics_endpoint": "http://consul-server.{{ .Release.Namespace }}.svc:8500/v1/agent/metrics?format=prometheus", - {{- end }} - {{- if ( .Values.global.acls.manageSystemACLs) }} - "headers": { - "X-Consul-Token": "ENC[k8s_secret@{{ .Release.Namespace }}/{{ .Release.Namespace }}-datadog-agent-metrics-acl-token/token]" - }, - {{- end }} - "namespace": "{{ .Release.Namespace }}", - "metrics": [ ".*" ] - } - ] - } - } - {{- else if (not .Values.global.metrics.datadog.dogstatsd.enabled) }} - "ad.datadoghq.com/consul.checks": | - { - "consul": { - "init_config": {}, - "instances": [ - { - {{- if .Values.global.tls.enabled }} - "url": "https://consul-server.{{ .Release.Namespace }}.svc:8501", - "tls_cert": "/etc/datadog-agent/conf.d/consul.d/certs/tls.crt", - "tls_private_key": "/etc/datadog-agent/conf.d/consul.d/certs/tls.key", - "tls_ca_cert": "/etc/datadog-agent/conf.d/consul.d/ca/tls.crt", - {{- else }} - "url": "http://consul-server.consul.svc:8500", - {{- end }} - "use_prometheus_endpoint": true, - {{- if ( .Values.global.acls.manageSystemACLs) }} - "acl_token": "ENC[k8s_secret@{{ .Release.Namespace }}/{{ .Release.Namespace }}-datadog-agent-metrics-acl-token/token]", - {{- end }} - "new_leader_checks": true, - "network_latency_checks": true, - "catalog_checks": true, - "auth_type": "basic" - } - ] - } - } - {{- else }} - "ad.datadoghq.com/consul.metrics_exclude": "true" - {{- end }} - {{- end }} - {{- end }} spec: {{- if .Values.server.affinity }} affinity: @@ -291,12 +218,6 @@ spec: emptyDir: medium: "Memory" {{- end }} - {{- if and .Values.global.metrics.datadog.enabled .Values.global.metrics.datadog.dogstatsd.enabled (eq .Values.global.metrics.datadog.dogstatsd.socketTransportType "UDS" ) }} - - name: dsdsocket - hostPath: - path: {{ dir .Values.global.metrics.datadog.dogstatsd.dogstatsdAddr | trimAll "\"" }} - type: DirectoryOrCreate - {{- end }} {{- range .Values.server.extraVolumes }} - name: userconfig-{{ .name }} {{ .type }}: @@ -316,27 +237,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" - - | - exec 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 | trimPrefix "\"" | trimSuffix "\"" }}" - imagePullPolicy: {{ .Values.global.imagePullPolicy }} + image: "{{ default .Values.global.image .Values.server.image }}" env: - name: ADVERTISE_IP valueFrom: @@ -508,18 +411,6 @@ spec: {{- 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 @@ -542,11 +433,6 @@ spec: mountPath: /consul/license readOnly: true {{- end }} - {{- if and .Values.global.metrics.datadog.enabled .Values.global.metrics.datadog.dogstatsd.enabled (eq .Values.global.metrics.datadog.dogstatsd.socketTransportType "UDS" ) }} - - name: dsdsocket - mountPath: {{ dir .Values.global.metrics.datadog.dogstatsd.dogstatsdAddr | trimAll "\"" }} - readOnly: true - {{- end }} {{- range .Values.server.extraVolumes }} - name: userconfig-{{ .name }} readOnly: true diff --git a/charts/consul/templates/sync-catalog-clusterrole.yaml b/charts/consul/templates/sync-catalog-clusterrole.yaml index 89ea9f3c5c..585b5ad171 100644 --- a/charts/consul/templates/sync-catalog-clusterrole.yaml +++ b/charts/consul/templates/sync-catalog-clusterrole.yaml @@ -14,19 +14,7 @@ rules: - apiGroups: [ "" ] resources: - services - verbs: - - get - - list - - watch -{{- if .Values.syncCatalog.toK8S }} - - update - - patch - - delete - - create -{{- end }} -- apiGroups: ["discovery.k8s.io"] - resources: - - endpointslices + - endpoints verbs: - get - list @@ -57,4 +45,4 @@ rules: - get - list - watch -{{- end }} +{{- 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 3851f0a8e2..c5a590d375 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 }} @@ -192,9 +191,6 @@ spec: -loadBalancer-ips=true \ {{- end }} {{- end }} - {{- if .Values.syncCatalog.syncLoadBalancerEndpoints }} - -sync-lb-services-endpoints=true \ - {{- end }} livenessProbe: httpGet: path: /health/ready diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index f7b6d7bd2e..45216600a6 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.telemetryCollector.enabled (not (mustHas "resource-apis" .Values.global.experiments)) }} +{{- 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 }} @@ -171,7 +171,7 @@ spec: containers: - name: consul-telemetry-collector image: {{ .Values.telemetryCollector.image }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} + imagePullPolicy: Always ports: - containerPort: 9090 name: metrics @@ -248,19 +248,6 @@ spec: - name: SSL_CERT_DIR value: "/etc/ssl/certs:/trusted-cas" {{- end }} - {{- if .Values.global.metrics.datadog.otlp.enabled }} - - name: HOST_IP - valueFrom: - fieldRef: - fieldPath: status.hostIP - {{- if eq (.Values.global.metrics.datadog.otlp.protocol | lower ) "http" }} - - name: CO_OTEL_HTTP_ENDPOINT - value: "http://$(HOST_IP):4318" - {{- else if eq (.Values.global.metrics.datadog.otlp.protocol | lower) "grpc" }} - - name: CO_OTEL_HTTP_ENDPOINT - value: "grpc://$(HOST_IP):4317" - {{- end }} - {{- end }} {{- include "consul.extraEnvironmentVars" .Values.telemetryCollector | nindent 12 }} command: - "/bin/sh" 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 09f4a2dbbc..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 - - |- - exec 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 }} - - exec 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 9afe938e56..69b44c60c3 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 }} @@ -101,9 +100,7 @@ spec: {{- end }} {{- if (and $root.Values.global.metrics.enabled $root.Values.global.metrics.enableGatewayMetrics) }} "prometheus.io/scrape": "true" - {{- if not (hasKey (default "" $defaults.annotations | fromYaml) "prometheus.io/path")}} "prometheus.io/path": "/metrics" - {{- end }} "prometheus.io/port": "20200" {{- end }} {{- if $defaults.annotations }} 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 41c0c2827e..455df2c60c 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..e13e2dc741 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml @@ -27,7 +27,6 @@ rules: - admissionregistration.k8s.io resources: - mutatingwebhookconfigurations - - validatingwebhookconfigurations verbs: - get - list diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index 45c87c9ceb..c71e5f414a 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -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: @@ -53,13 +52,10 @@ spec: image: {{ .Values.global.imageK8S }} name: webhook-cert-manager {{- include "consul.restrictedSecurityContext" . | nindent 8 }} + {{- with .Values.webhookCertManager.resources }} resources: - limits: - cpu: 100m - memory: 50Mi - requests: - cpu: 100m - memory: 50Mi + {{- toYaml . | nindent 12 }} + {{- end }} volumeMounts: - name: config mountPath: /bootstrap/config diff --git a/charts/consul/test/terraform/aks/main.tf b/charts/consul/test/terraform/aks/main.tf index f9dc36a51c..f15f1d106b 100644 --- a/charts/consul/test/terraform/aks/main.tf +++ b/charts/consul/test/terraform/aks/main.tf @@ -78,7 +78,7 @@ resource "azurerm_kubernetes_cluster" "default" { default_node_pool { name = "default" node_count = 3 - vm_size = "Standard_D3_v2" + vm_size = "Standard_D2_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/aks/variables.tf b/charts/consul/test/terraform/aks/variables.tf index 306532f85c..b60273bac2 100644 --- a/charts/consul/test/terraform/aks/variables.tf +++ b/charts/consul/test/terraform/aks/variables.tf @@ -7,7 +7,7 @@ variable "location" { } variable "kubernetes_version" { - default = "1.26" + default = "1.27" description = "Kubernetes version supported on AKS" } diff --git a/charts/consul/test/terraform/eks/main.tf b/charts/consul/test/terraform/eks/main.tf index 3bc8b40451..db667cf234 100644 --- a/charts/consul/test/terraform/eks/main.tf +++ b/charts/consul/test/terraform/eks/main.tf @@ -7,6 +7,7 @@ terraform { version = ">= 4.0.0" } } + } provider "aws" { @@ -80,7 +81,7 @@ module "eks" { max_capacity = 3 min_capacity = 3 - instance_type = "m5.xlarge" + instance_type = "m5.large" } } diff --git a/charts/consul/test/terraform/gke/main.tf b/charts/consul/test/terraform/gke/main.tf index 800aca5246..0594b9cf1c 100644 --- a/charts/consul/test/terraform/gke/main.tf +++ b/charts/consul/test/terraform/gke/main.tf @@ -24,11 +24,6 @@ data "google_container_engine_versions" "main" { version_prefix = "1.27." } -# 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 @@ -37,17 +32,13 @@ 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" + machine_type = "e2-standard-4" } - subnetwork = data.google_compute_subnetwork.subnet.name - resource_labels = var.labels - deletion_protection = false + 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..a0ffac907f 100644 --- a/charts/consul/test/terraform/gke/outputs.tf +++ b/charts/consul/test/terraform/gke/outputs.tf @@ -12,7 +12,3 @@ 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 -} diff --git a/charts/consul/test/terraform/gke/variables.tf b/charts/consul/test/terraform/gke/variables.tf index f33952850a..1eebe64145 100644 --- a/charts/consul/test/terraform/gke/variables.tf +++ b/charts/consul/test/terraform/gke/variables.tf @@ -37,9 +37,3 @@ 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/charts/consul/test/unit/api-gateway-controller-clusterrole.bats b/charts/consul/test/unit/api-gateway-controller-clusterrole.bats new file mode 100644 index 0000000000..f26fdfeebd --- /dev/null +++ b/charts/consul/test/unit/api-gateway-controller-clusterrole.bats @@ -0,0 +1,45 @@ +#!/usr/bin/env bats + +load _helpers + +@test "apiGateway/ClusterRole: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-controller-clusterrole.yaml \ + . +} + +@test "apiGateway/ClusterRole: enabled with apiGateway.enabled=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-clusterrole.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/ClusterRole: can use podsecuritypolicies with apiGateway.enabled=true and global.enablePodSecurityPolicies=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-clusterrole.yaml \ + --set 'global.enablePodSecurityPolicies=true' \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq '.rules[] | select((.resources[0] == "podsecuritypolicies") and (.verbs[0] == "use")) | length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/ClusterRole: can create roles and rolebindings with apiGateway.enabled=true and global.enablePodSecurityPolicies=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-clusterrole.yaml \ + --set 'global.enablePodSecurityPolicies=true' \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq '.rules[] | select((.resources[0] == "roles") and (.resources[1] == "rolebindings") and (.verbs | contains(["create","get","list","watch"]))) | length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} diff --git a/charts/consul/test/unit/api-gateway-controller-clusterrolebinding.bats b/charts/consul/test/unit/api-gateway-controller-clusterrolebinding.bats new file mode 100644 index 0000000000..3dfd94c36f --- /dev/null +++ b/charts/consul/test/unit/api-gateway-controller-clusterrolebinding.bats @@ -0,0 +1,22 @@ +#!/usr/bin/env bats + +load _helpers + +@test "apiGateway/ClusterRoleBinding: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-controller-clusterrolebinding.yaml \ + . +} + +@test "apiGateway/ClusterRoleBinding: enabled with global.enabled false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-clusterrolebinding.yaml \ + --set 'global.enabled=false' \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq -s 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} diff --git a/charts/consul/test/unit/api-gateway-controller-deployment.bats b/charts/consul/test/unit/api-gateway-controller-deployment.bats new file mode 100755 index 0000000000..696d5f7cbb --- /dev/null +++ b/charts/consul/test/unit/api-gateway-controller-deployment.bats @@ -0,0 +1,1754 @@ +#!/usr/bin/env bats + +load _helpers + +@test "apiGateway/Deployment: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + . +} + +@test "apiGateway/Deployment: fails if no image is set" { + cd `chart_dir` + run helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + . + [ "$status" -eq 1 ] + [[ "$output" =~ "apiGateway.image must be set to enable api gateway" ]] +} + +@test "apiGateway/Deployment: disable with apiGateway.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=false' \ + . +} + +@test "apiGateway/Deployment: disable with global.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'global.enabled=false' \ + . +} + +@test "apiGateway/Deployment: enable namespaces" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.enableConsulNamespaces=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | join(" ") | contains("-consul-destination-namespace=default")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: enable namespace mirroring" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'connectInject.consulNamespaces.mirroringK8S=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | join(" ") | contains("-mirroring-k8s=true")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: enable namespace mirroring prefixes" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'connectInject.consulNamespaces.mirroringK8S=true' \ + --set 'connectInject.consulNamespaces.mirroringK8SPrefix=foo' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | join(" ") | contains("-mirroring-k8s-prefix=foo")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: container image overrides" { + 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 '.spec.template.spec.containers[0].image' | tee /dev/stderr) + [ "${actual}" = "\"bar\"" ] +} + +@test "apiGateway/Deployment: SDS host set correctly" { + 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 '.spec.template.spec.containers[0].command | join(" ") | contains("-sds-server-host release-name-consul-api-gateway-controller.default.svc")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# nodeSelector + +@test "apiGateway/Deployment: nodeSelector is not set by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "null" ] +} + +@test "apiGateway/Deployment: specified nodeSelector" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'apiGateway.controller.nodeSelector=testing' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.nodeSelector' | tee /dev/stderr) + [ "${actual}" = "testing" ] +} + +#-------------------------------------------------------------------- +# global.tls.enabled + +@test "apiGateway/Deployment: Adds tls-ca-cert volume when global.tls.enabled is true" { + 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' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" != "" ] +} + +@test "apiGateway/Deployment: Adds tls-ca-cert volumeMounts when global.tls.enabled is true" { + 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' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${actual}" != "" ] +} + +@test "apiGateway/Deployment: can overwrite CA secret with the provided one" { + cd `chart_dir` + local ca_cert_volume=$(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.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 "apiGateway/Deployment: consul-auto-encrypt-ca-cert volume is added when TLS with auto-encrypt is enabled" { + 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' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name == "consul-auto-encrypt-ca-cert") | length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@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 \ + --set 'apiGateway.enabled=true' \ + --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 \ + -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' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[] | select(.name == "get-auto-encrypt-client-ca") | length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: adds both init containers when TLS with auto-encrypt and ACLs + namespaces are enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers | length == 3' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/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/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.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}" = "" ] +} + +#-------------------------------------------------------------------- +# global.acls.manageSystemACLs + +@test "apiGateway/Deployment: consul-logout preStop hook is added when ACLs are enabled" { + cd `chart_dir` + local object=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].lifecycle.preStop.exec.command[1]] | any(contains("logout"))' | tee /dev/stderr) + [ "${object}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_HTTP_TOKEN_FILE is not set when acls are disabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[1].name] | any(contains("CONSUL_HTTP_TOKEN_FILE"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "apiGateway/Deployment: CONSUL_HTTP_TOKEN_FILE is set when acls are enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[1].name] | any(contains("CONSUL_HTTP_TOKEN_FILE"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_LOGIN_DATACENTER is set when acls are enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[2].name] | any(contains("CONSUL_LOGIN_DATACENTER"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: init container is created when global.acls.manageSystemACLs=true" { + cd `chart_dir` + local object=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[1]' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.name' | tee /dev/stderr) + [ "${actual}" = "api-gateway-controller-acl-init" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("consul-k8s-control-plane acl-init"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '[.env[0].name] | any(contains("NAMESPACE"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '[.env[1].name] | any(contains("POD_NAME"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '[.env[2].name] | any(contains("CONSUL_LOGIN_META"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '[.env[2].value] | any(contains("component=api-gateway-controller,pod=$(NAMESPACE)/$(POD_NAME)"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + 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" ] +} + +@test "apiGateway/Deployment: init container is created when global.acls.manageSystemACLs=true and has correct command and environment with tls enabled" { + cd `chart_dir` + local object=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.consulAPITimeout=5s' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[] | select(.name == "api-gateway-controller-acl-init")' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.command | any(contains("consul-k8s-control-plane acl-init"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + 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[] | select(.name == "CONSUL_API_TIMEOUT") | [.value] | any(contains("5s"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + 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" ] +} + +@test "apiGateway/Deployment: init container is created when global.acls.manageSystemACLs=true and has correct command with Partitions enabled" { + cd `chart_dir` + local object=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.adminPartitions.name=default' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[] | select(.name == "api-gateway-controller-acl-init")' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.command | any(contains("consul-k8s-control-plane acl-init"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("-auth-method-name=release-name-consul-k8s-component-auth-method"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + 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[] | select(.name == "CONSUL_API_TIMEOUT") | [.value] | any(contains("5s"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_PARTITION") | [.value] | any(contains("default"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '.env[] | select(.name == "CONSUL_LOGIN_PARTITION") | [.value] | any(contains("default"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + 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" ] +} + +@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 \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'meshGateway.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.datacenter=dc2' \ + --set 'global.federation.enabled=true' \ + --set 'global.federation.primaryDatacenter=dc1' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[] | select(.name == "api-gateway-controller")' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.command | any(contains("consul-api-gateway server"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("-primary-datacenter=dc1"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: init container is created when global.acls.manageSystemACLs=true and has correct command 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 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.datacenter=dc2' \ + --set 'global.federation.enabled=true' \ + --set 'global.federation.primaryDatacenter=dc1' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[] | select(.name == "api-gateway-controller-acl-init")' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.command | any(contains("consul-k8s-control-plane acl-init"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.command | any(contains("-auth-method-name=release-name-consul-k8s-component-auth-method-dc2"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq '[.env[3].value] | any(contains("dc1"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: init container is created when global.acls.manageSystemACLs=true and has correct command and environment with tls enabled and autoencrypt enabled" { + cd `chart_dir` + local object=$(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 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[] | select(.name == "api-gateway-controller-acl-init")' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.command | any(contains("consul-k8s-control-plane acl-init"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + 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[] | select(.name == "CONSUL_API_TIMEOUT") | [.value] | any(contains("5s"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + 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" ] +} + +@test "apiGateway/Deployment: init container for copy consul is created when global.acls.manageSystemACLs=true" { + cd `chart_dir` + local object=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[] | select(.name == "copy-consul-bin")' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.command | any(contains("cp"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + local actual=$(echo $object | + yq -r '.volumeMounts[0] | any(contains("consul-bin"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: volumeMount for copy consul is created on container when global.acls.manageSystemACLs=true" { + cd `chart_dir` + local object=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[0] | any(contains("consul-bin"))' | tee /dev/stderr) + + [ "${object}" = "true" ] +} + +@test "apiGateway/Deployment: volume for copy consul is created when global.acls.manageSystemACLs=true" { + cd `chart_dir` + local object=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[0] | any(contains("consul-bin"))' | tee /dev/stderr) + + [ "${object}" = "true" ] +} + +@test "apiGateway/Deployment: auto-encrypt init container is created and is the first init-container when global.acls.manageSystemACLs=true and has correct command and environment with tls enabled and autoencrypt enabled" { + cd `chart_dir` + local object=$(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 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[1]' | tee /dev/stderr) + + local actual=$(echo $object | + yq -r '.name' | tee /dev/stderr) + [ "${actual}" = "get-auto-encrypt-client-ca" ] +} + +#-------------------------------------------------------------------- +# resources + +@test "apiGateway/Deployment: resources has default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].resources' | tee /dev/stderr) + + [ $(echo "${actual}" | yq -r '.requests.memory') = "100Mi" ] + [ $(echo "${actual}" | yq -r '.requests.cpu') = "100m" ] + [ $(echo "${actual}" | yq -r '.limits.memory') = "100Mi" ] + [ $(echo "${actual}" | yq -r '.limits.cpu') = "100m" ] +} + +@test "apiGateway/Deployment: resources can be overridden" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'apiGateway.resources.foo=bar' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].resources.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} + +#-------------------------------------------------------------------- +# init container resources + +@test "apiGateway/Deployment: init container has default resources" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.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 "apiGateway/Deployment: init container resources can be set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'apiGateway.initCopyConsulContainer.resources.requests.memory=memory' \ + --set 'apiGateway.initCopyConsulContainer.resources.requests.cpu=cpu' \ + --set 'apiGateway.initCopyConsulContainer.resources.limits.memory=memory2' \ + --set 'apiGateway.initCopyConsulContainer.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 "apiGateway/Deployment: no priorityClassName by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) + + [ "${actual}" = "null" ] +} + +@test "apiGateway/Deployment: can set a priorityClassName" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'apiGateway.controller.priorityClassName=name' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) + + [ "${actual}" = "name" ] +} + +#-------------------------------------------------------------------- +# logLevel + +@test "apiGateway/Deployment: logLevel info by default from global" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | 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 "apiGateway/Deployment: logLevel can be overridden" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'apiGateway.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" ] +} + +#-------------------------------------------------------------------- +# replicas + +@test "apiGateway/Deployment: replicas defaults to 1" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq '.spec.replicas' | tee /dev/stderr) + + [ "${actual}" = "1" ] +} + +@test "apiGateway/Deployment: replicas can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'apiGateway.controller.replicas=3' \ + . | tee /dev/stderr | + yq '.spec.replicas' | tee /dev/stderr) + + [ "${actual}" = "3" ] +} + + +#-------------------------------------------------------------------- +# get-auto-encrypt-client-ca + +@test "apiGateway/Deployment: get-auto-encrypt-client-ca uses server's stateful set address by default and passes ca cert" { + cd `chart_dir` + local command=$(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' \ + . | tee /dev/stderr | + yq '.spec.template.spec.initContainers[] | select(.name == "get-auto-encrypt-client-ca").command | join(" ")' | tee /dev/stderr) + + # check server address + actual=$(echo $command | jq ' . | contains("-server-addr=release-name-consul-server")') + [ "${actual}" = "true" ] + + # check server port + actual=$(echo $command | jq ' . | contains("-server-port=8501")') + [ "${actual}" = "true" ] + + # check server's CA cert + actual=$(echo $command | jq ' . | contains("-ca-file=/consul/tls/ca/tls.crt")') + [ "${actual}" = "true" ] + + # check consul-api-timeout + actual=$(echo $command | jq ' . | contains("-consul-api-timeout=5s")') + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# Vault + +@test "apiGateway/Deployment: vault CA is not configured by default" { + cd `chart_dir` + local object=$(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 '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 "apiGateway/Deployment: vault CA is not configured when secretName is set but secretKey is not" { + cd `chart_dir` + local object=$(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 '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 "apiGateway/Deployment: vault CA is not configured when secretKey is set but secretName is not" { + cd `chart_dir` + local object=$(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 '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 "apiGateway/Deployment: vault CA is configured when both secretName and secretKey are set" { + cd `chart_dir` + local object=$(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 '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 "apiGateway/Deployment: vault tls annotations are set when tls is enabled" { + 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.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 "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 \ + -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 '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" ] +} + +#-------------------------------------------------------------------- +# global.cloud + +@test "apiGateway/Deployment: fails when global.cloud.enabled is true and global.cloud.clientId.secretName is not set but global.cloud.clientSecret.secretName and global.cloud.resourceId.secretName is set" { + cd `chart_dir` + run 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 '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 "apiGateway/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/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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 "apiGateway/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/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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 "apiGateway/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/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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 "apiGateway/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/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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 "apiGateway/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/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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 "apiGateway/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/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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 "apiGateway/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/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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 "apiGateway/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/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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 "apiGateway/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/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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." ]] +} + +#-------------------------------------------------------------------- +# CONSUL_HTTP_SSL + +@test "apiGateway/Deployment: CONSUL_HTTP_SSL set correctly when not using TLS." { + 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=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[2].value' | tee /dev/stderr) + [ "${actual}" = "\"false\"" ] +} + +@test "apiGateway/Deployment: CONSUL_HTTP_SSL set correctly when using TLS." { + 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' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[3].value' | tee /dev/stderr) + [ "${actual}" = "\"true\"" ] +} + +#-------------------------------------------------------------------- +# CONSUL_HTTP_ADDR + +@test "apiGateway/Deployment: CONSUL_HTTP_ADDR set correctly with external servers, TLS, and no clients." { + 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 'server.enabled=false' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[2].value] | any(contains("external-consul.host:8501"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_HTTP_ADDR set correctly with external servers, no TLS, and no clients" { + 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=false' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.httpsPort=8500' \ + --set 'server.enabled=false' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[1].value] | any(contains("external-consul.host:8500"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_HTTP_ADDR set correctly with local servers, TLS, and clients" { + 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 'client.enabled=true' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[2].value] | any(contains("$(HOST_IP):8501"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_HTTP_ADDR set correctly with local servers, no TLS, and clients" { + 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=false' \ + --set 'client.enabled=true' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[1].value] | any(contains("$(HOST_IP):8500"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_HTTP_ADDR set correctly with local servers, TLS, and no clients" { + 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 'client.enabled=false' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[2].value] | any(contains("release-name-consul-server:8501"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_HTTP_ADDR set correctly with local servers, no TLS, and no clients" { + 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=false' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq '[.spec.template.spec.containers[0].env[1].value] | any(contains("release-name-consul-server:8500"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# externalServers tlsServerName + +@test "apiGateway/Deployment: CONSUL_TLS_SERVER_NAME can be set for externalServers" { + 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 'server.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[4].value == "hashi"' | tee /dev/stderr) + [ "${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 + +@test "apiGateway/Deployment: CONSUL_PARTITION is set when using admin partitions" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.adminPartitions.name=hashi' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[3].value == "hashi"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_LOGIN_PARTITION is set when using admin partitions with ACLs" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.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[0].env[6].value == "hashi"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_DYNAMIC_SERVER_DISCOVERY is set when not using clients" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].env[3].value == "true"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Deployment: CONSUL_DYNAMIC_SERVER_DISCOVERY is not set when using clients" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-deployment.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=bar' \ + --set 'client.enabled=true' \ + . | tee /dev/stderr | + 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 'global.tls.enabled=true' \ + --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}" = "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 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 \ + -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' \ + --set 'externalServers.useSystemRoots=true' \ + . | tee /dev/stderr | + 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].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) + [ "${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 \ + -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].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 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 \ + -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"' ] +} + +#-------------------------------------------------------------------- +# 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/api-gateway-controller-podsecuritypolicy.bats b/charts/consul/test/unit/api-gateway-controller-podsecuritypolicy.bats new file mode 100644 index 0000000000..dfd40c793f --- /dev/null +++ b/charts/consul/test/unit/api-gateway-controller-podsecuritypolicy.bats @@ -0,0 +1,22 @@ +#!/usr/bin/env bats + +load _helpers + +@test "apiGateway/PodSecurityPolicy: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-controller-podsecuritypolicy.yaml \ + . +} + +@test "apiGateway/PodSecurityPolicy: enabled with apiGateway.enabled=true and global.enablePodSecurityPolicies=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-podsecuritypolicy.yaml \ + --set 'global.enablePodSecurityPolicies=true' \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} diff --git a/charts/consul/test/unit/api-gateway-controller-service.bats b/charts/consul/test/unit/api-gateway-controller-service.bats new file mode 100755 index 0000000000..47cb7ff9aa --- /dev/null +++ b/charts/consul/test/unit/api-gateway-controller-service.bats @@ -0,0 +1,30 @@ +#!/usr/bin/env bats + +load _helpers + +@test "apiGateway/Service: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-controller-service.yaml \ + . +} + +@test "apiGateway/Service: enable with apiGateway.enabled set to true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-service.yaml \ + --set 'global.enabled=false' \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/Service: disable with apiGateway.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-controller-service.yaml \ + --set 'apiGateway.enabled=false' \ + . +} diff --git a/charts/consul/test/unit/api-gateway-controller-serviceaccount.bats b/charts/consul/test/unit/api-gateway-controller-serviceaccount.bats new file mode 100644 index 0000000000..22486799b2 --- /dev/null +++ b/charts/consul/test/unit/api-gateway-controller-serviceaccount.bats @@ -0,0 +1,76 @@ +#!/usr/bin/env bats + +load _helpers + +@test "apiGateway/ServiceAccount: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-controller-serviceaccount.yaml \ + . +} + +@test "apiGateway/ServiceAccount: enabled with apiGateway.enabled true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-serviceaccount.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq -s 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/ServiceAccount: disabled with apiGateway.enabled false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-controller-serviceaccount.yaml \ + --set 'apiGateway.enabled=false' \ + . +} +#-------------------------------------------------------------------- +# global.imagePullSecrets + +@test "apiGateway/ServiceAccount: can set image pull secrets" { + cd `chart_dir` + local object=$(helm template \ + -s templates/api-gateway-controller-serviceaccount.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --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" ] +} + +#-------------------------------------------------------------------- +# apiGateway.serviceAccount.annotations + +@test "apiGateway/ServiceAccount: no annotations by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-serviceaccount.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq '.metadata.annotations | length > 0' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "apiGateway/ServiceAccount: annotations when enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-controller-serviceaccount.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set "apiGateway.serviceAccount.annotations=foo: bar" \ + . | tee /dev/stderr | + yq -r '.metadata.annotations.foo' | tee /dev/stderr) + [ "${actual}" = "bar" ] +} diff --git a/charts/consul/test/unit/api-gateway-gatewayclass.bats b/charts/consul/test/unit/api-gateway-gatewayclass.bats new file mode 100755 index 0000000000..c79753c2f3 --- /dev/null +++ b/charts/consul/test/unit/api-gateway-gatewayclass.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load _helpers + +@test "apiGateway/GatewayClass: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-gatewayclass.yaml \ + . +} + +@test "apiGateway/GatewayClass: enable with global.enabled false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclass.yaml \ + --set 'global.enabled=false' \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClass: disable with apiGateway.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-gatewayclass.yaml \ + --set 'apiGateway.enabled=false' \ + . +} + +@test "apiGateway/GatewayClass: disable with global.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-gatewayclass.yaml \ + --set 'global.enabled=false' \ + . +} + +@test "apiGateway/GatewayClass: disable with apiGateway.managedGatewayClass.enabled" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-gatewayclass.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'apiGateway.managedGatewayClass.enabled=false' \ + . +} diff --git a/charts/consul/test/unit/api-gateway-gatewayclassconfig.bats b/charts/consul/test/unit/api-gateway-gatewayclassconfig.bats new file mode 100644 index 0000000000..742f31afa0 --- /dev/null +++ b/charts/consul/test/unit/api-gateway-gatewayclassconfig.bats @@ -0,0 +1,186 @@ +#!/usr/bin/env bats + +load _helpers + +@test "apiGateway/GatewayClassConfig: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + . +} + +@test "apiGateway/GatewayClassConfig: enabled with apiGateway.enabled=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClassConfig: deployment config disabled by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + . | tee /dev/stderr | + yq '.spec | has("deployment") | not' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClassConfig: deployment config enabled with defaultInstances=3" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'apiGateway.managedGatewayClass.deployment.defaultInstances=3' \ + . | tee /dev/stderr | + yq '.spec.deployment.defaultInstances == 3' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClassConfig: deployment config enabled with maxInstances=3" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'apiGateway.managedGatewayClass.deployment.maxInstances=3' \ + . | tee /dev/stderr | + yq '.spec.deployment.maxInstances == 3' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClassConfig: deployment config enabled with minInstances=3" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'apiGateway.managedGatewayClass.deployment.minInstances=3' \ + . | tee /dev/stderr | + yq '.spec.deployment.minInstances == 3' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClassConfig: imageEnvoy can be set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'apiGateway.imageEnvoy=bar' \ + . | tee /dev/stderr | + yq '.spec.image.envoy' | tee /dev/stderr) + [ "${actual}" = "\"bar\"" ] +} + +#-------------------------------------------------------------------- +# Consul server address + +@test "apiGateway/GatewayClassConfig: Consul server address set with external servers and no clients." { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'server.enabled=false' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.consul.address == "external-consul.host"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClassConfig: Consul server address set with external servers and clients." { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'server.enabled=false' \ + --set 'client.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.consul.address == "$(HOST_IP)"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClassConfig: Consul server address set with local servers and no clients." { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'client.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.consul.address == "release-name-consul-server.default.svc"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "apiGateway/GatewayClassConfig: Consul server address set with local servers and clients." { + cd `chart_dir` + local actual=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'client.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.consul.address == "$(HOST_IP)"' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# externalServers ports + +@test "apiGateway/GatewayClassConfig: ports for externalServers when not using TLS." { + cd `chart_dir` + local ports=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.tls.enabled=false' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.grpcPort=1234' \ + --set 'externalServers.httpsPort=5678' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.consul.ports' | tee /dev/stderr) + + local actual + actual=$(echo $ports | jq -r '.grpc' | tee /dev/stderr) + [ "${actual}" = "1234" ] + + actual=$(echo $ports | jq -r '.http' | tee /dev/stderr) + [ "${actual}" = "5678" ] +} + +@test "apiGateway/GatewayClassConfig: ports for externalServers when using TLS." { + cd `chart_dir` + local ports=$(helm template \ + -s templates/api-gateway-gatewayclassconfig.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ + --set 'global.tls.enabled=true' \ + --set 'externalServers.enabled=true' \ + --set 'externalServers.hosts[0]=external-consul.host' \ + --set 'externalServers.grpcPort=1234' \ + --set 'externalServers.httpsPort=5678' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq '.spec.consul.ports' | tee /dev/stderr) + + local actual + actual=$(echo $ports | jq -r '.grpc' | tee /dev/stderr) + [ "${actual}" = "1234" ] + + actual=$(echo $ports | jq -r '.http' | tee /dev/stderr) + [ "${actual}" = "5678" ] +} diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index 00fb346e26..3b09fd783b 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}" = "{}" ] } @@ -588,19 +584,6 @@ load _helpers [ "${actual}" = "/v1/agent/metrics" ] } -@test "client/DaemonSet: when global.metrics.enableAgentMetrics=true, and client annotation for prometheus path is specified, it uses the client annotation rather than default." { - cd `chart_dir` - local actual=$(helm template \ - -s templates/client-daemonset.yaml \ - --set 'client.enabled=true' \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'client.annotations=prometheus.io/path: /anew/path' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations."prometheus.io/path"' | tee /dev/stderr) - [ "${actual}" = "/anew/path" ] -} - @test "client/DaemonSet: when global.metrics.enableAgentMetrics=true, sets telemetry flag" { cd `chart_dir` local actual=$(helm template \ @@ -2741,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}" = "{}" ] } @@ -2824,6 +2800,8 @@ rollingUpdate: cd `chart_dir` run helm template \ -s templates/client-daemonset.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ --set 'global.tls.enabled=true' \ --set 'global.tls.enableAutoEncrypt=true' \ --set 'global.datacenter=dc-foo' \ @@ -2842,6 +2820,8 @@ rollingUpdate: cd `chart_dir` run helm template \ -s templates/client-daemonset.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ --set 'global.tls.enabled=true' \ --set 'global.tls.enableAutoEncrypt=true' \ --set 'global.datacenter=dc-foo' \ @@ -2861,6 +2841,8 @@ rollingUpdate: cd `chart_dir` run helm template \ -s templates/client-daemonset.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ --set 'global.tls.enabled=true' \ --set 'global.tls.enableAutoEncrypt=true' \ --set 'global.datacenter=dc-foo' \ @@ -2883,6 +2865,8 @@ rollingUpdate: cd `chart_dir` run helm template \ -s templates/client-daemonset.yaml \ + --set 'apiGateway.enabled=true' \ + --set 'apiGateway.image=foo' \ --set 'global.tls.enabled=true' \ --set 'global.tls.enableAutoEncrypt=true' \ --set 'global.datacenter=dc-foo' \ diff --git a/charts/consul/test/unit/connect-inject-clusterrole.bats b/charts/consul/test/unit/connect-inject-clusterrole.bats index cfe64337d9..4acdf211d2 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" { @@ -197,7 +197,7 @@ load _helpers --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 +217,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 a25bcfeee7..0495789b17 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -1315,33 +1315,6 @@ load _helpers [ "${actual}" = "true" ] } -@test "connectInject/Deployment: by default sidecar proxy lifecycle management startup grace period is set to 0 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-startup-grace-period-seconds=0"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "connectInject/Deployment: sidecar proxy lifecycle management startup 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.defaultStartupGracePeriodSeconds=13' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-default-sidecar-proxy-lifecycle-startup-grace-period-seconds=13"))' | 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 \ @@ -1396,33 +1369,6 @@ load _helpers [ "${actual}" = "true" ] } -@test "connectInject/Deployment: by default sidecar proxy lifecycle management graceful startup path is set to /graceful_startup" { - 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-startup-path=\"/graceful_startup\""))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "connectInject/Deployment: sidecar proxy lifecycle management graceful startup 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.defaultGracefulStartupPath=/start' \ - . | 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-startup-path=\"/start\""))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - #-------------------------------------------------------------------- # priorityClassName @@ -1515,10 +1461,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}" = "{}" ] } @@ -2274,12 +2217,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}" = "{}" ] } @@ -2738,58 +2676,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 ea3decc5c7..0000000000 --- a/charts/consul/test/unit/gateway-resources-configmap.bats +++ /dev/null @@ -1,477 +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' ] -} - - -#-------------------------------------------------------------------- -# API Gateway logLevel configuration - -@test "gateway-resources/ConfigMap: API Gateway logLevel default configuration" { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'meshGateway.enabled=false' \ - --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" | yq -r '.["consul.hashicorp.com/gateway-wan-address-source"]') - [ "${actual}" = 'Service' ] - - local actual=$(echo "$annotations" | yq -r '.["consul.hashicorp.com/gateway-wan-port"]') - [ "${actual}" = '443' ] - - local actual=$(echo "$annotations" | yq -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" | yq -r '.["consul.hashicorp.com/gateway-wan-address-source"]') - [ "${actual}" = 'Service' ] - - local actual=$(echo "$annotations" | yq -r '.["consul.hashicorp.com/gateway-wan-port"]') - [ "${actual}" = '30000' ] - - local actual=$(echo "$annotations" | yq -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" | yq -r '.["consul.hashicorp.com/gateway-wan-address-source"]') - [ "${actual}" = 'Static' ] - - local actual=$(echo "$annotations" | yq -r '.["consul.hashicorp.com/gateway-wan-port"]') - [ "${actual}" = '443' ] - - local actual=$(echo "$annotations" | yq -r '.["consul.hashicorp.com/gateway-wan-address-static"]') - [ "${actual}" = '127.0.0.1' ] -} - -#-------------------------------------------------------------------- -# API Gateway Tests mapPrivilageContainerPorts - -@test "gateway-resources/ConfigMap: API Gateway mapPrivilageContainerPorts empty by default { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.container.portModifier') - - [ "${actual}" = 'null' ] - - local actual=$(echo "$config" | yq -r '.initContainer.portModifier') - - [ "${actual}" = 'null' ] -} - - -@test "gateway-resources/ConfigMap: API Gateway mapPrivilageContainerPorts overrides default { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - --set 'connectInject.apiGateway.managedGatewayClass.mapPrivilegedContainerPorts=80' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.container.portModifier') - - [ "${actual}" = '80' ] - - local actual=$(echo "$config" | yq -r '.initContainer.portModifier') - - [ "${actual}" = '80' ] -} - -#-------------------------------------------------------------------- -# API Gateway Tests deployment replicas - -@test "gateway-resources/ConfigMap: API Gateway deploymentConfig overrides default { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - --set 'connectInject.apiGateway.managedGatewayClass.deployment.defaultInstances=2' \ - --set 'connectInject.apiGateway.managedGatewayClass.deployment.maxInstances=3' \ - --set 'connectInject.apiGateway.managedGatewayClass.deployment.minInstances=1' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.replicas.default') - [ "${actual}" = '2' ] - - local actual=$(echo "$config" | yq -r '.replicas.min') - [ "${actual}" = '1' ] - - local actual=$(echo "$config" | yq -r '.replicas.max') - [ "${actual}" = '3' ] -} - -@test "gateway-resources/ConfigMap: API Gateway deploymentConfig default { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.replicas.default') - [ "${actual}" = '1' ] - - local actual=$(echo "$config" | yq -r '.replicas.min') - [ "${actual}" = '1' ] - - local actual=$(echo "$config" | yq -r '.replicas.max') - [ "${actual}" = '1' ] -} - -#-------------------------------------------------------------------- -# API Gateway Tests nodeSelector - -@test "gateway-resources/ConfigMap: API Gateway nodeSelector overrides default { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - --set 'connectInject.apiGateway.managedGatewayClass.nodeSelector=- key: value' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.nodeSelector[0].key') - echo ${actual} - - [ "${actual}" = 'value' ] -} - -@test "gateway-resources/ConfigMap: API Gateway nodeSelector default { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.nodeSelector') - [ "${actual}" = 'null' ] -} - -#-------------------------------------------------------------------- -# API Gateway Tests tolerations - -@test "gateway-resources/ConfigMap: API Gateway tolerations overrides default { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - --set 'connectInject.apiGateway.managedGatewayClass.tolerations=- key: value' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.tolerations[0].key') - echo "${actual}" - - [ "${actual}" = 'value' ] -} - - - -@test "gateway-resources/ConfigMap: API Gateway tolerations default { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.deployment' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.tolerations') - [ "${actual}" = 'null' ] -} - - -#-------------------------------------------------------------------- -# API Gateway Tests copyAnnotations - -@test "gateway-resources/ConfigMap: API Gateway copyAnnotations overrides default { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - --set 'connectInject.apiGateway.managedGatewayClass.copyAnnotations.service.annotations=- annotation.name' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.annotations' | tee /dev/stderr) - - local actual=$(echo "$config" | yq -r '.service[0]') - echo "${actual}" - [ "${actual}" = 'annotation.name' ] -} - -@test "gateway-resources/ConfigMap: API Gateway copyAnnotations default { - cd `chart_dir` - local config=$(helm template \ - -s $target \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.logLevel=error' \ - . | tee /dev/stderr | - yq -r '.data["config.yaml"]' | yq -r '.gatewayClassConfigs[0].spec.annotations' | tee /dev/stderr) - - local actual=$(echo "$config" | jq -r '.service') - [ "${actual}" = 'null' ] -} - - -#-------------------------------------------------------------------- -# TODO openShiftSSCName \ No newline at end of file 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 32173838fe..0000000000 --- a/charts/consul/test/unit/gateway-resources-job.bats +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -target=templates/gateway-resources-job.yaml - -@test "gatewayresources/Job: fails if .values.apiGateway is set" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'apiGateway.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "[DEPRECATED and REMOVED] the apiGateway stanza is no longer supported as of Consul 1.19.0. Use connectInject.apiGateway instead." ]] -} - -@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" ] -} - -#-------------------------------------------------------------------- -# 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 4e33b91886..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,130 +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." ]] -} diff --git a/charts/consul/test/unit/ingress-gateways-deployment.bats b/charts/consul/test/unit/ingress-gateways-deployment.bats index 2f75996ca9..be617ac538 100644 --- a/charts/consul/test/unit/ingress-gateways-deployment.bats +++ b/charts/consul/test/unit/ingress-gateways-deployment.bats @@ -300,19 +300,6 @@ load _helpers [ "${actual}" = "/metrics" ] } -@test "ingressGateways/Deployment: when global.metrics.enabled=true, and ingress gateways annotation for prometheus path is specified, it uses the specified annotation rather than default." { - cd `chart_dir` - local actual=$(helm template \ - -s templates/ingress-gateways-deployment.yaml \ - --set 'ingressGateways.enabled=true' \ - --set 'connectInject.enabled=true' \ - --set 'global.metrics.enabled=true' \ - --set 'ingressGateways.defaults.annotations=prometheus.io/path: /anew/path' \ - . | tee /dev/stderr | - yq -s -r '.[0].spec.template.metadata.annotations."prometheus.io/path"' | tee /dev/stderr) - [ "${actual}" = "/anew/path" ] -} - @test "ingressGateways/Deployment: when global.metrics.enableGatewayMetrics=false, does not set proxy setting" { cd `chart_dir` local object=$(helm template \ @@ -812,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" { @@ -827,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" ] @@ -849,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" ] @@ -872,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" ] @@ -1159,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 6c449f7f03..b044b7c9ab 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" ] } #-------------------------------------------------------------------- @@ -99,19 +99,6 @@ key2: value2' \ [ "${actual}" = "/metrics" ] } -@test "meshGateway/Deployment: when global.metrics.enabled=true, and mesh gateways annotation for prometheus path is specified, it uses the specified annotation rather than default." { - cd `chart_dir` - local actual=$(helm template \ - -s templates/mesh-gateway-deployment.yaml \ - --set 'meshGateway.enabled=true' \ - --set 'connectInject.enabled=true' \ - --set 'global.metrics.enabled=true' \ - --set 'meshGateway.annotations=prometheus.io/path: /anew/path' \ - . | tee /dev/stderr | - yq -s -r '.[0].spec.template.metadata.annotations."prometheus.io/path"' | tee /dev/stderr) - [ "${actual}" = "/anew/path" ] -} - @test "meshGateway/Deployment: when global.metrics.enableGatewayMetrics=false, does not set annotations" { cd `chart_dir` local object=$(helm template \ @@ -1428,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 99fc6b9a9e..a9873a8e61 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -1081,7 +1081,6 @@ 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", @@ -2357,11 +2356,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,85 +2406,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" ] -} - -#-------------------------------------------------------------------- -# global.metrics.datadog - -@test "serverACLInit/Job: -create-dd-agent-token not set when datadog=false and manageSystemACLs=true" { - cd `chart_dir` - local command=$(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 "$command" | - yq 'any(contains("-create-dd-agent-token"))' | tee /dev/stderr) - [ "${actual}" = "false" ] -} - -@test "serverACLInit/Job: -create-dd-agent-token set when global.metrics.datadog=true and global.acls.manageSystemACLs=true" { - cd `chart_dir` - local command=$(helm template \ - -s templates/server-acl-init-job.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) - - local actual=$( echo "$command" | - yq 'any(contains("-create-dd-agent-token"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "serverACLInit/Job: -create-dd-agent-token NOT set when global.metrics.datadog=true, global.metrics.datadog.dogstatsd.enabled=true, and global.acls.manageSystemACLs=true" { - cd `chart_dir` - local command=$(helm template \ - -s templates/server-acl-init-job.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.enabled=true' \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) - - local actual=$( echo "$command" | - yq 'any(contains("-create-dd-agent-token"))' | tee /dev/stderr) - [ "${actual}" = "false" ] -} \ No newline at end of file diff --git a/charts/consul/test/unit/server-config-configmap.bats b/charts/consul/test/unit/server-config-configmap.bats index 45b8f09518..f0044c04e5 100755 --- a/charts/consul/test/unit/server-config-configmap.bats +++ b/charts/consul/test/unit/server-config-configmap.bats @@ -1033,98 +1033,6 @@ 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 @@ -1210,10 +1118,10 @@ load _helpers --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].rotate_max_files=20' \ --set 'server.auditLogs.sinks[0].rotate_bytes=12455355' \ + --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) @@ -1232,17 +1140,17 @@ 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].path=/tmp/audit.json' \ --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_duration=24h' \ + --set 'server.auditLogs.sinks[1].path=/tmp/audit-2.json' \ --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' \ --set 'server.auditLogs.sinks[2].format=json' \ @@ -1256,19 +1164,19 @@ load _helpers 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.MySink1.rotate_max_files | tee /dev/stderr) + [ ${actual} = 15 ] + 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.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" ] @@ -1308,275 +1216,3 @@ load _helpers [ "${configmap}" = "DEBUG" ] } - -#-------------------------------------------------------------------- -# Datadog - -@test "server/ConfigMap: when global.metrics.datadog.enabled=true, sets default telemetry.dogstatsd_addr config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.enabled=true' \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.dogstatsd_addr | tee /dev/stderr) - - [ "${actual}" = "unix:///var/run/datadog/dsd.socket" ] -} - -@test "server/ConfigMap: when global.metrics.datadog.enabled=true, sets non-default telemetry.dogstatsd_addr config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.socketTransportType="UDP"' \ - --set 'global.metrics.datadog.dogstatsd.dogstatsdAddr="datadog-agent.default.svc.cluster.local"' \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.dogstatsd_addr | tee /dev/stderr) - - [ "${actual}" = "datadog-agent.default.svc.cluster.local" ] -} - -@test "server/ConfigMap: when global.metrics.datadog.enabled=true, sets non-default namespace telemetry.dogstatsd_addr with non-default port config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.socketTransportType="UDP"' \ - --set 'global.metrics.datadog.dogstatsd.dogstatsdAddr="127.0.0.1"' \ - --set 'global.metrics.datadog.dogstatsd.dogstatsdPort=8000' \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.dogstatsd_addr | tee /dev/stderr) - - [ "${actual}" = "127.0.0.1:8000" ] -} - -@test "server/ConfigMap: when global.metrics.datadog.enabled=true, sets default telemetry.dogstatsd_tags config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.enabled=true' \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.dogstatsd_tags | jq -r '[ .[] ]| join (" ")' | tee /dev/stderr) - - [ "${actual}" = "source:consul consul_service:consul-server" ] -} - -@test "server/ConfigMap: when global.metrics.datadog.enabled=true, sets non-default telemetry.dogstatsd_tags config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.dogstatsdTags'='[\"source:consul-dataplane\"\,\"service:consul-server-connection-manager\"]' \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.dogstatsd_tags | jq -r '[ .[] ]| join (" ")' | tee /dev/stderr) - - [ "${actual}" = "source:consul-dataplane service:consul-server-connection-manager" ] -} - -#-------------------------------------------------------------------- -# Consul Agent Metrics Prefix Filtering - -@test "server/ConfigMap: when global.metrics.prefixFilter default, empty telemetry.prefix_filter string list" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.prefix_filter | jq -r '[ .[] ]| join (" ")' | tee /dev/stderr) - - [ "${actual}" = "" ] -} - -@test "server/ConfigMap: when global.metrics.prefixFilter.allowList, sets correctly prepended telemetry.prefix_filter string list" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.prefixFilter.allowList'={'"consul.rpc.server.call"'\,'"consul.grpc.server.call"'} \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.prefix_filter | jq -r '[ .[] ]| join (" ")' | tee /dev/stderr) - - [ "${actual}" = "+consul.rpc.server.call +consul.grpc.server.call" ] -} - -@test "server/ConfigMap: when global.metrics.prefixFilter.blockList, sets correctly prepended telemetry.prefix_filter string list" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.prefixFilter.blockList'={'"consul.rpc.server.call"'\,'"consul.grpc.server.call"'} \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.prefix_filter | jq -r '[ .[] ]| join (" ")' | tee /dev/stderr) - - [ "${actual}" = "-consul.rpc.server.call -consul.grpc.server.call" ] -} - -@test "server/ConfigMap: when global.metrics.prefixFilter.blockList and allowList, sets correctly prepended telemetry.prefix_filter string list" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.prefixFilter.allowList'={'"consul.rpc.server.call"'\,'"consul.http.GET"'} \ - --set 'global.metrics.prefixFilter.blockList'={'"consul.http"'\,'"consul.raft.apply"'} \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.prefix_filter | jq -r '[ .[] ]| join (" ")' | tee /dev/stderr) - - [ "${actual}" = "+consul.rpc.server.call +consul.http.GET -consul.http -consul.raft.apply" ] -} - -#-------------------------------------------------------------------- -# Consul Agent Debug (PPROF) - -@test "server/ConfigMap: global.server.enableAgentDebug default, sets default enable_debug = false in server agent config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .enable_debug | tee /dev/stderr) - - [ "${actual}" = "false" ] -} - -@test "server/ConfigMap: when global.server.enableAgentDebug=true, sets enable_debug = true in server agent config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'server.enableAgentDebug=true' \ - . | tee /dev/stderr | - yq -r '.data["server.json"]' | jq -r .enable_debug | tee /dev/stderr) - - [ "${actual}" = "true" ] -} - -#-------------------------------------------------------------------- -# Consul Agent Telemetry Host Metrics - -@test "server/ConfigMap: when global.metrics.enableHostMetrics is default, telemetry.enable_host_metrics = false in agent config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.enable_host_metrics | tee /dev/stderr) - - [ "${actual}" = "false" ] -} - -@test "server/ConfigMap: when global.metrics.enableHostMetrics=true, sets telemetry.enable_host_metrics = true in agent config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.enableHostMetrics=true' \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.enable_host_metrics | tee /dev/stderr) - - [ "${actual}" = "true" ] -} - -#-------------------------------------------------------------------- -# Consul Agent Telemetry Hostname Disable - -@test "server/ConfigMap: when global.metrics.disableAgentHostName is default, telemetry.disableAgentHostName = false in agent config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.enable_host_metrics | tee /dev/stderr) - - [ "${actual}" = "false" ] -} - -@test "server/ConfigMap: when global.metrics.disableAgentHostName=true, sets telemetry.disableAgentHostName = true in agent config" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-config-configmap.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.enableHostMetrics=true' \ - . | tee /dev/stderr | - yq -r '.data["telemetry-config.json"]' | jq -r .telemetry.enable_host_metrics | tee /dev/stderr) - - [ "${actual}" = "true" ] -} - -#-------------------------------------------------------------------- -# 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 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 3ef40a3141..587402c82f 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -694,11 +694,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}" = "{}" ] } @@ -748,19 +744,6 @@ load _helpers [ "${actual}" = "/v1/agent/metrics" ] } -@test "server/Statefulset: when global.metrics.enabled=true, and server annotation for prometheus path is specified, it uses the specified annotation rather than default." { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'server.annotations=prometheus.io/path: /anew/path' \ - . | tee /dev/stderr | - yq -s -r '.[0].spec.template.metadata.annotations."prometheus.io/path"' | tee /dev/stderr) - [ "${actual}" = "/anew/path" ] -} - - @test "server/StatefulSet: when global.metrics.enableAgentMetrics=true, adds prometheus scheme=http annotation" { cd `chart_dir` local actual=$(helm template \ @@ -796,324 +779,6 @@ load _helpers [ "${actual}" = "https" ] } -@test "server/StatefulSet: when global.metrics.datadog.enabled=true, adds ad.datadoghq.com annotations" { - cd `chart_dir` - local annotations=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations' | tee /dev/stderr) - - local actual=$(echo "$annotations" | \ - yq -r '."ad.datadoghq.com/tolerate-unready"' | tee /dev/stderr) - [ "${actual}" = "true" ] - - local actual=$(echo "$annotations" | \ - yq -r '."ad.datadoghq.com/consul.logs"' | tee /dev/stderr) - [ "${actual}" = '[{"source": "consul","consul_service": "consul-server"}]' ] - - local consul_checks=$(echo "$annotations" | \ - yq -r '."ad.datadoghq.com/consul.checks"' | tee /dev/stderr) - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.init_config | tee /dev/stderr)" - [ "${actual}" = "{}" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].url | tee /dev/stderr)" - [ "${actual}" = "http://consul-server.consul.svc:8500" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].new_leader_checks | tee /dev/stderr)" - [ "${actual}" = "true" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].catalog_checks | tee /dev/stderr)" - [ "${actual}" = "true" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].auth_type | tee /dev/stderr)" - [ "${actual}" = "basic" ] -} - -@test "server/StatefulSet: when global.metrics.datadog.enabled=true and global.tls.enabled, adds tls altered ad.datadoghq.com annotations" { - cd `chart_dir` - local annotations=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.tls.enabled=true' \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations' | tee /dev/stderr) - - local actual=$(echo "$annotations" | \ - yq -r '."ad.datadoghq.com/tolerate-unready"' | tee /dev/stderr) - [ "${actual}" = "true" ] - - local actual=$(echo "$annotations" | \ - yq -r '."ad.datadoghq.com/consul.logs"' | tee /dev/stderr) - [ "${actual}" = '[{"source": "consul","consul_service": "consul-server"}]' ] - - local consul_checks=$(echo "$annotations" | \ - yq -r '."ad.datadoghq.com/consul.checks"' | tee /dev/stderr) - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.init_config | tee /dev/stderr)" - [ "${actual}" = "{}" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].url | tee /dev/stderr)" - [ "${actual}" = "https://consul-server.default.svc:8501" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].tls_cert | tee /dev/stderr)" - [ "${actual}" = "/etc/datadog-agent/conf.d/consul.d/certs/tls.crt" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].tls_private_key | tee /dev/stderr)" - [ "${actual}" = "/etc/datadog-agent/conf.d/consul.d/certs/tls.key" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].tls_ca_cert | tee /dev/stderr)" - [ "${actual}" = "/etc/datadog-agent/conf.d/consul.d/ca/tls.crt" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].new_leader_checks | tee /dev/stderr)" - [ "${actual}" = "true" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].catalog_checks | tee /dev/stderr)" - [ "${actual}" = "true" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].auth_type | tee /dev/stderr)" - [ "${actual}" = "basic" ] -} - -@test "server/StatefulSet: when global.metrics.datadog.enabled=true and global.acls.manageSystemACLs=true, adds ad.datadoghq.com annotations for datadog-agent-metrics-acl-token secret rendering" { - cd `chart_dir` - local annotations=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations' | tee /dev/stderr) - - local consul_checks=$(echo "$annotations" | \ - yq -r '."ad.datadoghq.com/consul.checks"' | tee /dev/stderr) - - local actual="$( echo "$consul_checks" | \ - jq -r .consul.instances | jq -r .[0].acl_token | tee /dev/stderr)" - [ "${actual}" = "ENC[k8s_secret@default/default-datadog-agent-metrics-acl-token/token]" ] -} - -@test "server/StatefulSet: when global.metrics.datadog.openMetricsPrometheus.enabled, applicable openmetrics annotation is set" { - cd `chart_dir` - local annotations=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.openMetricsPrometheus.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations' | tee /dev/stderr) - - local consul_checks=$(echo "$annotations" | \ - yq -r '."ad.datadoghq.com/consul.checks"' | tee /dev/stderr) - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.init_config | tee /dev/stderr)" - [ "${actual}" = "{}" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].openmetrics_endpoint | tee /dev/stderr)" - [ "${actual}" = "http://consul-server.default.svc:8500/v1/agent/metrics?format=prometheus" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].headers | tee /dev/stderr)" - [ -n "${actual}" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].namespace | tee /dev/stderr)" - [ "${actual}" = "default" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].metrics[0] | tee /dev/stderr)" - [ "${actual}" = ".*" ] - -} - -@test "server/StatefulSet: when datadog.openMetricsPrometheus.enabled, applicable openmetrics annotation is set with tls url" { - cd `chart_dir` - local annotations=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'global.tls.enabled=true' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.openMetricsPrometheus.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations' | tee /dev/stderr) - - local consul_checks=$(echo "$annotations" | \ - yq -r '."ad.datadoghq.com/consul.checks"' | tee /dev/stderr) - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.init_config | tee /dev/stderr)" - [ "${actual}" = "{}" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].openmetrics_endpoint | tee /dev/stderr)" - [ "${actual}" = "https://consul-server.default.svc:8501/v1/agent/metrics?format=prometheus" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].headers | tee /dev/stderr)" - [ -n "${actual}" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].tls_cert | tee /dev/stderr)" - [ "${actual}" = "/etc/datadog-agent/conf.d/consul.d/certs/tls.crt" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].tls_private_key | tee /dev/stderr)" - [ "${actual}" = "/etc/datadog-agent/conf.d/consul.d/certs/tls.key" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].tls_ca_cert | tee /dev/stderr)" - [ "${actual}" = "/etc/datadog-agent/conf.d/consul.d/ca/tls.crt" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].namespace | tee /dev/stderr)" - [ "${actual}" = "default" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].metrics[0] | tee /dev/stderr)" - [ "${actual}" = ".*" ] -} - -@test "server/StatefulSet: when global.metrics.datadog.openMetricsPrometheus.enabled, applicable openmetrics annotation is set with acls.manageSystemACLs enabled" { - cd `chart_dir` - local annotations=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.openMetricsPrometheus.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations' | tee /dev/stderr) - - local consul_checks=$(echo "$annotations" | \ - yq -r '."ad.datadoghq.com/consul.checks"' | tee /dev/stderr) - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.init_config | tee /dev/stderr)" - [ "${actual}" = "{}" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].openmetrics_endpoint | tee /dev/stderr)" - [ "${actual}" = "http://consul-server.default.svc:8500/v1/agent/metrics?format=prometheus" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r '.[0].headers["X-Consul-Token"]' | tee /dev/stderr)" - [ "${actual}" = "ENC[k8s_secret@default/default-datadog-agent-metrics-acl-token/token]" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].namespace | tee /dev/stderr)" - [ "${actual}" = "default" ] - - local actual="$( echo "$consul_checks" | \ - jq -r .openmetrics.instances | jq -r .[0].metrics[0] | tee /dev/stderr)" - [ "${actual}" = ".*" ] - -} - -@test "server/StatefulSet: consul metrics exclusion annotation when using metrics.datadog.dogstatsd.enabled=true" { - cd `chart_dir` - local annotations=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.image=hashicorp/consul-enterprise:1.17.0-ent' \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations' | tee /dev/stderr) - - local actual=$( echo "$annotations" | \ - yq -r '."ad.datadoghq.com/consul.checks"' | tee /dev/stderr ) - [ -n "${actual}" ] - - local actual=$( echo "$annotations" | \ - yq -r '."ad.datadoghq.com/consul.metrics_exclude"' | tee /dev/stderr ) - [ "${actual}" = "true" ] -} - - -@test "server/StatefulSet: datadog unified tagging labels get added when global.metrics.datadog.enabled=true" { - cd `chart_dir` - local labels=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.image=hashicorp/consul-enterprise:1.17.0-ent' \ - --set 'global.metrics.enabled=true' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.labels' | tee /dev/stderr) - - local actual=$( echo "$labels" | \ - yq -r '."tags.datadoghq.com/version"' | tee /dev/stderr ) - [ "${actual}" = "1.17.0-ent" ] - - local actual=$( echo "$labels" | \ - yq -r '."tags.datadoghq.com/env"' | tee /dev/stderr ) - [ "${actual}" = "consul" ] - - local actual=$( echo "$labels" | \ - yq -r '."tags.datadoghq.com/service"' | tee /dev/stderr ) - [ "${actual}" = "consul-server" ] -} - -@test "server/StatefulSet: datadog unix socket path name rendering for hostPath volume and volumeMount using default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.volumes[] | select(.name=="dsdsocket") | .hostPath.path' | tee /dev/stderr) - - [ "${actual}" = "/var/run/datadog" ] -} - -@test "server/StatefulSet: datadog unix socket path name rendering for hostPath volume and volumeMount using non default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.metrics.enabled=true' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.enabled=true' \ - --set 'global.metrics.datadog.dogstatsd.dogstatsdAddr="/this/otherpath/datadog/dsd.socket"' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.volumes[] | select(.name=="dsdsocket") | .hostPath.path' | tee /dev/stderr) - - [ "${actual}" = "/this/otherpath/datadog" ] -} - #-------------------------------------------------------------------- # config-configmap @@ -1123,7 +788,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}" = a4771bea366d4a6ee9037572665dc4040519dc22e9b0ff3463a263aab13675b8 ] + [ "${actual}" = 607e209aa7d8529f488723dfdb3f0c7abadec03969c04d822901a8ab3b0338d0 ] } @test "server/StatefulSet: adds config-checksum annotation when extraConfig is provided" { @@ -1133,7 +798,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}" = c6b872933263bf5fe847d61e638035637d2db89edf31ad25d0aaeaa5261649c9 ] + [ "${actual}" = e6b4f62d1ecb6ce9408b1af0f7eae6ee50438b1937fbe9dccdff5a702fcd9b5b ] } @test "server/StatefulSet: adds config-checksum annotation when config is updated" { @@ -1143,84 +808,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}" = 576044232d6181bca69628af87c12f15311ebd3f0ab700e112b3e1dea9225125 ] -} - -#-------------------------------------------------------------------- -# server extraConfig validation - -@test "server/Statefulset: Validate enable_debug extraConfig for Consul Helm chart" { - cd `chart_dir` - run helm template \ - -s templates/server-statefulset.yaml \ - --set global.metrics.enabled=true \ - --set global.metrics.enableAgentMetrics=true \ - --set server.extraConfig=enable_debug=true \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "The enable_debug key is present in extra-from-values.json. Use server.enableAgentDebug to set this value." ]] -} - -@test "server/Statefulset: Validate disable_hostname extraConfig for Consul Helm chart" { - cd `chart_dir` - run helm template \ - -s templates/server-statefulset.yaml \ - --set global.metrics.enabled=true \ - --set global.metrics.enableAgentMetrics=true \ - --set server.extraConfig=telemetry.disable_hostname=true \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "The disable_hostname key is present in extra-from-values.json. Use global.metrics.disableAgentHostName to set this value." ]] -} - -@test "server/Statefulset: Validate enable_host_metrics extraConfig for Consul Helm chart" { - cd `chart_dir` - run helm template \ - -s templates/server-statefulset.yaml \ - --set global.metrics.enabled=true \ - --set global.metrics.enableAgentMetrics=true \ - --set server.extraConfig=telemetry.enable_host_metrics=true \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "The enable_host_metrics key is present in extra-from-values.json. Use global.metrics.enableHostMetrics to set this value." ]] -} - -@test "server/Statefulset: Validate prefix_filter extraConfig for Consul Helm chart" { - cd `chart_dir` - run helm template \ - -s templates/server-statefulset.yaml \ - --set global.metrics.enabled=true \ - --set global.metrics.enableAgentMetrics=true \ - --set server.extraConfig=telemetry.prefix_filter=["+consul.rpc.server.call"] \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "The prefix_filter key is present in extra-from-values.json. Use global.metrics.prefix_filter to set this value." ]] -} - -@test "server/Statefulset: Validate dogstatsd_tags extraConfig for Consul Helm chart" { - cd `chart_dir` - run helm template \ - -s templates/server-statefulset.yaml \ - --set global.metrics.enabled=true \ - --set global.metrics.enableAgentMetrics=true \ - --set global.metrics.datadog.dogstatsd.enabled=true \ - --set server.extraConfig=telemetry.dogstatsd_tags='[\"source:consul-server\"\,\"consul_service:consul\"]' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "The dogstatsd_tags key is present in extra-from-values.json. Use global.metrics.datadog.dogstatsd.dogstatsdTags to set this value." ]] -} - -@test "server/Statefulset: Validate dogstatsd_addr extraConfig for Consul Helm chart" { - cd `chart_dir` - run helm template \ - -s templates/server-statefulset.yaml \ - --set global.metrics.enabled=true \ - --set global.metrics.enableAgentMetrics=true \ - --set global.metrics.datadog.dogstatsd.enabled=true \ - --set server.extraConfig=telemetry.dogstatsd_addr="localhost:8125" \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "The dogstatsd_addr key is present in extra-from-values.json. Use global.metrics.datadog.dogstatsd.dogstatsd_addr to set this value." ]] + [ "${actual}" = 311e5c54231011d09630ea118f4781d62f1fc92f11894b4ac7c75879a534adf4 ] } #-------------------------------------------------------------------- @@ -1299,16 +887,16 @@ load _helpers . | tee /dev/stderr | yq -r '.spec.template.spec.securityContext' | tee /dev/stderr) - local actual=$(echo "$security_context" | yq -r .runAsNonRoot) + local actual=$(echo $security_context | jq -r .runAsNonRoot) [ "${actual}" = "true" ] - local actual=$(echo "$security_context" | yq -r .fsGroup) + local actual=$(echo $security_context | jq -r .fsGroup) [ "${actual}" = "1000" ] - local actual=$(echo "$security_context" | yq -r .runAsUser) + local actual=$(echo $security_context | jq -r .runAsUser) [ "${actual}" = "100" ] - local actual=$(echo "$security_context" | yq -r .runAsGroup) + local actual=$(echo $security_context | jq -r .runAsGroup) [ "${actual}" = "1000" ] } @@ -1318,26 +906,14 @@ load _helpers -s templates/server-statefulset.yaml \ --set 'server.securityContext.runAsNonRoot=false' \ --set 'server.securityContext.privileged=true' \ - --set 'server.securityContext.runAsGroup=0' \ - --set 'server.securityContext.runAsUser=0' \ - --set 'server.securityContext.fsGroup=0' \ . | tee /dev/stderr | yq -r '.spec.template.spec.securityContext' | tee /dev/stderr) - local actual=$(echo "$security_context" | yq -r .runAsNonRoot) + local actual=$(echo $security_context | jq -r .runAsNonRoot) [ "${actual}" = "false" ] - local actual=$(echo "$security_context" | yq -r .privileged) + local actual=$(echo $security_context | jq -r .privileged) [ "${actual}" = "true" ] - - local actual=$(echo "$security_context" | yq -r .fsGroup) - [ "${actual}" = "0" ] - - local actual=$(echo "$security_context" | yq -r .runAsUser) - [ "${actual}" = "0" ] - - local actual=$(echo "$security_context" | yq -r .runAsGroup) - [ "${actual}" = "0" ] } #-------------------------------------------------------------------- @@ -1372,9 +948,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) @@ -1395,11 +973,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" ] } #-------------------------------------------------------------------- @@ -1428,11 +1015,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" ] } #-------------------------------------------------------------------- @@ -2470,13 +2052,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}" = "{}" ] } @@ -3495,31 +3071,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/sync-catalog-clusterrole.bats b/charts/consul/test/unit/sync-catalog-clusterrole.bats index afc3a42b45..17141e434f 100755 --- a/charts/consul/test/unit/sync-catalog-clusterrole.bats +++ b/charts/consul/test/unit/sync-catalog-clusterrole.bats @@ -56,7 +56,7 @@ load _helpers --set 'syncCatalog.enabled=true' \ --set 'global.enablePodSecurityPolicies=true' \ . | tee /dev/stderr | - yq -r '.rules[3].resources[0]' | tee /dev/stderr) + yq -r '.rules[2].resources[0]' | tee /dev/stderr) [ "${actual}" = "podsecuritypolicies" ] } @@ -83,12 +83,4 @@ load _helpers . | tee /dev/stderr | yq -c '.rules[0].verbs' | tee /dev/stderr) [ "${actual}" = '["get","list","watch","update","patch","delete","create"]' ] - - actual=$(helm template \ - -s templates/sync-catalog-clusterrole.yaml \ - --set 'syncCatalog.enabled=true' \ - --set 'syncCatalog.toK8S=true' \ - . | tee /dev/stderr | - yq -c '.rules[0].verbs' | tee /dev/stderr) - [ "${actual}" = '["get","list","watch","update","patch","delete","create"]' ] } diff --git a/charts/consul/test/unit/sync-catalog-deployment.bats b/charts/consul/test/unit/sync-catalog-deployment.bats index b60b030ac0..d8321eefdf 100755 --- a/charts/consul/test/unit/sync-catalog-deployment.bats +++ b/charts/consul/test/unit/sync-catalog-deployment.bats @@ -413,29 +413,6 @@ load _helpers [ "${actual}" = "true" ] } -#-------------------------------------------------------------------- -# syncLoadBalancerEndpoints - -@test "syncCatalog/Deployment: enable LB endpoints sync flag not passed when disabled" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/sync-catalog-deployment.yaml \ - --set 'syncCatalog.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command | any(contains("-sync-lb-services-endpoints=true"))' | tee /dev/stderr) - [ "${actual}" = "false" ] -} -@test "syncCatalog/Deployment: enable LB endpoints sync flag passed when enabled" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/sync-catalog-deployment.yaml \ - --set 'syncCatalog.enabled=true' \ - --set 'syncCatalog.syncLoadBalancerEndpoints=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command | any(contains("-sync-lb-services-endpoints=true"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - #-------------------------------------------------------------------- # affinity @@ -1007,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}" = "{}" ] } @@ -1259,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-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats index 71f10d3934..57d6b84b27 100755 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -1315,20 +1315,6 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ [ "${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 @@ -1371,82 +1357,3 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ local actual=$(echo $object | jq -r '.[1].args | any(contains("-service-namespace=fakenamespace"))' | tee /dev/stderr) [ "${actual}" = 'true' ] } - -#-------------------------------------------------------------------- -# global.metrics.datadog.otlp - -@test "telemetryCollector/Deployment: DataDog OTLP Collector HTTP protocol verification" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.cloud.enabled=false' \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.otlp.enabled=true' \ - --set 'global.metrics.datadog.otlp.protocol'="http" \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) - - local actual=$(echo "$object" | - yq -r '.[] | select(.name=="CO_OTEL_HTTP_ENDPOINT").value' | tee /dev/stderr) - [ "${actual}" = 'http://$(HOST_IP):4318' ] -} - -@test "telemetryCollector/Deployment: DataDog OTLP Collector HTTP protocol verification, case-insensitive" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.cloud.enabled=false' \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.otlp.enabled=true' \ - --set 'global.metrics.datadog.otlp.protocol'="HTTP" \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) - - local actual=$(echo "$object" | - yq -r '.[] | select(.name=="CO_OTEL_HTTP_ENDPOINT").value' | tee /dev/stderr) - [ "${actual}" = 'http://$(HOST_IP):4318' ] -} - -@test "telemetryCollector/Deployment: DataDog OTLP Collector gRPC protocol verification" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.cloud.enabled=false' \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.otlp.enabled=true' \ - --set 'global.metrics.datadog.otlp.protocol'="grpc" \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) - - local actual=$(echo "$object" | - yq -r '.[] | select(.name=="CO_OTEL_HTTP_ENDPOINT").value' | tee /dev/stderr) - [ "${actual}" = 'grpc://$(HOST_IP):4317' ] -} - -@test "telemetryCollector/Deployment: DataDog OTLP Collector gRPC protocol verification, case-insensitive" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.cloud.enabled=false' \ - --set 'global.metrics.enabled=true' \ - --set 'global.metrics.enableAgentMetrics=true' \ - --set 'global.metrics.datadog.enabled=true' \ - --set 'global.metrics.datadog.otlp.enabled=true' \ - --set 'global.metrics.datadog.otlp.protocol'="gRPC" \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) - - local actual=$(echo "$object" | - yq -r '.[] | select(.name=="CO_OTEL_HTTP_ENDPOINT").value' | tee /dev/stderr) - [ "${actual}" = 'grpc://$(HOST_IP):4317' ] -} \ 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 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 71e615a027..5e10c5b1fd 100644 --- a/charts/consul/test/unit/terminating-gateways-deployment.bats +++ b/charts/consul/test/unit/terminating-gateways-deployment.bats @@ -338,19 +338,6 @@ load _helpers [ "${actual}" = "/metrics" ] } -@test "terminatingGateways/Deployment: when global.metrics.enabled=true, and terminating gateways annotation for prometheus path is specified, it uses the specified annotation rather than default." { - cd `chart_dir` - local actual=$(helm template \ - -s templates/terminating-gateways-deployment.yaml \ - --set 'terminatingGateways.enabled=true' \ - --set 'connectInject.enabled=true' \ - --set 'global.metrics.enabled=true' \ - --set 'terminatingGateways.defaults.annotations=prometheus.io/path: /anew/path' \ - . | tee /dev/stderr | - yq -s -r '.[0].spec.template.metadata.annotations."prometheus.io/path"' | tee /dev/stderr) - [ "${actual}" = "/anew/path" ] -} - @test "terminatingGateways/Deployment: when global.metrics.enableGatewayMetrics=false, does not set prometheus annotations" { cd `chart_dir` local object=$(helm template \ @@ -884,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" { @@ -899,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" ] @@ -921,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" ] @@ -944,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" ] @@ -1227,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/values.yaml b/charts/consul/values.yaml index 8e307d4265..b5268f6a73 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.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.5-dev + imageK8S: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.1.14-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 @@ -293,7 +293,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 +391,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: | @@ -517,7 +517,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 +601,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: "" @@ -618,16 +618,6 @@ global: # @type: boolean enableAgentMetrics: false - # Set to true to stop prepending the machine's hostname to gauge-type metrics. Default is false. - # Only applicable if `global.metrics.enabled` and `global.metrics.enableAgentMetrics` is true. - # @type: boolean - disableAgentHostName: false - - # Configures consul agent underlying host metrics. Default is false. - # Only applicable if `global.metrics.enabled` and `global.metrics.enableAgentMetrics` is true. - # @type: boolean - enableHostMetrics: false - # Configures the retention time for metrics in Consul clients and # servers. This must be greater than 0 for Consul clients and servers # to expose any metrics at all. @@ -646,149 +636,10 @@ global: # @type: boolean enableTelemetryCollector: false - # Configures the list of filter rules to apply for allowing or blocking - # metrics by prefix in the following format: - # - # A leading "+" will enable any metrics with the given prefix, and a leading "-" will block them. - # If there is overlap between two rules, the more specific rule will take precedence. - # Blocking will take priority if the same prefix is listed multiple times. - prefixFilter: - # @type: array - allowList: [] - # @type: array - blockList: [] - - # Configures consul integration configurations for datadog on kubernetes. - # Only applicable if `global.metrics.enabled` and `global.metrics.enableAgentMetrics` is true. - datadog: - # Enables datadog [Consul Autodiscovery Integration](https://docs.datadoghq.com/integrations/consul/?tab=containerized#metric-collection) - # by configuring the required `ad.datadoghq.com/consul.checks` annotation. The following _Consul_ agent metrics/health statuses - # are monitored by Datadog unless monitoring via OpenMetrics (Prometheus) or DogStatsD: - # - Serf events and member flaps - # - The Raft protocol - # - DNS performance - # - API Endpoints scraped: - # - `/v1/agent/metrics?format=prometheus` - # - `/v1/agent/self` - # - `/v1/status/leader` - # - `/v1/status/peers` - # - `/v1/catalog/services` - # - `/v1/health/service` - # - `/v1/health/state/any` - # - `/v1/coordinate/datacenters` - # - `/v1/coordinate/nodes` - # - # Setting either `global.metrics.datadog.otlp.enabled=true` or `global.metrics.datadog.dogstatsd.enabled=true` disables the above checks - # in lieu of metrics data collection via DogStatsD or by a customer OpenMetrics (Prometheus) collection endpoint. - # - # ~> **Note:** If you have a [dogstatsd_mapper_profile](https://docs.datadoghq.com/integrations/consul/?tab=host#dogstatsd) configured for Consul - # residing on either your Datadog NodeAgent or ClusterAgent the default Consul agent metrics/health status checks will fail. If you do not desire - # to utilize DogStatsD metrics emission from Consul, remove this configuration file, and restart your Datadog agent to permit the checks to run. - # - # @default: false - # @type: boolean - enabled: false - - # Configures Kubernetes Prometheus/OpenMetrics auto-discovery annotations for use with Datadog. - # This configuration is less common and more for advanced usage with custom metrics monitoring - # configurations. Refer to the [Datadog documentation](https://docs.datadoghq.com/containers/kubernetes/prometheus/?tab=kubernetesadv2) for more details. - openMetricsPrometheus: - # @default: false - # @type: boolean - enabled: false - - otlp: - # Enables forwarding of Consul's Telemetry Collector OTLP metrics for - # ingestion by Datadog Agent. - # @default: false - # @type: boolean - enabled: false - # Protocol used for DataDog Endpoint OTLP ingestion. - # - # Valid protocol options are one of either: - # - # - "http": will forward to DataDog HTTP OTLP Node Agent Endpoint default - "0.0.0.0:4318" - # - "grpc": will forward to DataDog gRPC OTLP Node Agent Endpoint default - "0.0.0.0:4317" - # - # @default: "http" - # @type: string - protocol: "http" - - # Configuration settings for DogStatsD metrics aggregation service - # that is bundled with the Datadog Agent. - # DogStatsD implements the StatsD protocol and adds a few Datadog-specific extensions: - # - Histogram metric type - # - Service checks - # - Events - # - Tagging - dogstatsd: - enabled: false - # Sets the socket transport type for dogstatsd: - # - "UDS" (Unix Domain Socket): prefixes `unix://` to URL and appends path to socket (i.e., "unix:///var/run/datadog/dsd.socket") - # If set, this will create the required [hostPath](https://kubernetes.io/docs/concepts/storage/volumes/#hostpath) mount for - # managing [DogStatsD with Unix Domain Socket on Kubernetes](https://docs.datadoghq.com/developers/dogstatsd/unix_socket/?tab=kubernetes). - # The volume is mounted using the `DirectoryOrCreate` type, thereby setting `0755` permissions with the same kubelet group ownership. - # - # Applies the following `volumes` and `volumeMounts` to the consul-server stateful set consul containers: - # - # ```yaml - # volumes: - # - name: dsdsocket - # hostPath: - # path: /var/run/datadog - # type: DirectoryOrCreate - # volumeMounts: - # - name: dsdsocket - # mountPath: /var/run/datadog - # readOnly: true - # ``` - # - "UDP" (User Datagram Protocol): assigns address to use `hostname/IP:Port` formatted URL for UDP transport to hostIP based - # dogstatsd sink (i.e., 127.0.0.1:8125). HostIP of Datadog agent must be reachable and known to Consul server emitting metrics. - # - # @default: "UDS" - # @type: string - socketTransportType: "UDS" - # Sets URL path for dogstatsd: - # - # Can be either a path to unix domain socket or an IP Address or Hostname that's reachable from the - # consul-server service, server containers. When using "UDS" the path will be appended. When using "UDP" - # the path will be prepended to the specified `dogstatsdPort`. - # - # @default: "/var/run/datadog/dsd.socket" - # @type: string - dogstatsdAddr: "/var/run/datadog/dsd.socket" - # Configures IP based dogstatsd designated port that will be appended to "UDP" based transport socket IP/Hostname URL. - # - # If using a kubernetes service based address (i.e., datadog.default.svc.cluster.local), set this to 0 to - # mitigate appending a port value to the dogstatsd address field. Resultant address would be "datadog.default.svc.cluster.local" with - # default port setting, while appending a non-zero port would result in "172.10.23.6:8125" with a dogstatsdAddr value - # of "172.10.23.6". - # - # @default: 0 - # @type: integer - dogstatsdPort: 0 - # Configures datadog [autodiscovery](https://docs.datadoghq.com/containers/kubernetes/log/?tab=operator#autodiscovery) - # style [log integration](https://docs.datadoghq.com/integrations/consul/?tab=containerized#log-collection) - # configuration for Consul. - # - # The default settings should handle most Consul Kubernetes deployment schemes. The resultant annotation - # will reside on the consul-server statefulset as autodiscovery annotations. - # (i.e., ad.datadoghq.com/consul.logs: ["source:consul","consul_service:consul-server", ""]) - # - # @default: ["source:consul","consul_service:consul-server"] - # @type: array - dogstatsdTags: ["source:consul","consul_service:consul-server"] - # Namespace - # - # @default: "default" - # @type: string - namespace: "default" - - # 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.3-dev # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. @@ -900,31 +751,6 @@ global: # @type: array trustedCAs: [] - # Consul feature flags that will be enabled across components. - # Supported feature flags: - # - `resource-apis`: - # _**Warning**_! 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`: - # _**Warning**_! 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. @@ -1064,12 +890,6 @@ server: # by setting the `server.extraConfig` value or by applying [configuration entries](https://developer.hashicorp.com/consul/docs/connect/config-entries). connect: true - # When set to true, enables Consul to report additional debugging information, including runtime profiling (pprof) data. - # This setting is only required for clusters without ACL enabled. Sets `enable_debug` in server agent config to `true`. - # If you change this setting, you must restart the agent for the change to take effect. Default is false. - # @type: boolean - enableAgentDebug: false - serviceAccount: # This value defines additional annotations for the server service account. This should be formatted as a multi-line # string. @@ -1160,14 +980,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 preserve 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. @@ -1497,43 +1311,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 @@ -2193,10 +1970,6 @@ syncCatalog: # Set this to false to skip syncing ClusterIP services. syncClusterIPServices: true - # If true, LoadBalancer service endpoints instead of ingress addresses will be synced to Consul. - # If false, LoadBalancer endpoints are not synced to Consul. - syncLoadBalancerEndpoints: false - 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 @@ -2368,114 +2141,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 - - # Metrics settings for gateways created with this gateway class configuration. - metrics: - # This value enables or disables metrics collection on a gateway, overriding the global gateway metrics collection settings. - # @type: boolean - enabled: "-" - # This value sets the port to use for scraping gateway metrics via prometheus, defaults to 20200 if not set. Must be in the port - # range of 1024-65535. - # @type: int - port: null - # This value sets the path to use for scraping gateway metrics via prometheus, defaults to /metrics if not set. - # @type: string - path: 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. @@ -2872,10 +2537,8 @@ connectInject: # - `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-startup-grace-period-seconds` # - `consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-port` # - `consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-shutdown-path` - # - `consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-startup-path` # @type: map lifecycle: # @type: boolean @@ -2885,13 +2548,9 @@ connectInject: # @type: integer defaultShutdownGracePeriodSeconds: 30 # @type: integer - defaultStartupGracePeriodSeconds: 0 - # @type: integer defaultGracefulPort: 20600 # @type: string defaultGracefulShutdownPath: "/graceful_shutdown" - # @type: string - defaultGracefulStartupPath: "/graceful_startup" # 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. @@ -2929,7 +2588,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: "" @@ -2962,7 +2621,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. @@ -3142,10 +2801,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: "" @@ -3313,7 +2973,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". @@ -3450,6 +3111,174 @@ terminatingGateways: gateways: - name: terminating-gateway +# Configuration settings for the Consul API Gateway integration +apiGateway: + # When true the helm chart will install the Consul API Gateway controller + enabled: false + + # Image to use for the api-gateway-controller pods and gateway instances + # + # ~> **Note:** Using API Gateway <= 0.4 with external servers requires setting `client.enabled: true`. + # @type: string + image: null + + # 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" + + # Override global log verbosity level for api-gateway-controller pods. One of "debug", "info", "warn", or "error". + # @type: string + logLevel: info + + # Configuration settings for the optional GatewayClass installed by consul-k8s (enabled by default) + managedGatewayClass: + # 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) + # 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 + + # This value toggles if the gateway ports should be mapped to host ports + useHostPorts: false + + # 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 + # + # Example: + # + # ```yaml + # deployment: + # defaultInstances: 3 + # maxInstances: 8 + # minInstances: 1 + # ``` + # + # @type: map + deployment: null + + # 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 + + # Configuration for the api-gateway controller component + controller: + # This value sets the number of controller replicas to deploy. + replicas: 1 + + # Annotations to apply to the api-gateway-controller pods. + # + # ```yaml + # annotations: | + # "annotation-key": "annotation-value" + # ``` + # + # @type: string + annotations: null + + # This value references an existing + # 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) + # labels for api-gateway-controller pod assignment, formatted as a multi-line string. + # + # Example: + # + # ```yaml + # nodeSelector: | + # beta.kubernetes.io/arch: amd64 + # ``` + # + # @type: string + 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. + # + # @type: string + tolerations: null + + # Configuration for the Service created for the api-gateway-controller + service: + # Annotations to apply to the api-gateway-controller service. + # + # ```yaml + # annotations: | + # "annotation-key": "annotation-value" + # ``` + # + # @type: string + annotations: null + + # The resource settings for api gateway pods. + # @recurse: false + # @type: map + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "100Mi" + cpu: "100m" + + # The resource settings for the `copy-consul-bin` init container. + # @recurse: false + # @type: map + initCopyConsulContainer: + resources: + requests: + memory: "25Mi" + cpu: "50m" + limits: + memory: "150Mi" + cpu: "50m" + # Configuration settings for the webhook-cert-manager # `webhook-cert-manager` ensures that cert bundles are up to date for the mutating webhook. webhookCertManager: @@ -3472,6 +3301,33 @@ webhookCertManager: # @type: string nodeSelector: 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: "100m" + limits: + memory: "50Mi" + cpu: "100m" + # Configures a demo Prometheus installation. prometheus: # When true, the Helm chart will install a demo Prometheus server instance diff --git a/charts/embed_chart.go b/charts/embed_chart.go index 8e36abba23..27cdd9da1c 100644 --- a/charts/embed_chart.go +++ b/charts/embed_chart.go @@ -15,7 +15,7 @@ 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/cli/cmd/install/install.go b/cli/cmd/install/install.go index f3d4671c6e..f578cc7608 100644 --- a/cli/cmd/install/install.go +++ b/cli/cmd/install/install.go @@ -592,7 +592,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..104b66cf23 100644 --- a/cli/cmd/install/install_test.go +++ b/cli/cmd/install/install_test.go @@ -165,45 +165,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.") + } }) } } diff --git a/cli/cmd/proxy/list/command.go b/cli/cmd/proxy/list/command.go index 0204832c44..49881417a3 100644 --- a/cli/cmd/proxy/list/command.go +++ b/cli/cmd/proxy/list/command.go @@ -10,16 +10,15 @@ 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 ( @@ -212,51 +211,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 +268,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 { @@ -335,5 +304,4 @@ func (c *ListCommand) output(pods []v1.Pod) { } else { c.UI.Table(tbl) } - } diff --git a/cli/cmd/proxy/list/command_test.go b/cli/cmd/proxy/list/command_test.go index 5493ab88a9..9e7104886d 100644 --- a/cli/cmd/proxy/list/command_test.go +++ b/cli/cmd/proxy/list/command_test.go @@ -274,33 +274,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", }, }, }, @@ -342,7 +321,7 @@ 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.*" + 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{ @@ -380,27 +359,6 @@ 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/cli/cmd/troubleshoot/proxy/proxy.go b/cli/cmd/troubleshoot/proxy/proxy.go index cd8362bf28..d0f57e9d4e 100644 --- a/cli/cmd/troubleshoot/proxy/proxy.go +++ b/cli/cmd/troubleshoot/proxy/proxy.go @@ -232,7 +232,7 @@ func (c *ProxyCommand) Troubleshoot() error { } else { c.UI.Output(o.Message, terminal.WithErrorStyle()) for _, action := range o.PossibleActions { - c.UI.Output(fmt.Sprintf("-> %s", action), terminal.WithInfoStyle()) + c.UI.Output(fmt.Sprintf("-> %v", action), terminal.WithInfoStyle()) } } } diff --git a/cli/cmd/troubleshoot/upstreams/upstreams.go b/cli/cmd/troubleshoot/upstreams/upstreams.go index abc3235cd5..c3f2888edd 100644 --- a/cli/cmd/troubleshoot/upstreams/upstreams.go +++ b/cli/cmd/troubleshoot/upstreams/upstreams.go @@ -121,7 +121,6 @@ func (c *UpstreamsCommand) Run(args []string) int { // 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") } @@ -207,7 +206,10 @@ func (c *UpstreamsCommand) Troubleshoot() error { 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{}) + table.AddRow( + []string{formatIPs(u.IPs), strconv.FormatBool(u.IsVirtual), formatClusterNames(u.ClusterNames)}, + []string{}, + ) } c.UI.Table(table) diff --git a/cli/cmd/upgrade/upgrade.go b/cli/cmd/upgrade/upgrade.go index a7e79d2239..0a99283cbe 100644 --- a/cli/cmd/upgrade/upgrade.go +++ b/cli/cmd/upgrade/upgrade.go @@ -20,6 +20,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 +428,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/commands.go b/cli/commands.go index 513bd6d9d7..2c2e797673 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -23,7 +23,7 @@ import ( cmdversion "github.com/hashicorp/consul-k8s/cli/cmd/version" "github.com/hashicorp/consul-k8s/cli/common" "github.com/hashicorp/consul-k8s/cli/common/terminal" - "github.com/hashicorp/consul-k8s/cli/version" + "github.com/hashicorp/consul-k8s/version" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" ) diff --git a/cli/go.mod b/cli/go.mod index d634c126ff..14601dc490 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,16 +1,21 @@ module github.com/hashicorp/consul-k8s/cli -go 1.20 +go 1.22 + +toolchain go1.22.5 + +replace github.com/hashicorp/consul-k8s/version => ../version require ( github.com/bgentry/speakeasy v0.1.0 github.com/cenkalti/backoff v2.2.1+incompatible - github.com/fatih/color v1.16.0 - github.com/google/go-cmp v0.5.9 + github.com/fatih/color v1.17.0 + github.com/google/go-cmp v0.6.0 github.com/hashicorp/consul-k8s/charts v0.0.0-00010101000000-000000000000 - github.com/hashicorp/consul/troubleshoot v0.5.2 - github.com/hashicorp/go-hclog v1.5.0 - github.com/hashicorp/hcp-sdk-go v0.62.1-0.20230913154003-cf69c0370c54 + github.com/hashicorp/consul-k8s/version v0.0.0 + github.com/hashicorp/consul/troubleshoot v0.2.1 + github.com/hashicorp/go-hclog v1.6.3 + 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.20 github.com/mitchellh/cli v1.1.5 @@ -18,25 +23,25 @@ require ( github.com/posener/complete v1.2.3 github.com/stretchr/testify v1.8.4 golang.org/x/text v0.14.0 - helm.sh/helm/v3 v3.11.3 - k8s.io/api v0.26.12 - k8s.io/apiextensions-apiserver v0.26.10 - k8s.io/apimachinery v0.26.12 - k8s.io/cli-runtime v0.26.10 - k8s.io/client-go v0.26.12 - k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 + helm.sh/helm/v3 v3.14.4 + k8s.io/api v0.29.8 + k8s.io/apiextensions-apiserver v0.29.0 + k8s.io/apimachinery v0.29.8 + k8s.io/cli-runtime v0.29.8 + k8s.io/client-go v0.29.8 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b sigs.k8s.io/yaml v1.3.0 ) require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/BurntSushi/toml v1.2.1 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect - github.com/Masterminds/squirrel v1.5.3 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect @@ -51,54 +56,55 @@ require ( github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.5.0 // indirect - github.com/docker/cli v25.0.1+incompatible // indirect + github.com/docker/cli v27.1.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v25.0.5+incompatible // indirect - github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect - github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/emicklei/go-restful/v3 v3.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/go-control-plane/xdsmatcher v0.0.0-20231026140209-dc05a22efe95 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch v5.7.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/go-errors/errors v1.0.1 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // 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.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/analysis v0.21.2 // indirect + github.com/go-openapi/errors v0.20.2 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // 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.22.3 // indirect - github.com/go-openapi/validate v0.22.1 // indirect + github.com/go-openapi/validate v0.21.0 // 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 github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/websocket v1.5.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.28.2 // indirect - github.com/hashicorp/consul/envoyextensions v0.5.2 // indirect + github.com/hashicorp/consul/api v1.21.3 // indirect + github.com/hashicorp/consul/envoyextensions v0.2.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 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/golang-lru v1.0.2 // indirect github.com/hashicorp/serf v0.10.1 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.13 // indirect @@ -109,7 +115,7 @@ require ( github.com/klauspost/compress v1.16.0 // 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.9 // 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 @@ -123,26 +129,24 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/onsi/ginkgo/v2 v2.6.0 // indirect - github.com/onsi/gomega v1.24.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc6 // 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/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - github.com/rubenv/sql-migrate v1.3.1 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect + github.com/rubenv/sql-migrate v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -153,41 +157,41 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xlab/treeprint v1.1.0 // indirect + github.com/xlab/treeprint v1.2.0 // indirect go.mongodb.org/mongo-driver v1.11.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect go.opentelemetry.io/otel v1.19.0 // indirect go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect - go.starlark.net v0.0.0-20230128213706-3f75dec8e403 // indirect + go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect golang.org/x/crypto v0.22.0 // indirect - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.19.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/grpc v1.58.3 // indirect google.golang.org/protobuf v1.33.0 // indirect 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.26.10 // indirect - k8s.io/component-base v0.26.10 // indirect - k8s.io/klog/v2 v2.90.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/kubectl v0.26.10 // indirect + k8s.io/apiserver v0.29.0 // indirect + k8s.io/component-base v0.29.0 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/kubectl v0.29.0 // indirect oras.land/oras-go v1.2.5 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect - sigs.k8s.io/kustomize/api v0.12.1 // indirect - sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect + sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) // This replace directive is to avoid having to manually bump the version of the charts module upon changes to the Helm diff --git a/cli/go.sum b/cli/go.sum index 68710d50fb..aa789f9fb2 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,80 +1,40 @@ 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 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 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/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= -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/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= -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/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= 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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/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/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= -github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= -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/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= 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/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= @@ -82,6 +42,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= @@ -91,19 +52,20 @@ 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.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= 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= 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 v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= @@ -114,134 +76,108 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/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/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.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is= github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/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-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/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 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= 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/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -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/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= +github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= -github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= +github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= -github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 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.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= -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 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/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.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/go-control-plane/xdsmatcher v0.0.0-20231026140209-dc05a22efe95 h1:6dlFXaGj6hb9BDbSdwDFlzOdcvQI4uPjID/QGvhKuWo= +github.com/envoyproxy/go-control-plane/xdsmatcher v0.0.0-20231026140209-dc05a22efe95/go.mod h1:4j4nOY2oaWqiwkbG6d/0GjQMJ/mbuxT5ij6RcsGA/EM= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/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.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 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.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= +github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -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.5/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +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-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= -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-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.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-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/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.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/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0= 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/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/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= @@ -249,13 +185,16 @@ github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ 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.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.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-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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= @@ -288,162 +227,112 @@ github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXs github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= 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-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/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= 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/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= -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= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 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.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= 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/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 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.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.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-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/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= -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/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/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/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/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.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8= -github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE= -github.com/hashicorp/consul/envoyextensions v0.5.2 h1:L5mYp/caTRHJyfTanIRk2J8phzqLfK2BCnrLxCIiPCA= -github.com/hashicorp/consul/envoyextensions v0.5.2/go.mod h1:Y8PTEDOAEs/785atx6iAnmxfpTaXgZjnlXCDJMw+T64= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.16.0 h1:SE9m0W6DEfgIVCJX7xU+iv/hUl4m/nxqMTnCdMxDpJ8= -github.com/hashicorp/consul/troubleshoot v0.5.2 h1:gtRPoQIcIMJtdicRs0RvjRhfwkDD38iglSvNQAwlazY= -github.com/hashicorp/consul/troubleshoot v0.5.2/go.mod h1:8UMoKivWLo2fQMSWGLqKOA97MS5lPZhwin6r9CPRlTc= +github.com/hashicorp/consul/api v1.21.3 h1:hHfaZwkrxbU6TjnlmxtSrvH0mXGep8aCXBrJ9nNhJug= +github.com/hashicorp/consul/api v1.21.3/go.mod h1:Kms78llSVzB8FtrLan+V+TSJTnfHkRGlYbhazvGzJUs= +github.com/hashicorp/consul/envoyextensions v0.2.2 h1:AriKSqZEXkfRemBuWMkU9b9ZtvGIaBhg+4ZAYtbovNU= +github.com/hashicorp/consul/envoyextensions v0.2.2/go.mod h1:b8muOXsWk/d+spHj7fc0mJMzFd5JVRPPueOsej4fSkk= +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/troubleshoot v0.2.1 h1:ajuvTgObx/VIcJD6Suf2TQkP601QwSCk7S/UxJpY2MQ= +github.com/hashicorp/consul/troubleshoot v0.2.1/go.mod h1:RnSAnpW0YXVlHkAp8PxPdOw1EKDrLcG6S0VZ2eWki9E= 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-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.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/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-msgpack v0.5.5/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-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-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.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.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/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +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/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -451,8 +340,6 @@ github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/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.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= @@ -462,27 +349,18 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf 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= -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 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= 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/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= 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.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/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= @@ -490,14 +368,9 @@ github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 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/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= -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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -509,12 +382,10 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= 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.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/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/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 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= @@ -540,10 +411,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/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= 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.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -552,11 +421,9 @@ github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.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.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= @@ -565,18 +432,14 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0 github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 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.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -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.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= @@ -587,8 +450,9 @@ github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQ github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 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= @@ -602,18 +466,17 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ 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/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= -github.com/nelsam/hel/v2 v2.3.3/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +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/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= -github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= -github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= -github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= @@ -623,124 +486,85 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr 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/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= 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= 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/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-20200406201722-06f95a1c68e8/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= 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.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= -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_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_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.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -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.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/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/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= 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.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/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/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/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rubenv/sql-migrate v1.3.1 h1:Vx+n4Du8X8VTYuXbhNxdEUoh6wiJERA0GlWocR5FrbA= -github.com/rubenv/sql-migrate v1.3.1/go.mod h1:YzG/Vh82CwyhTFXy+Mf5ahAiiEOpAlHurg+23VEzcsk= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= +github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 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.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/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -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/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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/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/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.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 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.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -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.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= @@ -750,14 +574,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/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/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/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= @@ -770,183 +592,86 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo 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/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= -github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 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.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -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= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= 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= -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.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -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/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.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= +go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= 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-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-20190605123033-f99c8df09eb5/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-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-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 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/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/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-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/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= -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/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.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-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= 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-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-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= -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-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-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-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-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.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 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-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-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -956,284 +681,105 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ 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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -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-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-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-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-20191026070338-33540a1f6037/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= -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-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-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-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= 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-20210809222454-d867a43fc93e/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-20220715151400-c0bba94af5f8/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-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/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.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.6.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/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= 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-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-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-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-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= -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-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-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -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-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-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.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= 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= -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.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/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/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= 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= -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-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-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= -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/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/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= -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.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.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1242,12 +788,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -1260,12 +802,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 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.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -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= @@ -1280,49 +818,42 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -helm.sh/helm/v3 v3.11.3 h1:n1X5yaQTP5DYywlBOZMl2gX398Gp6YwFp/IAVj6+5D4= -helm.sh/helm/v3 v3.11.3/go.mod h1:S+sOdQc3BLvt09a9rSlKKVs9x0N/yx+No0y3qFw+FQ8= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +helm.sh/helm/v3 v3.14.4 h1:6FSpEfqyDalHq3kUr4gOMThhgY55kXUEjdQoyODYnrM= +helm.sh/helm/v3 v3.14.4/go.mod h1:Tje7LL4gprZpuBNTbG34d1Xn5NmRT3OWfBRwpOSer9I= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-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.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/apiserver v0.26.10 h1:gradpIHygzZN87yK+o6V3gpbCSF78HZ0hejLZQQwdDs= -k8s.io/apiserver v0.26.10/go.mod h1:TGrQKQWUfQcotK3P4TtoVZxXOWklFF36QZlA5wufLs4= -k8s.io/cli-runtime v0.26.10 h1:a5t8ejLCCjWBEny70uMDyPfOyOJH1qAxrrEo2a9fopU= -k8s.io/cli-runtime v0.26.10/go.mod h1:i1UCYrl+n32ej4N2n2eacOMv4T94vRL0/ooOLopN23Q= -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.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/kubectl v0.26.10 h1:UoHA2Apb/Ack+B3evJjokbQ1shq6WdAmVi9AtWiY1B8= -k8s.io/kubectl v0.26.10/go.mod h1:U8Zb+jkWVI3H/LSbCDHQ0d70uYmOJtNQk9V2fmg7tGw= -k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= -k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/api v0.29.8 h1:ZBKg9clWnIGtQ5yGhNwMw2zyyrsIAQaXhZACcYNflQE= +k8s.io/api v0.29.8/go.mod h1:XlGIpmpzKGrtVca7GlgNryZJ19SvQdI808NN7fy1SgQ= +k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= +k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/apimachinery v0.29.8 h1:uBHc9WuKiTHClIspJqtR84WNpG0aOGn45HWqxgXkk8Y= +k8s.io/apimachinery v0.29.8/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= +k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o= +k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= +k8s.io/cli-runtime v0.29.8 h1:kVErAPf1v7MOwNO6rBYnf2i4kQ2668Y9pHGO5C1/wSo= +k8s.io/cli-runtime v0.29.8/go.mod h1:c00Fk85K05DtEknMAi1t7ao1MR4nmQ9YlvC+QluvNoY= +k8s.io/client-go v0.29.8 h1:QMRKcIzqE/qawknXcsi51GdIAYN8UP39S/M5KnFu/J0= +k8s.io/client-go v0.29.8/go.mod h1:ZzrAAVrqO2jVXMb8My/jTke8n0a/mIynnA3y/1y1UB0= +k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= +k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI= +k8s.io/kubectl v0.29.0/go.mod h1:0jMjGWIcMIQzmUaMgAzhSELv5WtHo2a8pq67DtviAJs= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= -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/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/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= -sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= -sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= -sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= -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/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= +sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= +sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= +sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/cli/helm/chart.go b/cli/helm/chart.go index c57d9220bc..6fc033e1f4 100644 --- a/cli/helm/chart.go +++ b/cli/helm/chart.go @@ -65,7 +65,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 +74,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/values.go b/cli/helm/values.go index 527843fd6d..06671382d1 100644 --- a/cli/helm/values.go +++ b/cli/helm/values.go @@ -438,8 +438,6 @@ type Lifecycle struct { DefaultShutdownGracePeriodSeconds int `yaml:"defaultShutdownGracePeriodSeconds"` DefaultGracefulPort int `yaml:"defaultGracefulPort"` DefaultGracefulShutdownPath string `yaml:"defaultGracefulShutdownPath"` - DefaultStartupGracePeriodSeconds int `yaml:"defaultStartupGracePeriodSeconds"` - DefaultGracefulStartupPath string `yaml:"defaultGracefulStartupPath"` } type ConnectInject struct { @@ -578,13 +576,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..86c2caecc3 100644 --- a/cli/main.go +++ b/cli/main.go @@ -9,7 +9,7 @@ import ( "os/signal" "syscall" - "github.com/hashicorp/consul-k8s/cli/version" + "github.com/hashicorp/consul-k8s/version" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" ) diff --git a/cli/preset/cloud_preset.go b/cli/preset/cloud_preset.go index 6122c3f6d1..cbc335ae17 100644 --- a/cli/preset/cloud_preset.go +++ b/cli/preset/cloud_preset.go @@ -25,26 +25,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 +57,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 +143,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 +184,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: @@ -266,7 +230,7 @@ telemetryCollector: server: replicas: %d affinity: null - serverCert: + serverCert: secretName: %s connectInject: enabled: true @@ -279,8 +243,8 @@ controller: secretNameHCPClientID, secretKeyHCPClientID, secretNameHCPClientSecret, secretKeyHCPClientSecret, apiHostCfg, authURLCfg, scadaAddressCfg, - observabilityClientIDSecretName, secretKeyHCPClientID, - observabilityClientSecretSecretName, secretKeyHCPClientSecret, + secretNameHCPClientID, secretKeyHCPClientID, + secretNameHCPClientSecret, secretKeyHCPClientSecret, cfg.BootstrapResponse.Cluster.BootstrapExpect, secretNameServerCert) valuesMap := config.ConvertToMap(values) return valuesMap @@ -341,28 +305,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..d905cb4088 100644 --- a/cli/preset/cloud_preset_test.go +++ b/cli/preset/cloud_preset_test.go @@ -25,32 +25,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", "bootstrap_expect" : 3 }, - "bootstrap": + "bootstrap": { "gossip_key": "Wa6/XFAnYy0f9iqVH2iiG+yore3CqHSemUy4AIVTa/w=", "server_tls": { @@ -63,22 +59,6 @@ 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{ @@ -99,14 +79,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 +108,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 +203,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) @@ -537,26 +497,6 @@ 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 @@ -564,39 +504,10 @@ global: 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 expectedWithoutObservability = `connectInject: + const expectedWithoutOptional = `connectInject: enabled: true controller: enabled: true @@ -660,14 +571,12 @@ 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, @@ -681,37 +590,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": { &CloudBootstrapConfig{ BootstrapResponse: &models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ Cluster: &models.HashicorpCloudGlobalNetworkManager20220215Cluster{ @@ -725,7 +614,7 @@ telemetryCollector: ClientSecret: "consul-hcp-client-secret", }, }, - expectedWithoutObservability, + expectedWithoutOptional, }, } for name, tc := range testCases { @@ -758,8 +647,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 +675,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 +720,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 +727,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/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/control-plane/Dockerfile b/control-plane/Dockerfile index b1752507fd..f352ce8782 100644 --- a/control-plane/Dockerfile +++ b/control-plane/Dockerfile @@ -23,7 +23,7 @@ RUN CGO_ENABLED=0 go install github.com/hashicorp/go-discover/cmd/discover@21457 # dev copies the binary from a local build # ----------------------------------- # BIN_NAME is a requirement in the hashicorp docker github action -FROM alpine:3.18 AS dev +FROM alpine:3.19 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. @@ -32,6 +32,7 @@ ARG CNI_BIN_NAME=consul-cni ARG VERSION ARG TARGETARCH ARG TARGETOS +ENV PRODUCT_NAME=$BIN_NAME LABEL name=${BIN_NAME} \ maintainer="Team Consul Kubernetes " \ @@ -39,7 +40,10 @@ LABEL name=${BIN_NAME} \ version=${VERSION} \ release=${VERSION} \ summary="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." \ - description="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." + description="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." \ + org.opencontainers.image.licenses="MPL-2.0" + +COPY LICENSE /usr/share/doc/$PRODUCT_NAME/LICENSE.txt # Set ARGs as ENV so that they can be used in ENTRYPOINT/CMD ENV BIN_NAME=${BIN_NAME} @@ -75,11 +79,12 @@ 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.18 AS release-default +FROM alpine:3.19 AS release-default ARG BIN_NAME=consul-k8s-control-plane ARG CNI_BIN_NAME=consul-cni ARG PRODUCT_VERSION +ENV PRODUCT_NAME=$BIN_NAME LABEL name=${BIN_NAME} \ maintainer="Team Consul Kubernetes " \ @@ -87,17 +92,16 @@ LABEL name=${BIN_NAME} \ version=${PRODUCT_VERSION} \ release=${PRODUCT_VERSION} \ summary="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." \ - description="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." + description="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." \ + org.opencontainers.image.licenses="MPL-2.0" + +COPY LICENSE /usr/share/doc/$PRODUCT_NAME/LICENSE.txt # Set ARGs as ENV so that they can be used in ENTRYPOINT/CMD 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 +118,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. @@ -141,8 +142,7 @@ ARG VERSION # and the version to download. Example: PRODUCT_NAME=consul PRODUCT_VERSION=1.2.3. ENV BIN_NAME=$BIN_NAME ENV PRODUCT_VERSION=$PRODUCT_VERSION - -ARG PRODUCT_NAME=$BIN_NAME +ENV PRODUCT_NAME=$BIN_NAME LABEL name=$PRODUCT_NAME \ maintainer="Team Consul Kubernetes " \ @@ -150,7 +150,10 @@ LABEL name=$PRODUCT_NAME \ version=$PRODUCT_VERSION \ release=$PRODUCT_VERSION \ summary="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." \ - description="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." + description="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." \ + org.opencontainers.image.licenses="MPL-2.0" + +COPY LICENSE /usr/share/doc/$PRODUCT_NAME/LICENSE.txt # Set ARGs as ENV so that they can be used in ENTRYPOINT/CMD ENV NAME=${BIN_NAME} @@ -179,8 +182,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/LICENSE b/control-plane/LICENSE new file mode 100644 index 0000000000..74f38c0103 --- /dev/null +++ b/control-plane/LICENSE @@ -0,0 +1,355 @@ +Copyright (c) 2018 HashiCorp, Inc. + +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. 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 c704a6b04e..0000000000 --- a/control-plane/api-gateway/binding/binder.go +++ /dev/null @@ -1,440 +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 - - // Configuration from helm. - HelmConfig common.HelmConfig -} - -// 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), - }) - - metricsConfig := common.GatewayMetricsConfig(b.config.Gateway, *gatewayClassConfig, b.config.HelmConfig) - registrations := registrationsForPods(metricsConfig, 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 489e765c61..0000000000 --- a/control-plane/api-gateway/binding/registration.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "fmt" - - gatewaycommon "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "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" - - // metricsConfiguration is the configuration key for binding a prometheus port to the envoy instance. - metricsConfiguration = "envoy_prometheus_bind_addr" -) - -func registrationsForPods(metrics gatewaycommon.MetricsConfig, namespace string, gateway gwv1beta1.Gateway, pods []corev1.Pod) []api.CatalogRegistration { - registrations := []api.CatalogRegistration{} - for _, pod := range pods { - registrations = append(registrations, registrationForPod(metrics, namespace, gateway, pod)) - } - return registrations -} - -func registrationForPod(metrics gatewaycommon.MetricsConfig, namespace string, gateway gwv1beta1.Gateway, pod corev1.Pod) api.CatalogRegistration { - healthStatus := api.HealthCritical - if isPodReady(pod) { - healthStatus = api.HealthPassing - } - - var proxyConfigOverrides *api.AgentServiceConnectProxyConfig - if metrics.Enabled { - proxyConfigOverrides = &api.AgentServiceConnectProxyConfig{ - Config: map[string]interface{}{ - metricsConfiguration: fmt.Sprintf("%s:%d", pod.Status.PodIP, metrics.Port), - }, - } - } - - 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, - Proxy: proxyConfigOverrides, - 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 6f60257112..0000000000 --- a/control-plane/api-gateway/binding/registration_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package binding - -import ( - "testing" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "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(common.MetricsConfig{}, 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 0b0d067df7..0000000000 --- a/control-plane/api-gateway/cache/consul.go +++ /dev/null @@ -1,592 +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 - - gatewayNameToPolicy map[string]*api.ACLPolicy - policyMutex *sync.Mutex - - gatewayNameToRole map[string]*api.ACLRole - aclRoleMutex *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{}, - gatewayNameToPolicy: make(map[string]*api.ACLPolicy), - policyMutex: &sync.Mutex{}, - gatewayNameToRole: make(map[string]*api.ACLRole), - aclRoleMutex: &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) { - c.policyMutex.Lock() - defer c.policyMutex.Unlock() - - createPolicy := func() (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 - } - - c.gatewayNameToPolicy[gatewayName] = created - return created.ID, nil - } - - cachedPolicy, found := c.gatewayNameToPolicy[gatewayName] - - if !found { - return createPolicy() - } - - existing, _, err := client.ACL().PolicyReadByName(cachedPolicy.Name, &api.QueryOptions{}) - - if existing == nil { - return createPolicy() - } - - if err != nil { - return "", err - } - - return existing.ID, nil -} - -func (c *Cache) ensureRole(client *api.Client, gatewayName string) (string, error) { - policyID, err := c.ensurePolicy(client, gatewayName) - if err != nil { - return "", err - } - - c.aclRoleMutex.Lock() - defer c.aclRoleMutex.Unlock() - - createRole := func() (string, error) { - aclRoleName := fmt.Sprint("managed-gateway-acl-role-", gatewayName) - role := &api.ACLRole{ - Name: aclRoleName, - Description: "ACL Role for Managed API Gateways", - Policies: []*api.ACLLink{{ID: policyID}}, - } - - _, _, err = client.ACL().RoleCreate(role, &api.WriteOptions{}) - if err != nil { - return "", err - } - c.gatewayNameToRole[gatewayName] = role - return aclRoleName, err - } - - cachedRole, found := c.gatewayNameToRole[gatewayName] - - if !found { - return createRole() - } - - aclRole, _, err := client.ACL().RoleReadByName(cachedRole.Name, &api.QueryOptions{}) - if err != nil { - return "", err - } - - if aclRole != nil { - return cachedRole.Name, nil - } - - return createRole() -} - -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 ecf245c04c..0000000000 --- a/control-plane/api-gateway/common/helm_config.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "strings" - "time" - - v1 "k8s.io/api/core/v1" -) - -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 - - // EnableGatewayMetrics indicates whether or not gateway metrics should be enabled - // by default on a deployed gateway, passed from the helm chart via command-line flags to our controller. - EnableGatewayMetrics bool - - // The default path to use for scraping prometheus metrics, passed from the helm chart via command-line flags to our controller. - DefaultPrometheusScrapePath string - - // The default port to use for scraping prometheus metrics, passed from the helm chart via command-line flags to our controller. - DefaultPrometheusScrapePort string - - InitContainerResources *v1.ResourceRequirements -} - -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/metrics.go b/control-plane/api-gateway/common/metrics.go deleted file mode 100644 index 8ba582c8a5..0000000000 --- a/control-plane/api-gateway/common/metrics.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "strconv" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -const ( - defaultScrapePort = 20200 - defaultScrapePath = "/metrics" -) - -type MetricsConfig struct { - Enabled bool - Path string - Port int -} - -func gatewayMetricsEnabled(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config HelmConfig) bool { - // first check our annotations, if something is there, then it means we've explicitly - // annotated metrics collection - if scrape, isSet := gateway.Annotations[constants.AnnotationEnableMetrics]; isSet { - enabled, err := strconv.ParseBool(scrape) - if err == nil { - return enabled - } - // TODO: log an error - // we fall through to the other metrics enabled checks - } - - // if it's not set on the annotation, then we check to see if it's set on the GatewayClassConfig - if gcc.Spec.Metrics.Enabled != nil { - return *gcc.Spec.Metrics.Enabled - } - - // otherwise, fallback to the global helm setting - return config.EnableGatewayMetrics -} - -func fetchPortString(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config HelmConfig) string { - // first check our annotations, if something is there, then it means we've explicitly - // annotated metrics collection - if portString, isSet := gateway.Annotations[constants.AnnotationPrometheusScrapePort]; isSet { - return portString - } - - // if it's not set on the annotation, then we check to see if it's set on the GatewayClassConfig - if gcc.Spec.Metrics.Port != nil { - return strconv.Itoa(int(*gcc.Spec.Metrics.Port)) - } - - // otherwise, fallback to the global helm setting - return config.DefaultPrometheusScrapePort -} - -func gatewayMetricsPort(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config HelmConfig) int { - portString := fetchPortString(gateway, gcc, config) - - port, err := strconv.Atoi(portString) - if err != nil { - // if we can't parse the port string, just use the default - // TODO: log an error - return defaultScrapePort - } - - if port < 1024 || port > 65535 { - // if we requested a privileged port, use the default - // TODO: log an error - return defaultScrapePort - } - - return port -} - -func gatewayMetricsPath(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config HelmConfig) string { - // first check our annotations, if something is there, then it means we've explicitly - // annotated metrics collection - if path, isSet := gateway.Annotations[constants.AnnotationPrometheusScrapePath]; isSet { - return path - } - - // if it's not set on the annotation, then we check to see if it's set on the GatewayClassConfig - if gcc.Spec.Metrics.Path != nil { - return *gcc.Spec.Metrics.Path - } - - // otherwise, fallback to the global helm setting - return config.DefaultPrometheusScrapePath -} - -func GatewayMetricsConfig(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config HelmConfig) MetricsConfig { - return MetricsConfig{ - Enabled: gatewayMetricsEnabled(gateway, gcc, config), - Path: gatewayMetricsPath(gateway, gcc, config), - Port: gatewayMetricsPort(gateway, gcc, config), - } -} 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 537100fd70..0000000000 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ /dev/null @@ -1,1291 +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, - HelmConfig: r.HelmConfig, - }) - - 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 16839cbb09..0000000000 --- a/control-plane/api-gateway/gatekeeper/dataplane.go +++ /dev/null @@ -1,187 +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 - defaultEnvoyProxyConcurrency = 1 - volumeName = "consul-connect-inject-data" -) - -func consulDataplaneContainer(metrics common.MetricsConfig, 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(metrics, 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 metrics.Enabled { - container.Ports = append(container.Ports, corev1.ContainerPort{ - Name: "prometheus", - ContainerPort: int32(metrics.Port), - Protocol: corev1.ProtocolTCP, - }) - } - - // 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(metrics common.MetricsConfig, 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)) - - if metrics.Enabled { - // Set up metrics collection. - args = append(args, "-telemetry-prom-scrape-path="+metrics.Path) - } - - 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 d3bb56a69f..0000000000 --- a/control-plane/api-gateway/gatekeeper/deployment.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -import ( - "context" - "strconv" - - "github.com/google/go-cmp/cmp" - 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 - } - - annotations := map[string]string{ - "consul.hashicorp.com/connect-inject": "false", - constants.AnnotationGatewayConsulServiceName: gateway.Name, - constants.AnnotationGatewayKind: "api-gateway", - } - - metrics := common.GatewayMetricsConfig(gateway, gcc, config) - - if metrics.Enabled { - annotations[constants.AnnotationPrometheusScrape] = "true" - annotations[constants.AnnotationPrometheusPath] = metrics.Path - annotations[constants.AnnotationPrometheusPort] = strconv.Itoa(metrics.Port) - } - - container, err := consulDataplaneContainer(metrics, 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: annotations, - }, - 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 -} - -// compareDeployments determines whether two Deployments are equal for all -// of the fields that we care about. There are some differences between a -// Deployment returned by the Kubernetes API and one that we would create -// in memory which are perfectly fine. We want to ignore those differences. -func compareDeployments(a, b *appsv1.Deployment) bool { - if len(b.Spec.Template.Spec.InitContainers) != len(a.Spec.Template.Spec.InitContainers) { - return false - } - - for i, containerA := range a.Spec.Template.Spec.InitContainers { - containerB := b.Spec.Template.Spec.InitContainers[i] - if !cmp.Equal(containerA.Resources.Limits, containerB.Resources.Limits) { - return false - } - - if !cmp.Equal(containerA.Resources.Requests, containerB.Resources.Requests) { - return false - } - } - - 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/deployment_test.go b/control-plane/api-gateway/gatekeeper/deployment_test.go deleted file mode 100644 index 56a0fc8327..0000000000 --- a/control-plane/api-gateway/gatekeeper/deployment_test.go +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatekeeper - -import ( - "testing" - - "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" -) - -func Test_compareDeployments(t *testing.T) { - testCases := []struct { - name string - a, b *appsv1.Deployment - shouldBeEqual bool - }{ - { - name: "zero-state deployments", - a: &appsv1.Deployment{}, - b: &appsv1.Deployment{}, - shouldBeEqual: true, - }, - { - name: "different replicas", - a: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - }, - }, - b: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(2)), - }, - }, - shouldBeEqual: false, - }, - { - name: "same replicas", - a: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - }, - }, - b: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - }, - }, - shouldBeEqual: true, - }, - { - name: "different init container resources", - a: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - InitContainers: []corev1.Container{ - { - Resources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - "cpu": requireQuantity(t, "111m"), - "memory": requireQuantity(t, "111Mi"), - }, - }, - }, - }, - }, - }, - }, - }, - b: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - InitContainers: []corev1.Container{ - { - Resources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - "cpu": requireQuantity(t, "222m"), - "memory": requireQuantity(t, "111Mi"), - }, - }, - }, - }, - }, - }, - }, - }, - shouldBeEqual: false, - }, - { - name: "same init container resources", - a: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - InitContainers: []corev1.Container{ - { - Resources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - "cpu": requireQuantity(t, "111m"), - "memory": requireQuantity(t, "111Mi"), - }, - }, - }, - }, - }, - }, - }, - }, - b: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - InitContainers: []corev1.Container{ - { - Resources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - "cpu": requireQuantity(t, "111m"), - "memory": requireQuantity(t, "111Mi"), - }, - }, - }, - }, - }, - }, - }, - }, - shouldBeEqual: true, - }, - { - name: "different container ports", - a: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Ports: []corev1.ContainerPort{ - {ContainerPort: 7070}, - {ContainerPort: 9090}, - }, - }, - }, - }, - }, - }, - }, - b: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Ports: []corev1.ContainerPort{ - {ContainerPort: 8080}, - {ContainerPort: 9090}, - }, - }, - }, - }, - }, - }, - }, - shouldBeEqual: false, - }, - { - name: "same container ports", - a: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Ports: []corev1.ContainerPort{ - {ContainerPort: 8080}, - {ContainerPort: 9090}, - }, - }, - }, - }, - }, - }, - }, - b: &appsv1.Deployment{ - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Ports: []corev1.ContainerPort{ - {ContainerPort: 8080}, - {ContainerPort: 9090}, - }, - }, - }, - }, - }, - }, - }, - shouldBeEqual: true, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - if testCase.shouldBeEqual { - assert.True(t, compareDeployments(testCase.a, testCase.b), "expected deployments to be equal but they were not") - } else { - assert.False(t, compareDeployments(testCase.a, testCase.b), "expected deployments to be different but they were not") - } - }) - } -} 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 8c0d842d49..0000000000 --- a/control-plane/api-gateway/gatekeeper/gatekeeper_test.go +++ /dev/null @@ -1,1560 +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" - "k8s.io/apimachinery/pkg/api/resource" - 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", - } - - // These annotations are used for testing that annotations stay on the service after reconcile. - copyAnnotationKey = "copy-this-annotation" - copyAnnotations = map[string]string{ - copyAnnotationKey: "copy-this-annotation-value", - } - externalAnnotations = map[string]string{ - "external-annotation": "external-annotation-value", - } - externalAndCopyAnnotations = map[string]string{ - "external-annotation": "external-annotation-value", - copyAnnotationKey: "copy-this-annotation-value", - } - - 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 - - // This is used to ignore the timestamp on the service when comparing the final resources - // This is useful for testing an update on a service - ignoreTimestampOnService bool -} - -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, - InitContainerResources: &corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: requireQuantity(t, "100m"), - corev1.ResourceMemory: requireQuantity(t, "2Gi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: requireQuantity(t, "100m"), - corev1.ResourceMemory: requireQuantity(t, "2Gi"), - }, - }, - }, - 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", false, false), - }, - 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", false, false), - }, - 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", false, false), - }, - 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", true, false), - }, - 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", false, false), - }, - serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), - }, - }, - ignoreTimestampOnService: true, - }, - "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", true, false), - }, - 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", false, false), - }, - serviceAccounts: []*corev1.ServiceAccount{ - configureServiceAccount(name, namespace, labels, "1"), - }, - }, - ignoreTimestampOnService: true, - }, - "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{}, - }, - }, - "updating a gateway deployment respects the labels and annotations a user has set": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Annotations: copyAnnotations, - }, - 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{Service: []string{copyAnnotationKey}}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - services: []*corev1.Service{ - configureService(name, namespace, labels, externalAnnotations, (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", true, false), - }, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{}, - roles: []*rbac.Role{}, - services: []*corev1.Service{ - configureService(name, namespace, labels, externalAndCopyAnnotations, (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", false, false), - }, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - ignoreTimestampOnService: true, - }, - "updating a gateway that has copy-annotations and labels doesn't panic if another controller has removed them all": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Annotations: copyAnnotations, - }, - 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{Service: []string{copyAnnotationKey}}, - ServiceType: (*corev1.ServiceType)(common.PointerTo("NodePort")), - }, - }, - helmConfig: common.HelmConfig{ - ImageDataplane: dataplaneImage, - }, - initialResources: resources{ - services: []*corev1.Service{ - configureService(name, namespace, nil, 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", true, false), - }, - }, - finalResources: resources{ - deployments: []*appsv1.Deployment{}, - roles: []*rbac.Role{}, - services: []*corev1.Service{ - configureService(name, namespace, labels, copyAnnotations, (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", false, false), - }, - serviceAccounts: []*corev1.ServiceAccount{}, - }, - ignoreTimestampOnService: true, - }, - "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.helmConfig, tc.finalResources, tc.ignoreTimestampOnService)) - }) - } -} - -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", true, false), - }, - 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", true, false), - }, - 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.helmConfig, tc.finalResources, false)) - 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, helmConfig common.HelmConfig, resources resources, ignoreTimestampOnService bool) 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 an init container - hasInitContainer := false - for _, container := range actual.Spec.Template.Spec.InitContainers { - if container.Name == injectInitContainerName { - hasInitContainer = true - - // If the Helm config specifies init container resources, verify they are set - if helmConfig.InitContainerResources != nil { - assert.Equal(t, helmConfig.InitContainerResources.Limits, container.Resources.Limits) - assert.Equal(t, helmConfig.InitContainerResources.Requests, container.Resources.Requests) - } - } - } - assert.True(t, hasInitContainer) - - // 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 - - if ignoreTimestampOnService { - expected.CreationTimestamp = actual.CreationTimestamp - } - - 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, isInitialResource, addExternalLabel bool) *corev1.Service { - - // This is used only to test that any external labels added to the service - // are not removed on reconcile - combinedLabels := labels - if addExternalLabel { - combinedLabels["extra-label"] = "extra-label-value" - } - - service := corev1.Service{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: combinedLabels, - 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, - }, - } - - if isInitialResource { - service.ObjectMeta.CreationTimestamp = metav1.Now() - } - - return &service -} - -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), - }, - }, - }, - } -} - -func requireQuantity(t *testing.T, v string) resource.Quantity { - quantity, err := resource.ParseQuantity(v) - require.NoError(t, err) - return quantity -} diff --git a/control-plane/api-gateway/gatekeeper/init.go b/control-plane/api-gateway/gatekeeper/init.go deleted file mode 100644 index 1cd616bfc9..0000000000 --- a/control-plane/api-gateway/gatekeeper/init.go +++ /dev/null @@ -1,203 +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" - - "k8s.io/utils/pointer" - - "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" -) - -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, - }) - } - - if config.InitContainerResources != nil { - container.Resources = *config.InitContainerResources - } - - // 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 e78afc239b..0000000000 --- a/control-plane/api-gateway/gatekeeper/service.go +++ /dev/null @@ -1,176 +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" - 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}) - } - - desiredService := g.service(gateway, gcc) - - existingService := desiredService.DeepCopy() - mutator := newServiceMutator(existingService, desiredService, gateway, g.Client.Scheme()) - - result, err := controllerutil.CreateOrUpdate(ctx, g.Client, existingService, 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 `existing` Service -// to the `desired` service. This prevents an infinite reconciliation loop when -// Kubernetes adds this configuration back in. -func mergeServiceInto(existing, desired *corev1.Service) { - duplicate := existing.DeepCopy() - - // Reset the existing object in kubernetes to have the same base spec as - // our generated service. - existing.Spec = desired.Spec - - // For NodePort services, kubernetes will internally set the ports[*].NodePort - // we don't want to override that, so reset it to what exists in the store. - if hasEqualPorts(duplicate, desired) { - existing.Spec.Ports = duplicate.Spec.Ports - } - - // If the Service already exists, add any desired annotations + labels to existing set - - // Note: the annotations could be empty if an external controller decided to remove them all - // do not want to panic in that case. - if existing.ObjectMeta.Annotations == nil { - existing.Annotations = desired.Annotations - } else { - for k, v := range desired.ObjectMeta.Annotations { - existing.ObjectMeta.Annotations[k] = v - } - } - - // Note: the labels could be empty if an external controller decided to remove them all - // do not want to panic in that case. - if existing.ObjectMeta.Labels == nil { - existing.Labels = desired.Labels - } else { - for k, v := range desired.ObjectMeta.Labels { - existing.ObjectMeta.Labels[k] = v - } - } -} - -// hasEqualPorts does a fuzzy comparison of the ports on a service spec -// ignoring any fields set internally by Kubernetes. -func hasEqualPorts(a, b *corev1.Service) bool { - 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(existing, desired *corev1.Service, gateway gwv1beta1.Gateway, scheme *runtime.Scheme) resourceMutator { - return func() error { - mergeServiceInto(existing, desired) - return ctrl.SetControllerReference(&gateway, existing, 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 f237fd3e12..0000000000 --- a/control-plane/api/auth/v2beta1/traffic_permissions_types.go +++ /dev/null @@ -1,237 +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" -) - -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, - }, - } -} - -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 85bedb6c40..0000000000 --- a/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go +++ /dev/null @@ -1,1040 +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, - }, - }, - 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, - }, - 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..2c579ba715 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -4,42 +4,16 @@ // 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 +26,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/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 18bd4ad5b1..0000000000 --- a/control-plane/api/mesh/v2beta1/api_gateway_types.go +++ /dev/null @@ -1,205 +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" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" -) - -const ( - apiGatewayKubeKind = "gateway" -) - -func init() { - MeshSchemeBuilder.Register(&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 `json:"status,omitempty"` - Addresses []GatewayAddress `json:"addresses,omitempty"` - Listeners []ListenerStatus `json:"listeners,omitempty"` -} - -type ListenerStatus struct { - Status `json:"status,omitempty"` - 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 *APIGatewayList) ReconcileRequests() []reconcile.Request { - requests := make([]reconcile.Request, 0, len(in.Items)) - - for _, item := range in.Items { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: item.Name, - Namespace: item.Namespace, - }, - }) - } - return requests -} - -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, - }, - } -} - -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) {} - -// ListenersToServicePorts converts the APIGateway listeners to ServicePorts. -func (in *APIGateway) ListenersToServicePorts(portModifier int32) []corev1.ServicePort { - ports := []corev1.ServicePort{} - - for _, listener := range in.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 -} - -func (in *APIGateway) ListenersToContainerPorts(_ int32, _ int32) []corev1.ContainerPort { - // TODO: check if this is actually needed: we don't map any container ports in v1 - return []corev1.ContainerPort{} -} 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 1678a14b55..0000000000 --- a/control-plane/api/mesh/v2beta1/gateway_class_config_types.go +++ /dev/null @@ -1,171 +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" -) - -const KindGatewayClassConfig = "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="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 4e82e0a6b1..0000000000 --- a/control-plane/api/mesh/v2beta1/gateway_class_types.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const KindGatewayClass = "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="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 GatewayClassSpec `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"` -} - -type GatewayClassSpec struct { - // ControllerName is the name of the Kubernetes controller - // that manages Gateways of this class - ControllerName string `json:"controllerName"` - - // ParametersRef refers to a resource responsible for configuring - // the behavior of the GatewayClass. - ParametersRef *ParametersReference `json:"parametersRef"` - - // Description of GatewayClass - Description string `json:"description,omitempty"` -} - -type ParametersReference struct { - // The Kubernetes Group that the referred object belongs to - Group string `json:"group,omitempty"` - - // The Kubernetes Kind that the referred object is - Kind string `json:"kind,omitempty"` - - // The Name of the referred object - Name string `json:"name"` - - // The kubernetes namespace that the referred object is in - Namespace *string `json:"namespace,omitempty"` -} 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 16c6725cf9..0000000000 --- a/control-plane/api/mesh/v2beta1/grpc_route_types.go +++ /dev/null @@ -1,322 +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" -) - -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, - }, - } -} - -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 c5ae7864de..0000000000 --- a/control-plane/api/mesh/v2beta1/grpc_route_types_test.go +++ /dev/null @@ -1,1193 +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, - }, - }, - 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, - }, - 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 dd8e0848f4..0000000000 --- a/control-plane/api/mesh/v2beta1/http_route_types.go +++ /dev/null @@ -1,304 +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" -) - -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, - }, - } -} - -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 7c9996f185..0000000000 --- a/control-plane/api/mesh/v2beta1/http_route_types_test.go +++ /dev/null @@ -1,1330 +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, - }, - }, - 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, - }, - 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 32a19ae2e7..0000000000 --- a/control-plane/api/mesh/v2beta1/mesh_configuration_types.go +++ /dev/null @@ -1,145 +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" - 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, - }, - } -} - -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 4531a2089e..0000000000 --- a/control-plane/api/mesh/v2beta1/mesh_gateway_types.go +++ /dev/null @@ -1,199 +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" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" -) - -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 *MeshGatewayList) ReconcileRequests() []reconcile.Request { - requests := make([]reconcile.Request, 0, len(in.Items)) - - for _, item := range in.Items { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: item.Name, - Namespace: item.Namespace, - }, - }) - } - return requests -} - -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 - }, - } -} - -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) {} - -// ListenersToServicePorts converts the MeshGateway listeners to ServicePorts. -func (in *MeshGateway) ListenersToServicePorts(portModifier int32) []corev1.ServicePort { - ports := []corev1.ServicePort{} - - for _, listener := range in.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 -} - -func (in *MeshGateway) ListenersToContainerPorts(portModifier int32, hostPort int32) []corev1.ContainerPort { - ports := []corev1.ContainerPort{} - - for _, listener := range in.Spec.Listeners { - port := int32(listener.Port) - - ports = append(ports, corev1.ContainerPort{ - Name: listener.Name, - ContainerPort: port + portModifier, - HostPort: hostPort, - Protocol: corev1.Protocol(listener.Protocol), - }) - } - return ports -} 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 cc1b5db9bd..0000000000 --- a/control-plane/api/mesh/v2beta1/proxy_configuration_types.go +++ /dev/null @@ -1,155 +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" -) - -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, - }, - } -} - -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 55e6ce45a9..0000000000 --- a/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go +++ /dev/null @@ -1,543 +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, - }, - }, - 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, - }, - 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 f5fa2888d6..0000000000 --- a/control-plane/api/mesh/v2beta1/tcp_route_types.go +++ /dev/null @@ -1,190 +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" -) - -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, - }, - } -} - -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 f03e5232db..0000000000 --- a/control-plane/api/mesh/v2beta1/tcp_route_types_test.go +++ /dev/null @@ -1,564 +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", - }, - 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", - }, - 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, - }, - }, - 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", - }, - 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", - }, - 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", - }, - 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, - }, - 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 d4ca224b61..0000000000 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,1039 +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.APIGatewayStatus.DeepCopyInto(&out.APIGatewayStatus) -} - -// 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 *APIGatewayStatus) DeepCopyInto(out *APIGatewayStatus) { - *out = *in - in.Status.DeepCopyInto(&out.Status) - if in.Addresses != nil { - in, out := &in.Addresses, &out.Addresses - *out = make([]GatewayAddress, len(*in)) - copy(*out, *in) - } - if in.Listeners != nil { - in, out := &in.Listeners, &out.Listeners - *out = make([]ListenerStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGatewayStatus. -func (in *APIGatewayStatus) DeepCopy() *APIGatewayStatus { - if in == nil { - return nil - } - out := new(APIGatewayStatus) - 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 - 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 *GatewayAddress) DeepCopyInto(out *GatewayAddress) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayAddress. -func (in *GatewayAddress) DeepCopy() *GatewayAddress { - if in == nil { - return nil - } - out := new(GatewayAddress) - in.DeepCopyInto(out) - return out -} - -// 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 *GatewayClassSpec) DeepCopyInto(out *GatewayClassSpec) { - *out = *in - if in.ParametersRef != nil { - in, out := &in.ParametersRef, &out.ParametersRef - *out = new(ParametersReference) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayClassSpec. -func (in *GatewayClassSpec) DeepCopy() *GatewayClassSpec { - if in == nil { - return nil - } - out := new(GatewayClassSpec) - 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 *ListenerStatus) DeepCopyInto(out *ListenerStatus) { - *out = *in - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerStatus. -func (in *ListenerStatus) DeepCopy() *ListenerStatus { - if in == nil { - return nil - } - out := new(ListenerStatus) - in.DeepCopyInto(out) - return out -} - -// 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 *ParametersReference) DeepCopyInto(out *ParametersReference) { - *out = *in - if in.Namespace != nil { - in, out := &in.Namespace, &out.Namespace - *out = new(string) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParametersReference. -func (in *ParametersReference) DeepCopy() *ParametersReference { - if in == nil { - return nil - } - out := new(ParametersReference) - in.DeepCopyInto(out) - return out -} - -// 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/v2/exported_services_types.go b/control-plane/api/multicluster/v2/exported_services_types.go deleted file mode 100644 index 032e10bd03..0000000000 --- a/control-plane/api/multicluster/v2/exported_services_types.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 -package v2 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmulticluster "github.com/hashicorp/consul/proto-public/pbmulticluster/v2" - "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" -) - -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 - }, - } -} - -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/v2/multicluster_groupversion_info.go b/control-plane/api/multicluster/v2/multicluster_groupversion_info.go deleted file mode 100644 index 7b8f92f4ac..0000000000 --- a/control-plane/api/multicluster/v2/multicluster_groupversion_info.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Package v2 contains API Schema definitions for the consul.hashicorp.com v2 API group -// +kubebuilder:object:generate=true -// +groupName=multicluster.consul.hashicorp.com -package v2 - -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: "v2"} - - // 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/v2/shared_types.go b/control-plane/api/multicluster/v2/shared_types.go deleted file mode 100644 index 04f9fcefa4..0000000000 --- a/control-plane/api/multicluster/v2/shared_types.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2 - -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/v2/status.go b/control-plane/api/multicluster/v2/status.go deleted file mode 100644 index 070c57a7d9..0000000000 --- a/control-plane/api/multicluster/v2/status.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2 - -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/v2/zz_generated.deepcopy.go b/control-plane/api/multicluster/v2/zz_generated.deepcopy.go deleted file mode 100644 index c52d2bfe81..0000000000 --- a/control-plane/api/multicluster/v2/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 v2 - -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 bc7b65fbe4..0000000000 --- a/control-plane/api/v1alpha1/api_gateway_types.go +++ /dev/null @@ -1,163 +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"` - - // Metrics defines how to configure the metrics for a gateway. - Metrics MetricsSpec `json:"metrics,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"` -} - -// +k8s:deepcopy-gen=true - -type MetricsSpec struct { - // +kubebuilder:validation:Maximum=65535 - // +kubebuilder:validation:Minimum=1024 - // The port used for metrics. - Port *int32 `json:"port,omitempty"` - - // The path used for metrics. - Path *string `json:"path,omitempty"` - - // Enable metrics for this class of gateways. If unspecified, will inherit - // behavior from the global Helm configuration. - Enabled *bool `json:"enabled,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..862e4fb87d 100644 --- a/control-plane/api/v1alpha1/exportedservices_types.go +++ b/control-plane/api/v1alpha1/exportedservices_types.go @@ -20,7 +20,6 @@ import ( ) const ExportedServicesKubeKind = "exportedservices" -const WildcardSpecifier = "*" func init() { SchemeBuilder.Register(&ExportedServices{}, &ExportedServicesList{}) @@ -73,10 +72,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 +170,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{ @@ -242,34 +238,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..5e7834485e 100644 --- a/control-plane/api/v1alpha1/exportedservices_types_test.go +++ b/control-plane/api/v1alpha1/exportedservices_types_test.go @@ -60,9 +60,6 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "second-peer", }, - { - SamenessGroup: "sg1", - }, }, }, { @@ -78,9 +75,6 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "third-peer", }, - { - SamenessGroup: "sg2", - }, }, }, }, @@ -102,10 +96,6 @@ func TestExportedServices_MatchesConsul(t *testing.T) { { Peer: "second-peer", }, - { - SamenessGroup: "sg1", - Partition: "default", - }, }, }, { @@ -119,10 +109,8 @@ func TestExportedServices_MatchesConsul(t *testing.T) { Partition: "fifth", }, { - Peer: "third-peer", - }, - { - SamenessGroup: "sg2", + Peer: "third-peer", + Partition: "default", }, }, }, @@ -197,9 +185,6 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "second-peer", }, - { - SamenessGroup: "sg2", - }, }, }, { @@ -215,9 +200,6 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "third-peer", }, - { - SamenessGroup: "sg3", - }, }, }, }, @@ -239,9 +221,6 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "second-peer", }, - { - SamenessGroup: "sg2", - }, }, }, { @@ -257,9 +236,6 @@ func TestExportedServices_ToConsul(t *testing.T) { { Peer: "third-peer", }, - { - SamenessGroup: "sg3", - }, }, }, }, @@ -304,9 +280,6 @@ func TestExportedServices_Validate(t *testing.T) { { Peer: "second-peer", }, - { - SamenessGroup: "sg2", - }, }, }, }, @@ -360,10 +333,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 +356,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 +381,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 +409,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 +425,6 @@ func TestExportedServices_Validate(t *testing.T) { Peer: "second-peer", }, {}, - { - SamenessGroup: "sg2", - Partition: "partition2", - }, }, }, }, @@ -539,9 +433,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..a9f1de2e83 100644 --- a/control-plane/api/v1alpha1/exportedservices_webhook.go +++ b/control-plane/api/v1alpha1/exportedservices_webhook.go @@ -9,10 +9,12 @@ import ( "net/http" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" admissionv1 "k8s.io/api/admission/v1" + ctrl "sigs.k8s.io/controller-runtime" "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 @@ -62,7 +64,7 @@ func (v *ExportedServicesWebhook) Handle(ctx context.Context, req admission.Requ return admission.Allowed(fmt.Sprintf("valid %s request", exports.KubeKind())) } -func (v *ExportedServicesWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *ExportedServicesWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-exportedservices", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/exportedservices_webhook_test.go b/control-plane/api/v1alpha1/exportedservices_webhook_test.go index 7cca7ff915..3dbfdbad33 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].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{}, @@ -175,8 +175,7 @@ func TestValidateExportedServices(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(GroupVersion, &ExportedServices{}, &ExportedServicesList{}) client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.existingResources...).Build() - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) validator := &ExportedServicesWebhook{ Client: client, 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/ingressgateway_types_test.go b/control-plane/api/v1alpha1/ingressgateway_types_test.go index 9250d4b0c6..54cfa64190 100644 --- a/control-plane/api/v1alpha1/ingressgateway_types_test.go +++ b/control-plane/api/v1alpha1/ingressgateway_types_test.go @@ -12,7 +12,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" + "k8s.io/utils/ptr" "github.com/hashicorp/consul-k8s/control-plane/api/common" ) @@ -77,8 +77,8 @@ func TestIngressGateway_MatchesConsul(t *testing.T) { Duration: 2 * time.Second, }, MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), + EnforcingConsecutive5xx: ptr.To(uint32(100)), + MaxEjectionPercent: ptr.To(uint32(10)), BaseEjectionTime: &metav1.Duration{ Duration: 10 * time.Second, }, @@ -185,9 +185,9 @@ func TestIngressGateway_MatchesConsul(t *testing.T) { PassiveHealthCheck: &capi.PassiveHealthCheck{ Interval: 2 * time.Second, MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), - BaseEjectionTime: pointer.Duration(10 * time.Second), + EnforcingConsecutive5xx: ptr.To(uint32(100)), + MaxEjectionPercent: ptr.To(uint32(10)), + BaseEjectionTime: ptr.To(10 * time.Second), }, }, Listeners: []capi.IngressListener{ @@ -356,8 +356,8 @@ func TestIngressGateway_ToConsul(t *testing.T) { Duration: 2 * time.Second, }, MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), + EnforcingConsecutive5xx: ptr.To(uint32(100)), + MaxEjectionPercent: ptr.To(uint32(10)), BaseEjectionTime: &metav1.Duration{ Duration: 10 * time.Second, }, @@ -464,9 +464,9 @@ func TestIngressGateway_ToConsul(t *testing.T) { PassiveHealthCheck: &capi.PassiveHealthCheck{ Interval: 2 * time.Second, MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), - BaseEjectionTime: pointer.Duration(10 * time.Second), + EnforcingConsecutive5xx: ptr.To(uint32(100)), + MaxEjectionPercent: ptr.To(uint32(10)), + BaseEjectionTime: ptr.To(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..04b9fb16cf 100644 --- a/control-plane/api/v1alpha1/ingressgateway_webhook.go +++ b/control-plane/api/v1alpha1/ingressgateway_webhook.go @@ -8,9 +8,11 @@ import ( "net/http" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" + ctrl "sigs.k8s.io/controller-runtime" "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 @@ -55,7 +57,7 @@ func (v *IngressGatewayWebhook) List(ctx context.Context) ([]common.ConfigEntryR return entries, nil } -func (v *IngressGatewayWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *IngressGatewayWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-ingressgateway", &admission.Webhook{Handler: v}) } 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..345b4b17e5 100644 --- a/control-plane/api/v1alpha1/mesh_types.go +++ b/control-plane/api/v1alpha1/mesh_types.go @@ -51,15 +51,19 @@ 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. HTTP *MeshHTTPConfig `json:"http,omitempty"` // Peering defines the peering configuration for the service mesh. Peering *PeeringMeshConfig `json:"peering,omitempty"` + // ValidateClusters controls whether the clusters the route table refers to are validated. The default value is + // false. When set to false and a route refers to a cluster that does not exist, the route table loads and routing + // to a non-existent cluster results in a 404. When set to true and the route is set to a cluster that do not exist, + // the route table will not load. For more information, refer to + // [HTTP route configuration in the Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route.proto#envoy-v3-api-field-config-route-v3-routeconfiguration-validate-clusters) + // for more details. + ValidateClusters bool `json:"validateClusters,omitempty"` } // TransparentProxyMeshConfig controls configuration specific to proxies in "transparent" mode. Added in v1.10.0. @@ -195,12 +199,12 @@ 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(), + ValidateClusters: in.Spec.ValidateClusters, + 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..817406bc1b 100644 --- a/control-plane/api/v1alpha1/mesh_types_test.go +++ b/control-plane/api/v1alpha1/mesh_types_test.go @@ -48,7 +48,7 @@ func TestMesh_MatchesConsul(t *testing.T) { TransparentProxy: TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, - AllowEnablingPermissiveMutualTLS: true, + ValidateClusters: true, TLS: &MeshTLSConfig{ Incoming: &MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -73,7 +73,7 @@ func TestMesh_MatchesConsul(t *testing.T) { TransparentProxy: capi.TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, - AllowEnablingPermissiveMutualTLS: true, + ValidateClusters: true, TLS: &capi.MeshTLSConfig{ Incoming: &capi.MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -150,7 +150,7 @@ func TestMesh_ToConsul(t *testing.T) { TransparentProxy: TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, - AllowEnablingPermissiveMutualTLS: true, + ValidateClusters: true, TLS: &MeshTLSConfig{ Incoming: &MeshDirectionalTLSConfig{ TLSMinVersion: "TLSv1_0", @@ -175,7 +175,7 @@ func TestMesh_ToConsul(t *testing.T) { TransparentProxy: capi.TransparentProxyMeshConfig{ MeshDestinationsOnly: true, }, - AllowEnablingPermissiveMutualTLS: true, + ValidateClusters: 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..58d5ec46f8 100644 --- a/control-plane/api/v1alpha1/mesh_webhook.go +++ b/control-plane/api/v1alpha1/mesh_webhook.go @@ -9,10 +9,12 @@ import ( "net/http" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" admissionv1 "k8s.io/api/admission/v1" + ctrl "sigs.k8s.io/controller-runtime" "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 @@ -79,7 +81,7 @@ func (v *MeshWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, e return entries, nil } -func (v *MeshWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *MeshWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-mesh", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/mesh_webhook_test.go b/control-plane/api/v1alpha1/mesh_webhook_test.go index 2266a6b77e..63ec814a48 100644 --- a/control-plane/api/v1alpha1/mesh_webhook_test.go +++ b/control-plane/api/v1alpha1/mesh_webhook_test.go @@ -9,13 +9,14 @@ import ( "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" 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 TestValidateMesh(t *testing.T) { @@ -92,8 +93,7 @@ func TestValidateMesh(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(GroupVersion, &Mesh{}, &MeshList{}) client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.existingResources...).Build() - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) validator := &MeshWebhook{ Client: client, diff --git a/control-plane/api/v1alpha1/peeringacceptor_webhook.go b/control-plane/api/v1alpha1/peeringacceptor_webhook.go index 2bb48b3580..ed520e700c 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_webhook.go +++ b/control-plane/api/v1alpha1/peeringacceptor_webhook.go @@ -10,6 +10,7 @@ import ( "github.com/go-logr/logr" admissionv1 "k8s.io/api/admission/v1" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) @@ -63,7 +64,7 @@ func (v *PeeringAcceptorWebhook) Handle(ctx context.Context, req admission.Reque return admission.Allowed(fmt.Sprintf("valid %s request", acceptor.KubeKind())) } -func (v *PeeringAcceptorWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *PeeringAcceptorWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringacceptors", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go b/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go index 251ab87c8e..c2027b468c 100644 --- a/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go +++ b/control-plane/api/v1alpha1/peeringacceptor_webhook_test.go @@ -133,8 +133,7 @@ func TestValidatePeeringAcceptor(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(GroupVersion, &PeeringAcceptor{}, &PeeringAcceptorList{}) client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.existingResources...).Build() - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) validator := &PeeringAcceptorWebhook{ Client: client, diff --git a/control-plane/api/v1alpha1/peeringdialer_webhook.go b/control-plane/api/v1alpha1/peeringdialer_webhook.go index 76c2011e4c..ac77f87a4f 100644 --- a/control-plane/api/v1alpha1/peeringdialer_webhook.go +++ b/control-plane/api/v1alpha1/peeringdialer_webhook.go @@ -10,6 +10,7 @@ import ( "github.com/go-logr/logr" admissionv1 "k8s.io/api/admission/v1" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) @@ -62,7 +63,7 @@ func (v *PeeringDialerWebhook) Handle(ctx context.Context, req admission.Request return admission.Allowed(fmt.Sprintf("valid %s request", dialer.KubeKind())) } -func (v *PeeringDialerWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *PeeringDialerWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringdialers", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/peeringdialer_webhook_test.go b/control-plane/api/v1alpha1/peeringdialer_webhook_test.go index 42372451d1..a8981d5ed2 100644 --- a/control-plane/api/v1alpha1/peeringdialer_webhook_test.go +++ b/control-plane/api/v1alpha1/peeringdialer_webhook_test.go @@ -133,8 +133,7 @@ func TestValidatePeeringDialer(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(GroupVersion, &PeeringDialer{}, &PeeringDialerList{}) client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.existingResources...).Build() - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) validator := &PeeringDialerWebhook{ Client: client, diff --git a/control-plane/api/v1alpha1/proxydefaults_types.go b/control-plane/api/v1alpha1/proxydefaults_types.go index 7b1529b941..9a3e97de90 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types.go +++ b/control-plane/api/v1alpha1/proxydefaults_types.go @@ -9,7 +9,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" @@ -17,6 +16,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 ( @@ -67,17 +68,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 @@ -93,11 +83,6 @@ 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"` - // 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 +167,15 @@ 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(), + AccessLogs: in.Spec.AccessLogs.toConsul(), + EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), + Meta: meta(datacenter), } } @@ -217,9 +199,6 @@ 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) } @@ -231,8 +210,6 @@ 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 6a965fce3a..011ab7d724 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -74,7 +74,6 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: MutualTLSModePermissive, AccessLogs: &AccessLogs{ Enabled: true, DisableListenerLogs: true, @@ -94,13 +93,6 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { Required: true, }, }, - FailoverPolicy: &FailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-1"}, - }, - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "failover", - }, }, }, Theirs: &capi.ProxyConfigEntry{ @@ -133,7 +125,6 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: capi.MutualTLSModePermissive, AccessLogs: &capi.AccessLogsConfig{ Enabled: true, DisableListenerLogs: true, @@ -160,13 +151,6 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { Required: true, }, }, - FailoverPolicy: &capi.ServiceResolverFailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-1"}, - }, - PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ - Mode: "failover", - }, }, Matches: true, }, @@ -300,7 +284,6 @@ func TestProxyDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: MutualTLSModeStrict, AccessLogs: &AccessLogs{ Enabled: true, DisableListenerLogs: true, @@ -320,13 +303,6 @@ func TestProxyDefaults_ToConsul(t *testing.T) { Required: true, }, }, - FailoverPolicy: &FailoverPolicy{ - Mode: "sequential", - Regions: []string{"us-west-1"}, - }, - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "none", - }, }, }, Exp: &capi.ProxyConfigEntry{ @@ -360,7 +336,6 @@ func TestProxyDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: capi.MutualTLSModeStrict, AccessLogs: &capi.AccessLogsConfig{ Enabled: true, DisableListenerLogs: true, @@ -387,13 +362,6 @@ func TestProxyDefaults_ToConsul(t *testing.T) { 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", @@ -513,17 +481,6 @@ 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{ @@ -651,32 +608,6 @@ 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"`, - }, - "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/proxydefaults_webhook.go b/control-plane/api/v1alpha1/proxydefaults_webhook.go index ced4853b12..c446e97344 100644 --- a/control-plane/api/v1alpha1/proxydefaults_webhook.go +++ b/control-plane/api/v1alpha1/proxydefaults_webhook.go @@ -9,10 +9,12 @@ import ( "net/http" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" admissionv1 "k8s.io/api/admission/v1" + ctrl "sigs.k8s.io/controller-runtime" "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 @@ -67,7 +69,7 @@ func (v *ProxyDefaultsWebhook) Handle(ctx context.Context, req admission.Request return admission.Allowed(fmt.Sprintf("valid %s request", proxyDefaults.KubeKind())) } -func (v *ProxyDefaultsWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *ProxyDefaultsWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-proxydefaults", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go index ade806b7e3..2b353d2488 100644 --- a/control-plane/api/v1alpha1/proxydefaults_webhook_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_webhook_test.go @@ -117,8 +117,7 @@ func TestValidateProxyDefault(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(GroupVersion, &ProxyDefaults{}, &ProxyDefaultsList{}) client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.existingResources...).Build() - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) validator := &ProxyDefaultsWebhook{ Client: client, 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..7b470a956b 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -11,6 +11,7 @@ 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" @@ -18,8 +19,6 @@ import ( "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" ) @@ -75,17 +74,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. @@ -116,9 +104,6 @@ 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"` } @@ -220,150 +205,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 } @@ -448,7 +289,6 @@ 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), @@ -456,7 +296,6 @@ 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(), } } @@ -477,9 +316,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) } @@ -505,7 +341,6 @@ 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 7cfe606385..5ce3eef8fb 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -12,7 +12,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" + "k8s.io/utils/ptr" "github.com/hashicorp/consul-k8s/control-plane/api/common" ) @@ -70,7 +70,6 @@ func TestServiceDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: MutualTLSModePermissive, UpstreamConfig: &Upstreams{ Defaults: &Upstream{ Name: "upstream-default", @@ -90,8 +89,8 @@ func TestServiceDefaults_ToConsul(t *testing.T) { Duration: 2 * time.Second, }, MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), + EnforcingConsecutive5xx: ptr.To(uint32(100)), + MaxEjectionPercent: ptr.To(uint32(10)), BaseEjectionTime: &metav1.Duration{ Duration: 10 * time.Second, }, @@ -119,8 +118,8 @@ func TestServiceDefaults_ToConsul(t *testing.T) { Duration: 2 * time.Second, }, MaxFailures: uint32(10), - EnforcingConsecutive5xx: pointer.Uint32(60), - MaxEjectionPercent: pointer.Uint32(20), + EnforcingConsecutive5xx: ptr.To(uint32(60)), + MaxEjectionPercent: ptr.To(uint32(20)), BaseEjectionTime: &metav1.Duration{ Duration: 20 * time.Second, }, @@ -147,8 +146,8 @@ func TestServiceDefaults_ToConsul(t *testing.T) { Duration: 2 * time.Second, }, MaxFailures: uint32(10), - EnforcingConsecutive5xx: pointer.Uint32(60), - MaxEjectionPercent: pointer.Uint32(30), + EnforcingConsecutive5xx: ptr.To(uint32(60)), + MaxEjectionPercent: ptr.To(uint32(30)), BaseEjectionTime: &metav1.Duration{ Duration: 30 * time.Second, }, @@ -160,23 +159,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", @@ -227,7 +209,6 @@ func TestServiceDefaults_ToConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: capi.MutualTLSModePermissive, UpstreamConfig: &capi.UpstreamConfiguration{ Defaults: &capi.UpstreamConfig{ Name: "upstream-default", @@ -245,9 +226,9 @@ func TestServiceDefaults_ToConsul(t *testing.T) { PassiveHealthCheck: &capi.PassiveHealthCheck{ Interval: 2 * time.Second, MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), - BaseEjectionTime: pointer.Duration(10 * time.Second), + EnforcingConsecutive5xx: ptr.To(uint32(100)), + MaxEjectionPercent: ptr.To(uint32(10)), + BaseEjectionTime: ptr.To(10 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "local", @@ -270,9 +251,9 @@ func TestServiceDefaults_ToConsul(t *testing.T) { PassiveHealthCheck: &capi.PassiveHealthCheck{ Interval: 2 * time.Second, MaxFailures: uint32(10), - EnforcingConsecutive5xx: pointer.Uint32(60), - MaxEjectionPercent: pointer.Uint32(20), - BaseEjectionTime: pointer.Duration(20 * time.Second), + EnforcingConsecutive5xx: ptr.To(uint32(60)), + MaxEjectionPercent: ptr.To(uint32(20)), + BaseEjectionTime: ptr.To(20 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "remote", @@ -294,9 +275,9 @@ func TestServiceDefaults_ToConsul(t *testing.T) { PassiveHealthCheck: &capi.PassiveHealthCheck{ Interval: 2 * time.Second, MaxFailures: uint32(10), - EnforcingConsecutive5xx: pointer.Uint32(60), - MaxEjectionPercent: pointer.Uint32(30), - BaseEjectionTime: pointer.Duration(30 * time.Second), + EnforcingConsecutive5xx: ptr.To(uint32(60)), + MaxEjectionPercent: ptr.To(uint32(30)), + BaseEjectionTime: ptr.To(30 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "remote", @@ -305,23 +286,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", @@ -506,7 +470,6 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: MutualTLSModeStrict, UpstreamConfig: &Upstreams{ Defaults: &Upstream{ Name: "upstream-default", @@ -525,8 +488,8 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { Duration: 2 * time.Second, }, MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), + EnforcingConsecutive5xx: ptr.To(uint32(100)), + MaxEjectionPercent: ptr.To(uint32(10)), BaseEjectionTime: &metav1.Duration{ Duration: 10 * time.Second, }, @@ -553,8 +516,8 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { Duration: 2 * time.Second, }, MaxFailures: uint32(10), - EnforcingConsecutive5xx: pointer.Uint32(60), - MaxEjectionPercent: pointer.Uint32(20), + EnforcingConsecutive5xx: ptr.To(uint32(60)), + MaxEjectionPercent: ptr.To(uint32(20)), BaseEjectionTime: &metav1.Duration{ Duration: 20 * time.Second, }, @@ -579,8 +542,8 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { Duration: 2 * time.Second, }, MaxFailures: uint32(10), - EnforcingConsecutive5xx: pointer.Uint32(60), - MaxEjectionPercent: pointer.Uint32(30), + EnforcingConsecutive5xx: ptr.To(uint32(60)), + MaxEjectionPercent: ptr.To(uint32(30)), BaseEjectionTime: &metav1.Duration{ Duration: 30 * time.Second, }, @@ -592,23 +555,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", @@ -655,7 +601,6 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { OutboundListenerPort: 1000, DialedDirectly: true, }, - MutualTLSMode: capi.MutualTLSModeStrict, UpstreamConfig: &capi.UpstreamConfiguration{ Defaults: &capi.UpstreamConfig{ Name: "upstream-default", @@ -672,9 +617,9 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { PassiveHealthCheck: &capi.PassiveHealthCheck{ Interval: 2 * time.Second, MaxFailures: uint32(20), - EnforcingConsecutive5xx: pointer.Uint32(100), - MaxEjectionPercent: pointer.Uint32(10), - BaseEjectionTime: pointer.Duration(10 * time.Second), + EnforcingConsecutive5xx: ptr.To(uint32(100)), + MaxEjectionPercent: ptr.To(uint32(10)), + BaseEjectionTime: ptr.To(10 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "local", @@ -696,9 +641,9 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { PassiveHealthCheck: &capi.PassiveHealthCheck{ Interval: 2 * time.Second, MaxFailures: uint32(10), - EnforcingConsecutive5xx: pointer.Uint32(60), - MaxEjectionPercent: pointer.Uint32(20), - BaseEjectionTime: pointer.Duration(20 * time.Second), + EnforcingConsecutive5xx: ptr.To(uint32(60)), + MaxEjectionPercent: ptr.To(uint32(20)), + BaseEjectionTime: ptr.To(20 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "remote", @@ -720,9 +665,9 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { PassiveHealthCheck: &capi.PassiveHealthCheck{ Interval: 2 * time.Second, MaxFailures: uint32(10), - EnforcingConsecutive5xx: pointer.Uint32(60), - MaxEjectionPercent: pointer.Uint32(30), - BaseEjectionTime: pointer.Duration(30 * time.Second), + EnforcingConsecutive5xx: ptr.To(uint32(60)), + MaxEjectionPercent: ptr.To(uint32(30)), + BaseEjectionTime: ptr.To(30 * time.Second), }, MeshGateway: capi.MeshGatewayConfig{ Mode: "remote", @@ -731,23 +676,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", @@ -873,7 +801,6 @@ func TestServiceDefaults_Validate(t *testing.T) { MeshGateway: MeshGateway{ Mode: "remote", }, - MutualTLSMode: MutualTLSModePermissive, Expose: Expose{ Checks: false, Paths: []ExposePath{ @@ -1009,17 +936,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{ @@ -1397,152 +1313,6 @@ 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 { @@ -1647,7 +1417,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..db66c5e9a1 100644 --- a/control-plane/api/v1alpha1/servicedefaults_webhook.go +++ b/control-plane/api/v1alpha1/servicedefaults_webhook.go @@ -8,9 +8,11 @@ import ( "net/http" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" + ctrl "sigs.k8s.io/controller-runtime" "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 @@ -55,7 +57,7 @@ func (v *ServiceDefaultsWebhook) List(ctx context.Context) ([]common.ConfigEntry return entries, nil } -func (v *ServiceDefaultsWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *ServiceDefaultsWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicedefaults", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/serviceintentions_types.go b/control-plane/api/v1alpha1/serviceintentions_types.go index d393f72a2d..04d7054f40 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types.go +++ b/control-plane/api/v1alpha1/serviceintentions_types.go @@ -5,7 +5,6 @@ package v1alpha1 import ( "encoding/json" - "fmt" "net/http" "strings" @@ -59,8 +58,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 +81,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 +105,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 +139,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 +223,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 +289,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 +302,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 +330,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 +350,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 +385,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 +478,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 +511,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..b28189c87c 100644 --- a/control-plane/api/v1alpha1/serviceintentions_types_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_types_test.go @@ -4,7 +4,6 @@ package v1alpha1 import ( - "strings" "testing" "time" @@ -165,37 +164,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 +219,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 +341,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 +368,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 +394,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 +421,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 +674,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 +724,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 +1196,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 +1415,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 +1449,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..4cae976cce 100644 --- a/control-plane/api/v1alpha1/serviceintentions_webhook.go +++ b/control-plane/api/v1alpha1/serviceintentions_webhook.go @@ -10,10 +10,12 @@ import ( "net/http" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" admissionv1 "k8s.io/api/admission/v1" + ctrl "sigs.k8s.io/controller-runtime" "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 @@ -97,7 +99,7 @@ func (v *ServiceIntentionsWebhook) Handle(ctx context.Context, req admission.Req return admission.Patched(fmt.Sprintf("valid %s request", svcIntentions.KubeKind()), defaultingPatches...) } -func (v *ServiceIntentionsWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *ServiceIntentionsWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceintentions", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/serviceintentions_webhook_test.go b/control-plane/api/v1alpha1/serviceintentions_webhook_test.go index 238ff7f33e..184e842260 100644 --- a/control-plane/api/v1alpha1/serviceintentions_webhook_test.go +++ b/control-plane/api/v1alpha1/serviceintentions_webhook_test.go @@ -10,7 +10,6 @@ import ( "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" admissionv1 "k8s.io/api/admission/v1" @@ -18,6 +17,8 @@ import ( "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 TestHandle_ServiceIntentions_Create(t *testing.T) { @@ -248,8 +249,7 @@ func TestHandle_ServiceIntentions_Create(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(GroupVersion, &ServiceIntentions{}, &ServiceIntentionsList{}) client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.existingResources...).Build() - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) validator := &ServiceIntentionsWebhook{ Client: client, @@ -437,8 +437,7 @@ func TestHandle_ServiceIntentions_Update(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(GroupVersion, &ServiceIntentions{}, &ServiceIntentionsList{}) client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.existingResources...).Build() - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) validator := &ServiceIntentionsWebhook{ Client: client, @@ -597,8 +596,7 @@ func TestHandle_ServiceIntentions_Patches(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(GroupVersion, &ServiceIntentions{}, &ServiceIntentionsList{}) client := fake.NewClientBuilder().WithScheme(s).Build() - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) validator := &ServiceIntentionsWebhook{ Client: client, diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 645cc23ac1..b8284d4e5f 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -5,19 +5,16 @@ package v1alpha1 import ( "encoding/json" - "regexp" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + 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" ) @@ -83,9 +80,6 @@ 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 *PrioritizeByLocality `json:"prioritizeByLocality,omitempty"` } type ServiceResolverRedirect struct { @@ -107,8 +101,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 +135,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 +295,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, - 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, + RequestTimeout: in.Spec.RequestTimeout.Duration, + LoadBalancer: in.Spec.LoadBalancer.toConsul(), + Meta: meta(datacenter), } } @@ -341,7 +328,6 @@ func (in *ServiceResolver) MatchesConsul(candidate capi.ConfigEntry) 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) } @@ -354,17 +340,12 @@ 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)...) @@ -392,31 +373,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,114 +391,32 @@ 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 { if in == nil { return nil } 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 +526,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")) - } - - if in.Service != "" && len(in.Targets) != 0 { - errs = append(errs, field.Invalid(path, string(asJSON), - "targets cannot be set with service")) + return field.Invalid(path, "{}", + "service, serviceSubset, namespace, datacenters, and targets cannot all be empty at once") } - - 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..493e77ca0b 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -4,7 +4,6 @@ package v1alpha1 import ( - "strings" "testing" "time" @@ -12,7 +11,6 @@ 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" "github.com/hashicorp/consul-k8s/control-plane/api/common" ) @@ -64,35 +62,21 @@ 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{ @@ -100,10 +84,6 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { {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}, @@ -151,44 +131,28 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Service: "redirect", ServiceSubset: "redirect_subset", Namespace: "redirect_namespace", + Partition: "default", 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"}, + {Partition: "default", Peer: "failover_peer4"}, }, }, }, @@ -289,41 +253,24 @@ 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}, @@ -374,41 +321,24 @@ 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, @@ -573,15 +503,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 +528,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 +545,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 +571,11 @@ func TestServiceResolver_Validate(t *testing.T) { Redirect: &ServiceResolverRedirect{ Service: "bar", }, + Failover: map[string]ServiceResolverFailover{ + "failA": { + Service: "baz", + }, + }, }, }, namespacesEnabled: false, @@ -649,13 +589,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 +606,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 +683,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 +738,6 @@ func TestServiceResolver_Validate(t *testing.T) { }, Spec: ServiceResolverSpec{ Redirect: &ServiceResolverRedirect{ - Service: "bar", Namespace: "namespace-a", }, }, @@ -845,7 +754,6 @@ func TestServiceResolver_Validate(t *testing.T) { }, Spec: ServiceResolverSpec{ Redirect: &ServiceResolverRedirect{ - Service: "bar", Namespace: "namespace-a", Partition: "other", }, @@ -864,19 +772,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 +805,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 +820,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..9582f3bbe9 100644 --- a/control-plane/api/v1alpha1/serviceresolver_webhook.go +++ b/control-plane/api/v1alpha1/serviceresolver_webhook.go @@ -8,9 +8,11 @@ import ( "net/http" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" + ctrl "sigs.k8s.io/controller-runtime" "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 @@ -55,7 +57,7 @@ func (v *ServiceResolverWebhook) List(ctx context.Context) ([]common.ConfigEntry return entries, nil } -func (v *ServiceResolverWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *ServiceResolverWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceresolver", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/servicerouter_types.go b/control-plane/api/v1alpha1/servicerouter_types.go index a06977b9e7..7054d9e9e2 100644 --- a/control-plane/api/v1alpha1/servicerouter_types.go +++ b/control-plane/api/v1alpha1/servicerouter_types.go @@ -75,8 +75,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. @@ -424,13 +422,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..f7eb766945 100644 --- a/control-plane/api/v1alpha1/servicerouter_types_test.go +++ b/control-plane/api/v1alpha1/servicerouter_types_test.go @@ -53,10 +53,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", @@ -132,10 +131,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", @@ -261,10 +259,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", @@ -340,10 +337,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", diff --git a/control-plane/api/v1alpha1/servicerouter_webhook.go b/control-plane/api/v1alpha1/servicerouter_webhook.go index cdcc3ba439..cc1f24f2bc 100644 --- a/control-plane/api/v1alpha1/servicerouter_webhook.go +++ b/control-plane/api/v1alpha1/servicerouter_webhook.go @@ -8,9 +8,11 @@ import ( "net/http" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" + ctrl "sigs.k8s.io/controller-runtime" "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 @@ -55,7 +57,7 @@ func (v *ServiceRouterWebhook) List(ctx context.Context) ([]common.ConfigEntryRe return entries, nil } -func (v *ServiceRouterWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *ServiceRouterWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicerouter", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/servicesplitter_webhook.go b/control-plane/api/v1alpha1/servicesplitter_webhook.go index 42dbbbf54a..a17f4be9fa 100644 --- a/control-plane/api/v1alpha1/servicesplitter_webhook.go +++ b/control-plane/api/v1alpha1/servicesplitter_webhook.go @@ -8,9 +8,11 @@ import ( "net/http" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" + ctrl "sigs.k8s.io/controller-runtime" "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 @@ -56,7 +58,7 @@ func (v *ServiceSplitterWebhook) List(ctx context.Context) ([]common.ConfigEntry return entries, nil } -func (v *ServiceSplitterWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *ServiceSplitterWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicesplitter", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index 148376a393..28d53b8926 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -16,9 +16,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 +55,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 { @@ -277,70 +245,6 @@ 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 - // "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/terminatinggateway_types.go b/control-plane/api/v1alpha1/terminatinggateway_types.go index d439e635fe..cf453160ff 100644 --- a/control-plane/api/v1alpha1/terminatinggateway_types.go +++ b/control-plane/api/v1alpha1/terminatinggateway_types.go @@ -79,9 +79,6 @@ type LinkedService struct { // SNI is the optional name to specify during the TLS handshake with a linked service. SNI string `json:"sni,omitempty"` - - //DisableAutoHostRewrite disables terminating gateways auto host rewrite feature when set to true. - DisableAutoHostRewrite bool `json:"disableAutoHostRewrite,omitempty"` } func (in *TerminatingGateway) GetObjectMeta() metav1.ObjectMeta { @@ -221,13 +218,12 @@ func (in *TerminatingGateway) DefaultNamespaceFields(consulMeta common.ConsulMet func (in LinkedService) toConsul() capi.LinkedService { return capi.LinkedService{ - Namespace: in.Namespace, - Name: in.Name, - CAFile: in.CAFile, - CertFile: in.CertFile, - KeyFile: in.KeyFile, - SNI: in.SNI, - DisableAutoHostRewrite: in.DisableAutoHostRewrite, + Namespace: in.Namespace, + Name: in.Name, + CAFile: in.CAFile, + CertFile: in.CertFile, + KeyFile: in.KeyFile, + SNI: in.SNI, } } diff --git a/control-plane/api/v1alpha1/terminatinggateway_types_test.go b/control-plane/api/v1alpha1/terminatinggateway_types_test.go index 02dcbc03c1..2daf93c6a4 100644 --- a/control-plane/api/v1alpha1/terminatinggateway_types_test.go +++ b/control-plane/api/v1alpha1/terminatinggateway_types_test.go @@ -49,12 +49,11 @@ func TestTerminatingGateway_MatchesConsul(t *testing.T) { Spec: TerminatingGatewaySpec{ Services: []LinkedService{ { - Name: "name", - CAFile: "caFile", - CertFile: "certFile", - KeyFile: "keyFile", - SNI: "sni", - DisableAutoHostRewrite: true, + Name: "name", + CAFile: "caFile", + CertFile: "certFile", + KeyFile: "keyFile", + SNI: "sni", }, { Name: "*", @@ -72,12 +71,11 @@ func TestTerminatingGateway_MatchesConsul(t *testing.T) { }, Services: []capi.LinkedService{ { - Name: "name", - CAFile: "caFile", - CertFile: "certFile", - KeyFile: "keyFile", - SNI: "sni", - DisableAutoHostRewrite: true, + Name: "name", + CAFile: "caFile", + CertFile: "certFile", + KeyFile: "keyFile", + SNI: "sni", }, { Name: "*", diff --git a/control-plane/api/v1alpha1/terminatinggateway_webhook.go b/control-plane/api/v1alpha1/terminatinggateway_webhook.go index 481a1a1f15..85dfe40bf7 100644 --- a/control-plane/api/v1alpha1/terminatinggateway_webhook.go +++ b/control-plane/api/v1alpha1/terminatinggateway_webhook.go @@ -8,9 +8,11 @@ import ( "net/http" "github.com/go-logr/logr" - "github.com/hashicorp/consul-k8s/control-plane/api/common" + ctrl "sigs.k8s.io/controller-runtime" "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 @@ -55,7 +57,7 @@ func (v *TerminatingGatewayWebhook) List(ctx context.Context) ([]common.ConfigEn return entries, nil } -func (v *TerminatingGatewayWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil +func (v *TerminatingGatewayWebhook) SetupWithManager(mgr ctrl.Manager) { + v.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-terminatinggateway", &admission.Webhook{Handler: v}) } diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 2a1854d178..303268425c 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated // Code generated by controller-gen. DO NOT EDIT. @@ -7,10 +6,8 @@ package v1alpha1 import ( "encoding/json" - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/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. @@ -65,146 +62,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,61 +78,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 @@ -454,194 +256,121 @@ 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) - in.Metrics.DeepCopyInto(&out.Metrics) -} - -// 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 *GatewayJWTClaimVerification) DeepCopyInto(out *GatewayJWTClaimVerification) { - *out = *in - if in.Path != nil { - in, out := &in.Path, &out.Path + 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 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 { +// 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(GatewayJWTProvider) + 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 *GatewayJWTRequirement) DeepCopyInto(out *GatewayJWTRequirement) { +func (in *HashPolicy) DeepCopyInto(out *HashPolicy) { *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) - } - } + 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 GatewayJWTRequirement. -func (in *GatewayJWTRequirement) DeepCopy() *GatewayJWTRequirement { +// 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(GatewayJWTRequirement) + 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 *GatewayPolicy) DeepCopyInto(out *GatewayPolicy) { +func (in *IngressGateway) DeepCopyInto(out *IngressGateway) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -649,18 +378,18 @@ func (in *GatewayPolicy) DeepCopyInto(out *GatewayPolicy) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicy. -func (in *GatewayPolicy) DeepCopy() *GatewayPolicy { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressGateway. +func (in *IngressGateway) DeepCopy() *IngressGateway { if in == nil { return nil } - out := new(GatewayPolicy) + out := new(IngressGateway) 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 { +func (in *IngressGateway) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -668,51 +397,31 @@ func (in *GatewayPolicy) DeepCopyObject() runtime.Object { } // 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) { +func (in *IngressGatewayList) DeepCopyInto(out *IngressGatewayList) { *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)) + *out = make([]IngressGateway, 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 { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressGatewayList. +func (in *IngressGatewayList) DeepCopy() *IngressGatewayList { if in == nil { return nil } - out := new(GatewayPolicyList) + out := new(IngressGatewayList) 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 { +func (in *IngressGatewayList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -720,228 +429,7 @@ func (in *GatewayPolicyList) DeepCopyObject() runtime.Object { } // 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 { - if in == nil { - return nil - } - out := new(IngressGateway) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IngressGateway) 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 *IngressGatewayList) DeepCopyInto(out *IngressGatewayList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]IngressGateway, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressGatewayList. -func (in *IngressGatewayList) DeepCopy() *IngressGatewayList { - if in == nil { - return nil - } - out := new(IngressGatewayList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IngressGatewayList) 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 *IngressGatewaySpec) DeepCopyInto(out *IngressGatewaySpec) { +func (in *IngressGatewaySpec) DeepCopyInto(out *IngressGatewaySpec) { *out = *in in.TLS.DeepCopyInto(&out.TLS) if in.Listeners != nil { @@ -1066,41 +554,6 @@ 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 @@ -1176,1373 +629,330 @@ func (in *IntentionHTTPPermission) DeepCopy() *IntentionHTTPPermission { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IntentionJWTClaimVerification) DeepCopyInto(out *IntentionJWTClaimVerification) { +func (in *IntentionPermission) DeepCopyInto(out *IntentionPermission) { *out = *in - if in.Path != nil { - in, out := &in.Path, &out.Path - *out = make([]string, len(*in)) - copy(*out, *in) + if in.HTTP != nil { + in, out := &in.HTTP, &out.HTTP + *out = new(IntentionHTTPPermission) + (*in).DeepCopyInto(*out) } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionJWTClaimVerification. -func (in *IntentionJWTClaimVerification) DeepCopy() *IntentionJWTClaimVerification { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionPermission. +func (in *IntentionPermission) DeepCopy() *IntentionPermission { if in == nil { return nil } - out := new(IntentionJWTClaimVerification) + out := new(IntentionPermission) 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)) +func (in IntentionPermissions) DeepCopyInto(out *IntentionPermissions) { + { + in := &in + *out = make(IntentionPermissions, len(*in)) for i := range *in { if (*in)[i] != nil { in, out := &(*in)[i], &(*out)[i] - *out = new(IntentionJWTClaimVerification) + *out = new(IntentionPermission) (*in).DeepCopyInto(*out) } } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionJWTProvider. -func (in *IntentionJWTProvider) DeepCopy() *IntentionJWTProvider { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionPermissions. +func (in IntentionPermissions) DeepCopy() IntentionPermissions { if in == nil { return nil } - out := new(IntentionJWTProvider) + out := new(IntentionPermissions) 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 *IntentionJWTRequirement) DeepCopyInto(out *IntentionJWTRequirement) { +func (in *LeastRequestConfig) DeepCopyInto(out *LeastRequestConfig) { *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 - if in.HTTP != nil { - in, out := &in.HTTP, &out.HTTP - *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. -func (in *IntentionPermission) DeepCopy() *IntentionPermission { - if in == nil { - return nil - } - out := new(IntentionPermission) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in IntentionPermissions) DeepCopyInto(out *IntentionPermissions) { - { - in := &in - *out = make(IntentionPermissions, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(IntentionPermission) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntentionPermissions. -func (in IntentionPermissions) DeepCopy() IntentionPermissions { - if in == nil { - return nil - } - out := new(IntentionPermissions) - in.DeepCopyInto(out) - 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 *JWKSCluster) DeepCopyInto(out *JWKSCluster) { - *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 { - 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 - 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 *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 -} - -// 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 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 - 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 -} - -// 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 *MetricsSpec) DeepCopyInto(out *MetricsSpec) { - *out = *in - if in.Port != nil { - in, out := &in.Port, &out.Port - *out = new(int32) - **out = **in - } - if in.Path != nil { - in, out := &in.Path, &out.Path - *out = new(string) - **out = **in - } - if in.Enabled != nil { - in, out := &in.Enabled, &out.Enabled - *out = new(bool) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsSpec. -func (in *MetricsSpec) DeepCopy() *MetricsSpec { - if in == nil { - return nil - } - out := new(MetricsSpec) - 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 { +// 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(PeeringDialerList) + out := new(LeastRequestConfig) 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) { +func (in *LinkedService) DeepCopyInto(out *LinkedService) { *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 { +// 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(PeeringDialerSpec) + 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 *PeeringDialerStatus) DeepCopyInto(out *PeeringDialerStatus) { +func (in *LoadBalancer) DeepCopyInto(out *LoadBalancer) { *out = *in - if in.LatestPeeringVersion != nil { - in, out := &in.LatestPeeringVersion, &out.LatestPeeringVersion - *out = new(uint64) + if in.RingHashConfig != nil { + in, out := &in.RingHashConfig, &out.RingHashConfig + *out = new(RingHashConfig) **out = **in } - if in.SecretRef != nil { - in, out := &in.SecretRef, &out.SecretRef - *out = new(SecretRefStatus) + if in.LeastRequestConfig != nil { + in, out := &in.LeastRequestConfig, &out.LeastRequestConfig + *out = new(LeastRequestConfig) **out = **in } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(Conditions, len(*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]) } } - 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 { +// 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(PeeringDialerStatus) + 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 *PeeringMeshConfig) DeepCopyInto(out *PeeringMeshConfig) { +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 PeeringMeshConfig. -func (in *PeeringMeshConfig) DeepCopy() *PeeringMeshConfig { +// 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(PeeringMeshConfig) + 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 *PolicyTargetReference) DeepCopyInto(out *PolicyTargetReference) { +func (in *MeshDirectionalTLSConfig) DeepCopyInto(out *MeshDirectionalTLSConfig) { *out = *in - if in.SectionName != nil { - in, out := &in.SectionName, &out.SectionName - *out = new(v1beta1.SectionName) - **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 PolicyTargetReference. -func (in *PolicyTargetReference) DeepCopy() *PolicyTargetReference { +// 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(PolicyTargetReference) + 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 *PrioritizeByLocality) DeepCopyInto(out *PrioritizeByLocality) { +func (in *MeshGateway) DeepCopyInto(out *MeshGateway) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrioritizeByLocality. -func (in *PrioritizeByLocality) DeepCopy() *PrioritizeByLocality { +// 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(PrioritizeByLocality) + 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 *ProxyDefaults) DeepCopyInto(out *ProxyDefaults) { +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 ProxyDefaults. -func (in *ProxyDefaults) DeepCopy() *ProxyDefaults { +// 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(ProxyDefaults) + out := new(MeshHTTPConfig) 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) { +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([]ProxyDefaults, 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 ProxyDefaultsList. -func (in *ProxyDefaultsList) DeepCopy() *ProxyDefaultsList { +// 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(ProxyDefaultsList) + out := new(MeshList) 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 { +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 *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 - } - 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) @@ -2550,18 +960,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 } @@ -2569,31 +979,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 } @@ -2601,49 +1011,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) @@ -2651,18 +1075,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 } @@ -2670,31 +1094,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 } @@ -2702,118 +1126,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) @@ -2821,18 +1205,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 } @@ -2840,31 +1224,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 } @@ -2872,55 +1256,60 @@ 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) + 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]) + } + } } -// 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 } @@ -3075,11 +1464,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)) @@ -3173,11 +1557,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. @@ -3230,11 +1609,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. @@ -3359,11 +1733,6 @@ func (in *ServiceResolverSpec) DeepCopyInto(out *ServiceResolverSpec) { *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. diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index 00cbf9d3fa..6d264fc727 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 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 +190,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 +344,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 +380,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 +482,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,26 +619,46 @@ 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_version_k8s_for_chart_version="$2" local full_consul_version="$5" 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_version_k8s_for_chart_version="$2-$3" 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" full_consul_dataplane_version="${7%.*}-$3" fi + # validate script versions + if test -z "$full_version"; then + err "ERROR: full_version is empty" + return 1 + fi + if test -z "$full_version_k8s_for_chart_version"; then + err "ERROR: full_version_k8s_for_chart_version is empty" + return 1 + fi + if test -z "$full_consul_version"; then + err "ERROR: full_consul_version is empty" + return 1 + fi + if test -z "$full_consul_dataplane_version"; then + err "ERROR: full_consul_dataplane_version is empty" + return 1 + 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 +680,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 @@ -690,13 +710,8 @@ function set_version { 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 - 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 + status_stage "==> Updating version/version.go with version info: ${vers} "$4"" + if ! update_version "${sdir}/version/version.go" "${vers}" "$4"; then return 1 fi @@ -710,13 +725,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 +749,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,19 +761,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}.x is compatible with Consul ${consul_version_short}.x and Consul Dataplane ${consul_dataplane_version_short}.x. 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 \ @@ -778,13 +774,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: @@ -801,39 +797,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) @@ -888,7 +857,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..a4f36ee3e4 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, 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 +188,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 +242,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 +290,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/scripts/build-local.sh b/control-plane/build-support/scripts/build-local.sh index 7325e025b7..453310b0b7 100755 --- a/control-plane/build-support/scripts/build-local.sh +++ b/control-plane/build-support/scripts/build-local.sh @@ -35,8 +35,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 +94,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/catalog/to-consul/resource.go b/control-plane/catalog/to-consul/resource.go index 879789d4b5..0c319d90ee 100644 --- a/control-plane/catalog/to-consul/resource.go +++ b/control-plane/catalog/to-consul/resource.go @@ -17,7 +17,6 @@ import ( consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" corev1 "k8s.io/api/core/v1" - discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -34,11 +33,10 @@ const ( // ConsulK8SNS is the key used in the meta to record the namespace // of the service/node registration. - ConsulK8SNS = "external-k8s-ns" - ConsulK8SRefKind = "external-k8s-ref-kind" - ConsulK8SRefValue = "external-k8s-ref-name" - ConsulK8SNodeName = "external-k8s-node-name" - ConsulK8STopologyZone = "external-k8s-topology-zone" + ConsulK8SNS = "external-k8s-ns" + 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" @@ -145,11 +143,9 @@ type ServiceResource struct { // in the form /. serviceMap map[string]*corev1.Service - // endpointSlicesMap tracks EndpointSlices associated with services that are being synced to Consul. - // The outer map's keys represent service identifiers in the same format as serviceMap and maps - // each service to its related EndpointSlices. The inner map's keys are EndpointSlice name keys - // the format "/". - endpointSlicesMap map[string]map[string]*discoveryv1.EndpointSlice + // endpointsMap uses the same keys as serviceMap but maps to the endpoints + // 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. @@ -229,47 +225,22 @@ func (t *ServiceResource) Upsert(key string, raw interface{}) error { t.serviceMap[key] = service t.Log.Debug("[ServiceResource.Upsert] adding service to serviceMap", "key", key, "service", service) - // If we care about endpoints, we should load the associated endpoint slices. + // If we care about endpoints, we should do the initial endpoints load. if t.shouldTrackEndpoints(key) { - allEndpointSlices := make(map[string]*discoveryv1.EndpointSlice) - labelSelector := fmt.Sprintf("%s=%s", discoveryv1.LabelServiceName, service.Name) - continueToken := "" - limit := int64(100) - - for { - opts := metav1.ListOptions{ - LabelSelector: labelSelector, - Limit: limit, - Continue: continueToken, - } - endpointSliceList, err := t.Client.DiscoveryV1(). - EndpointSlices(service.Namespace). - List(t.Ctx, opts) - - if err != nil { - t.Log.Warn("error loading endpoint slices list", - "key", key, - "err", err) - break - } - - for _, endpointSlice := range endpointSliceList.Items { - endptKey := service.Namespace + "/" + endpointSlice.Name - allEndpointSlices[endptKey] = &endpointSlice - } - - if endpointSliceList.Continue != "" { - continueToken = endpointSliceList.Continue - } else { - break + endpoints, err := t.Client.CoreV1(). + Endpoints(service.Namespace). + Get(t.Ctx, service.Name, metav1.GetOptions{}) + if err != nil { + t.Log.Warn("error loading initial endpoints", + "key", key, + "err", err) + } else { + if t.endpointsMap == nil { + 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) } - - if t.endpointSlicesMap == nil { - t.endpointSlicesMap = make(map[string]map[string]*discoveryv1.EndpointSlice) - } - t.endpointSlicesMap[key] = allEndpointSlices - t.Log.Debug("[ServiceResource.Upsert] adding service's endpoint slices to endpointSlicesMap", "key", key, "service", service, "endpointSlices", allEndpointSlices) } // Update the registration and trigger a sync @@ -294,8 +265,8 @@ func (t *ServiceResource) Delete(key string, _ interface{}) error { func (t *ServiceResource) doDelete(key string) { delete(t.serviceMap, key) t.Log.Debug("[doDelete] deleting service from serviceMap", "key", key) - delete(t.endpointSlicesMap, key) - t.Log.Debug("[doDelete] deleting endpoints from endpointSlicesMap", "key", key) + delete(t.endpointsMap, key) + t.Log.Debug("[doDelete] deleting endpoints from endpointsMap", "key", key) // If there were registrations related to this service, then // delete them and sync. if _, ok := t.consulMap[key]; ok { @@ -541,7 +512,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 { @@ -590,7 +561,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 { @@ -611,26 +582,27 @@ func (t *ServiceResource) generateRegistrations(key string) { // pods are running on. This way we don't register _every_ K8S // node as part of the service. case corev1.ServiceTypeNodePort: - if t.endpointSlicesMap == nil { + if t.endpointsMap == nil { return } - endpointSliceList := t.endpointSlicesMap[key] - if endpointSliceList == nil { + endpoints := t.endpointsMap[key] + if endpoints == nil { return } - for _, endpointSlice := range endpointSliceList { - for _, endpoint := range endpointSlice.Endpoints { + for _, subset := range endpoints.Subsets { + for _, subsetAddr := range subset.Addresses { // Check that the node name exists // subsetAddr.NodeName is of type *string - if endpoint.NodeName == nil { + if subsetAddr.NodeName == nil { continue } + // Look up the node's ip address by getting node info - node, err := t.Client.CoreV1().Nodes().Get(t.Ctx, *endpoint.NodeName, metav1.GetOptions{}) + node, err := t.Client.CoreV1().Nodes().Get(t.Ctx, *subsetAddr.NodeName, metav1.GetOptions{}) if err != nil { - t.Log.Error("error getting node info", "error", err) + t.Log.Warn("error getting node info", "error", err) continue } @@ -642,18 +614,37 @@ func (t *ServiceResource) generateRegistrations(key string) { expectedType = corev1.NodeExternalIP } - for _, endpointAddr := range endpoint.Addresses { + // Find the ip address for the node and + // create the Consul service using it + var found bool + for _, address := range node.Status.Addresses { + if address.Type == expectedType { + found = true + r := baseNode + rs := baseService + r.Service = &rs + r.Service.ID = serviceID(r.Service.Service, subsetAddr.IP) + r.Service.Address = address.Address + + t.consulMap[key] = append(t.consulMap[key], &r) + // Only consider the first address that matches. In some cases + // there will be multiple addresses like when using AWS CNI. + // In those cases, Kubernetes will ensure eth0 is always the first + // address in the list. + // See https://github.com/kubernetes/kubernetes/blob/b559434c02f903dbcd46ee7d6c78b216d3f0aca0/staging/src/k8s.io/legacy-cloud-providers/aws/aws.go#L1462-L1464 + break + } + } - // Find the ip address for the node and - // create the Consul service using it - var found bool + // If an ExternalIP wasn't found, and ExternalFirst is set, + // use an InternalIP + if t.NodePortSync == ExternalFirst && !found { for _, address := range node.Status.Addresses { - if address.Type == expectedType { - found = true + if address.Type == corev1.NodeInternalIP { r := baseNode rs := baseService r.Service = &rs - r.Service.ID = serviceID(r.Service.Service, endpointAddr) + r.Service.ID = serviceID(r.Service.Service, subsetAddr.IP) r.Service.Address = address.Address t.consulMap[key] = append(t.consulMap[key], &r) @@ -665,29 +656,6 @@ func (t *ServiceResource) generateRegistrations(key string) { break } } - - // If an ExternalIP wasn't found, and ExternalFirst is set, - // use an InternalIP - if t.NodePortSync == ExternalFirst && !found { - for _, address := range node.Status.Addresses { - if address.Type == corev1.NodeInternalIP { - r := baseNode - rs := baseService - r.Service = &rs - r.Service.ID = serviceID(r.Service.Service, endpointAddr) - r.Service.Address = address.Address - - t.consulMap[key] = append(t.consulMap[key], &r) - // Only consider the first address that matches. In some cases - // there will be multiple addresses like when using AWS CNI. - // In those cases, Kubernetes will ensure eth0 is always the first - // address in the list. - // See https://github.com/kubernetes/kubernetes/blob/b559434c02f903dbcd46ee7d6c78b216d3f0aca0/staging/src/k8s.io/legacy-cloud-providers/aws/aws.go#L1462-L1464 - break - } - } - } - } } } @@ -707,100 +675,94 @@ func (t *ServiceResource) registerServiceInstance( overridePortNumber int, useHostname bool) { - if t.endpointSlicesMap == nil { + if t.endpointsMap == nil { return } - endpointSliceList := t.endpointSlicesMap[key] - if endpointSliceList == nil { + endpoints := t.endpointsMap[key] + if endpoints == nil { return } seen := map[string]struct{}{} - for _, endpointSlice := range endpointSliceList { + for _, subset := range endpoints.Subsets { // For ClusterIP services and if LoadBalancerEndpointsSync is true, we use the endpoint port instead // of the service port because we're registering each endpoint // as a separate service instance. epPort := baseService.Port if overridePortName != "" { // If we're supposed to use a specific named port, find it. - for _, p := range endpointSlice.Ports { - if overridePortName == *p.Name { - epPort = int(*p.Port) + for _, p := range subset.Ports { + if overridePortName == p.Name { + epPort = int(p.Port) break } } } else if overridePortNumber == 0 { // Otherwise we'll just use the first port in the list // (unless the port number was overridden by an annotation). - for _, p := range endpointSlice.Ports { - epPort = int(*p.Port) + for _, p := range subset.Ports { + epPort = int(p.Port) break } } - for _, endpoint := range endpointSlice.Endpoints { - for _, endpointAddr := range endpoint.Addresses { - - 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 = endpointAddr - if addr == "" && useHostname { - addr = *endpoint.Hostname - } - if addr == "" { - continue - } + for _, subsetAddr := range subset.Addresses { + 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 } - - // Its not clear whether K8S guarantees ready addresses to - // be unique so we maintain a set to prevent duplicates just - // in case. - if _, ok := seen[addr]; ok { + if addr == "" { continue } - seen[addr] = struct{}{} + } - r := baseNode - rs := baseService - r.Service = &rs - r.Service.ID = serviceID(r.Service.Service, addr) - r.Service.Address = addr - r.Service.Port = epPort - r.Service.Meta = make(map[string]string) - // Deepcopy baseService.Meta into r.Service.Meta as baseService is shared - // between all nodes of a service - for k, v := range baseService.Meta { - r.Service.Meta[k] = v - } - if endpoint.TargetRef != nil { - r.Service.Meta[ConsulK8SRefValue] = endpoint.TargetRef.Name - r.Service.Meta[ConsulK8SRefKind] = endpoint.TargetRef.Kind - } - if endpoint.NodeName != nil { - r.Service.Meta[ConsulK8SNodeName] = *endpoint.NodeName - } - if endpoint.Zone != nil { - r.Service.Meta[ConsulK8STopologyZone] = *endpoint.Zone - } + // Its not clear whether K8S guarantees ready addresses to + // be unique so we maintain a set to prevent duplicates just + // in case. + if _, ok := seen[addr]; ok { + continue + } + seen[addr] = struct{}{} - r.Check = &consulapi.AgentCheck{ - CheckID: consulHealthCheckID(endpointSlice.Namespace, serviceID(r.Service.Service, addr)), - Name: consulKubernetesCheckName, - Namespace: baseService.Namespace, - Type: consulKubernetesCheckType, - Status: consulapi.HealthPassing, - ServiceID: serviceID(r.Service.Service, addr), - Output: kubernetesSuccessReasonMsg, - } + r := baseNode + rs := baseService + r.Service = &rs + r.Service.ID = serviceID(r.Service.Service, addr) + r.Service.Address = addr + r.Service.Port = epPort + r.Service.Meta = make(map[string]string) + // Deepcopy baseService.Meta into r.Service.Meta as baseService is shared + // between all nodes of a service + for k, v := range baseService.Meta { + r.Service.Meta[k] = v + } + if subsetAddr.TargetRef != nil { + r.Service.Meta[ConsulK8SRefValue] = subsetAddr.TargetRef.Name + r.Service.Meta[ConsulK8SRefKind] = subsetAddr.TargetRef.Kind + } + if subsetAddr.NodeName != nil { + r.Service.Meta[ConsulK8SNodeName] = *subsetAddr.NodeName + } - t.consulMap[key] = append(t.consulMap[key], &r) + 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) } } } @@ -849,88 +811,68 @@ func (t *serviceEndpointsResource) Informer() cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { - return t.Service.Client.DiscoveryV1(). - EndpointSlices(metav1.NamespaceAll). + return t.Service.Client.CoreV1(). + Endpoints(metav1.NamespaceAll). List(t.Ctx, options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { - return t.Service.Client.DiscoveryV1(). - EndpointSlices(metav1.NamespaceAll). + return t.Service.Client.CoreV1(). + Endpoints(metav1.NamespaceAll). Watch(t.Ctx, options) }, }, - &discoveryv1.EndpointSlice{}, + &corev1.Endpoints{}, 0, cache.Indexers{}, ) } -func (t *serviceEndpointsResource) Upsert(endptKey string, raw interface{}) error { +func (t *serviceEndpointsResource) Upsert(key string, raw interface{}) error { svc := t.Service - - endpointSlice, ok := raw.(*discoveryv1.EndpointSlice) + endpoints, ok := raw.(*corev1.Endpoints) if !ok { - svc.Log.Error("upsert got invalid type", "raw", raw) + svc.Log.Warn("upsert got invalid type", "raw", raw) return nil } svc.serviceLock.Lock() defer svc.serviceLock.Unlock() - // Extract service name and format the service key - svcKey := endpointSlice.Namespace + "/" + endpointSlice.Labels[discoveryv1.LabelServiceName] - // Check if we care about endpoints for this service - if !svc.shouldTrackEndpoints(svcKey) { + if !svc.shouldTrackEndpoints(key) { return nil } // We are tracking this service so let's keep track of the endpoints - if svc.endpointSlicesMap == nil { - svc.endpointSlicesMap = make(map[string]map[string]*discoveryv1.EndpointSlice) - } - if _, ok := svc.endpointSlicesMap[svcKey]; !ok { - svc.endpointSlicesMap[svcKey] = make(map[string]*discoveryv1.EndpointSlice) + if svc.endpointsMap == nil { + svc.endpointsMap = make(map[string]*corev1.Endpoints) } - svc.endpointSlicesMap[svcKey][endptKey] = endpointSlice + svc.endpointsMap[key] = endpoints // Update the registration and trigger a sync - svc.generateRegistrations(svcKey) + svc.generateRegistrations(key) svc.sync() - svc.Log.Info("upsert endpoint", "key", endptKey) + svc.Log.Info("upsert endpoint", "key", key) return nil } -func (t *serviceEndpointsResource) Delete(endptKey string, raw interface{}) error { - - endpointSlice, ok := raw.(*discoveryv1.EndpointSlice) - if !ok { - t.Service.Log.Error("upsert got invalid type", "raw", raw) - return nil - } - +func (t *serviceEndpointsResource) Delete(key string, _ interface{}) error { t.Service.serviceLock.Lock() defer t.Service.serviceLock.Unlock() - // Extract service name and format key - svcName := endpointSlice.Labels[discoveryv1.LabelServiceName] - svcKey := endpointSlice.Namespace + "/" + svcName - // This is a bit of an optimization. We only want to force a resync // if we were tracking this endpoint to begin with and that endpoint // had associated registrations. - if _, ok := t.Service.endpointSlicesMap[svcKey]; ok { - if _, ok := t.Service.endpointSlicesMap[svcKey][endptKey]; ok { - delete(t.Service.endpointSlicesMap[svcKey], endptKey) - if _, ok := t.Service.consulMap[svcKey]; ok { - delete(t.Service.consulMap, svcKey) - t.Service.sync() - } + if _, ok := t.Service.endpointsMap[key]; ok { + delete(t.Service.endpointsMap, key) + if _, ok := t.Service.consulMap[key]; ok { + delete(t.Service.consulMap, key) + t.Service.sync() } } - t.Service.Log.Info("delete endpoint", "key", endptKey) + t.Service.Log.Info("delete endpoint", "key", key) return nil } @@ -1086,7 +1028,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 3272849bd3..3b8fb78497 100644 --- a/control-plane/catalog/to-consul/resource_test.go +++ b/control-plane/catalog/to-consul/resource_test.go @@ -14,14 +14,11 @@ import ( "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" - discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" - "k8s.io/utils/pointer" ) const nodeName1 = "ip-10-11-12-13.ec2.internal" @@ -47,9 +44,6 @@ func TestServiceResource_createDelete(t *testing.T) { _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - createEndpointSlice(t, client, svc.Name, metav1.NamespaceDefault) - // Delete require.NoError(t, client.CoreV1().Services(metav1.NamespaceDefault).Delete(context.Background(), "foo", metav1.DeleteOptions{})) @@ -765,36 +759,23 @@ func TestServiceResource_lbRegisterEndpoints(t *testing.T) { node1, _ := createNodes(t, client) - // Insert the endpoint slice - _, err := client.DiscoveryV1().EndpointSlices(metav1.NamespaceDefault).Create( + // Insert the endpoints + _, err := client.CoreV1().Endpoints(metav1.NamespaceDefault).Create( context.Background(), - &discoveryv1.EndpointSlice{ + &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "foo-", - Labels: map[string]string{discoveryv1.LabelServiceName: "foo"}, + Name: "foo", }, - AddressType: discoveryv1.AddressTypeIPv4, - Endpoints: []discoveryv1.Endpoint{ + + Subsets: []corev1.EndpointSubset{ { - Addresses: []string{"8.8.8.8"}, - Conditions: discoveryv1.EndpointConditions{ - Ready: pointer.Bool(true), - Serving: pointer.Bool(true), - Terminating: pointer.Bool(false), + Addresses: []corev1.EndpointAddress{ + {NodeName: &node1.Name, IP: "8.8.8.8"}, + }, + Ports: []corev1.EndpointPort{ + {Name: "http", Port: 8080}, + {Name: "rpc", Port: 2000}, }, - TargetRef: &corev1.ObjectReference{Kind: "pod", Name: "foo", Namespace: metav1.NamespaceDefault}, - NodeName: &node1.Name, - Zone: pointer.String("us-west-2a"), - }, - }, - Ports: []discoveryv1.EndpointPort{ - { - Name: pointer.String("http"), - Port: pointer.Int32(8080), - }, - { - Name: pointer.String("rpc"), - Port: pointer.Int32(2000), }, }, }, @@ -833,7 +814,7 @@ func TestServiceResource_nodePort(t *testing.T) { createNodes(t, client) - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Insert the service svc := nodePortService("foo", metav1.NamespaceDefault) @@ -873,7 +854,7 @@ func TestServiceResource_nodePortPrefix(t *testing.T) { createNodes(t, client) - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Insert the service svc := nodePortService("foo", metav1.NamespaceDefault) @@ -913,36 +894,23 @@ func TestServiceResource_nodePort_singleEndpoint(t *testing.T) { node1, _ := createNodes(t, client) - // Insert the endpoint slice - _, err := client.DiscoveryV1().EndpointSlices(metav1.NamespaceDefault).Create( + // Insert the endpoints + _, err := client.CoreV1().Endpoints(metav1.NamespaceDefault).Create( context.Background(), - &discoveryv1.EndpointSlice{ + &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "foo-", - Labels: map[string]string{discoveryv1.LabelServiceName: "foo"}, + Name: "foo", }, - AddressType: discoveryv1.AddressTypeIPv4, - Endpoints: []discoveryv1.Endpoint{ + + Subsets: []corev1.EndpointSubset{ { - Addresses: []string{"1.2.3.4"}, - Conditions: discoveryv1.EndpointConditions{ - Ready: pointer.Bool(true), - Serving: pointer.Bool(true), - Terminating: pointer.Bool(false), + Addresses: []corev1.EndpointAddress{ + {NodeName: &node1.Name, IP: "1.2.3.4"}, + }, + Ports: []corev1.EndpointPort{ + {Name: "http", Port: 8080}, + {Name: "rpc", Port: 2000}, }, - TargetRef: &corev1.ObjectReference{Kind: "pod", Name: "foo", Namespace: metav1.NamespaceDefault}, - NodeName: &node1.Name, - Zone: pointer.String("us-west-2a"), - }, - }, - Ports: []discoveryv1.EndpointPort{ - { - Name: pointer.String("http"), - Port: pointer.Int32(8080), - }, - { - Name: pointer.String("rpc"), - Port: pointer.Int32(2000), }, }, }, @@ -981,12 +949,12 @@ func TestServiceResource_nodePortAnnotatedPort(t *testing.T) { createNodes(t, client) + createEndpoints(t, client, "foo", metav1.NamespaceDefault) + // Insert the service svc := nodePortService("foo", metav1.NamespaceDefault) svc.Annotations = map[string]string{annotationServicePort: "rpc"} _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) - createEndpointSlice(t, client, svc.Name, metav1.NamespaceDefault) - require.NoError(t, err) // Verify what we got @@ -1021,7 +989,7 @@ func TestServiceResource_nodePortUnnamedPort(t *testing.T) { createNodes(t, client) - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Insert the service svc := nodePortService("foo", metav1.NamespaceDefault) @@ -1066,7 +1034,7 @@ func TestServiceResource_nodePort_internalOnlySync(t *testing.T) { createNodes(t, client) - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Insert the service svc := nodePortService("foo", metav1.NamespaceDefault) @@ -1114,7 +1082,7 @@ func TestServiceResource_nodePort_externalFirstSync(t *testing.T) { _, err := client.CoreV1().Nodes().UpdateStatus(context.Background(), node1, metav1.UpdateOptions{}) require.NoError(t, err) - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Insert the service svc := nodePortService("foo", metav1.NamespaceDefault) @@ -1156,10 +1124,8 @@ func TestServiceResource_clusterIP(t *testing.T) { _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - - // Insert the endpoint slice - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + // Insert the endpoints + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Verify what we got retry.Run(t, func(r *retry.R) { @@ -1173,8 +1139,6 @@ func TestServiceResource_clusterIP(t *testing.T) { require.Equal(r, "foo", actual[1].Service.Service) require.Equal(r, "2.2.2.2", actual[1].Service.Address) require.Equal(r, 8080, actual[1].Service.Port) - require.Equal(r, "us-west-2a", actual[0].Service.Meta["external-k8s-topology-zone"]) - require.Equal(r, "us-west-2b", actual[1].Service.Meta["external-k8s-topology-zone"]) require.NotEqual(r, actual[0].Service.ID, actual[1].Service.ID) }) } @@ -1196,10 +1160,8 @@ func TestServiceResource_clusterIP_healthCheck(t *testing.T) { _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - - // Insert the endpoint slice - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + // Insert the endpoints + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Verify what we got retry.Run(t, func(r *retry.R) { @@ -1236,10 +1198,8 @@ func TestServiceResource_clusterIPPrefix(t *testing.T) { _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - - // Insert the endpoint slice - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + // Insert the endpoints + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Verify what we got retry.Run(t, func(r *retry.R) { @@ -1276,10 +1236,8 @@ func TestServiceResource_clusterIPAnnotatedPortName(t *testing.T) { _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - - // Insert the endpoint slice - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + // Insert the endpoints + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Verify what we got retry.Run(t, func(r *retry.R) { @@ -1316,10 +1274,8 @@ func TestServiceResource_clusterIPAnnotatedPortNumber(t *testing.T) { _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - - // Insert the endpoint slice - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + // Insert the endpoints + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Verify what we got retry.Run(t, func(r *retry.R) { @@ -1358,10 +1314,8 @@ func TestServiceResource_clusterIPUnnamedPorts(t *testing.T) { _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - - // Insert the endpoint slice - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + // Insert the endpoints + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Verify what we got retry.Run(t, func(r *retry.R) { @@ -1397,10 +1351,8 @@ func TestServiceResource_clusterIPSyncDisabled(t *testing.T) { _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - - // Insert the endpoint slice - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + // Insert the endpoints + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Verify what we got retry.Run(t, func(r *retry.R) { @@ -1429,10 +1381,8 @@ func TestServiceResource_clusterIPAllNamespaces(t *testing.T) { _, err := client.CoreV1().Services(testNamespace).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - - // Insert the endpoint slice - createEndpointSlice(t, client, "foo", testNamespace) + // Insert the endpoints + createEndpoints(t, client, "foo", testNamespace) // Verify what we got retry.Run(t, func(r *retry.R) { @@ -1472,10 +1422,8 @@ func TestServiceResource_clusterIPTargetPortNamed(t *testing.T) { _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - - // Insert the endpoint slice - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + // Insert the endpoints + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Verify what we got retry.Run(t, func(r *retry.R) { @@ -1510,10 +1458,8 @@ func TestServiceResource_targetRefInMeta(t *testing.T) { _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.Background(), svc, metav1.CreateOptions{}) require.NoError(t, err) - createNodes(t, client) - - // Insert the endpoint slice - createEndpointSlice(t, client, "foo", metav1.NamespaceDefault) + // Insert the endpoints + createEndpoints(t, client, "foo", metav1.NamespaceDefault) // Verify what we got retry.Run(t, func(r *retry.R) { @@ -1999,10 +1945,7 @@ func TestServiceResource_addIngress(t *testing.T) { // Create the ingress _, err = client.NetworkingV1().Ingresses(metav1.NamespaceDefault).Create(context.Background(), test.ingress, metav1.CreateOptions{}) require.NoError(t, err) - - createNodes(t, client) - createEndpointSlice(t, client, "test-service", metav1.NamespaceDefault) - + createEndpoints(t, client, "test-service", metav1.NamespaceDefault) // Verify that the service name annotation is preferred retry.Run(t, func(r *retry.R) { syncer.Lock() @@ -2123,55 +2066,43 @@ func createNodes(t *testing.T, client *fake.Clientset) (*corev1.Node, *corev1.No return node1, node2 } -// createEndpointSlices calls the fake k8s client to create an endpoint slices with two endpoints on different nodes. -func createEndpointSlice(t *testing.T, client *fake.Clientset, serviceName string, namespace string) { +// createEndpoints calls the fake k8s client to create two endpoints on two nodes. +func createEndpoints(t *testing.T, client *fake.Clientset, serviceName string, namespace string) { node1 := nodeName1 node2 := nodeName2 targetRef := corev1.ObjectReference{Kind: "pod", Name: "foobar"} - - _, err := client.DiscoveryV1().EndpointSlices(namespace).Create( + _, err := client.CoreV1().Endpoints(namespace).Create( context.Background(), - &discoveryv1.EndpointSlice{ + &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{discoveryv1.LabelServiceName: serviceName}, - Name: serviceName + "-" + rand.String(5), + Name: serviceName, + Namespace: namespace, }, - AddressType: discoveryv1.AddressTypeIPv4, - Endpoints: []discoveryv1.Endpoint{ + + Subsets: []corev1.EndpointSubset{ { - Addresses: []string{"1.1.1.1"}, - Conditions: discoveryv1.EndpointConditions{ - Ready: pointer.Bool(true), - Serving: pointer.Bool(true), - Terminating: pointer.Bool(false), + Addresses: []corev1.EndpointAddress{ + {NodeName: &node1, IP: "1.1.1.1", TargetRef: &targetRef}, }, - TargetRef: &targetRef, - NodeName: &node1, - Zone: pointer.String("us-west-2a"), - }, - { - Addresses: []string{"2.2.2.2"}, - Conditions: discoveryv1.EndpointConditions{ - Ready: pointer.Bool(true), - Serving: pointer.Bool(true), - Terminating: pointer.Bool(false), + Ports: []corev1.EndpointPort{ + {Name: "http", Port: 8080}, + {Name: "rpc", Port: 2000}, }, - NodeName: &node2, - Zone: pointer.String("us-west-2b"), - }, - }, - Ports: []discoveryv1.EndpointPort{ - { - Name: pointer.String("http"), - Port: pointer.Int32(8080), }, + { - Name: pointer.String("rpc"), - Port: pointer.Int32(2000), + Addresses: []corev1.EndpointAddress{ + {NodeName: &node2, IP: "2.2.2.2"}, + }, + Ports: []corev1.EndpointPort{ + {Name: "http", Port: 8080}, + {Name: "rpc", Port: 2000}, + }, }, }, }, metav1.CreateOptions{}) + require.NoError(t, err) } diff --git a/control-plane/catalog/to-consul/syncer_test.go b/control-plane/catalog/to-consul/syncer_test.go index ab2cfee0a2..3fae7a3d16 100644 --- a/control-plane/catalog/to-consul/syncer_test.go +++ b/control-plane/catalog/to-consul/syncer_test.go @@ -13,13 +13,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 +233,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/cni/go.mod b/control-plane/cni/go.mod index d17c04567d..e6038b555f 100644 --- a/control-plane/cni/go.mod +++ b/control-plane/cni/go.mod @@ -1,30 +1,31 @@ module github.com/hashicorp/consul-k8s/control-plane/cni +replace github.com/hashicorp/consul-k8s/version => ../../version + require ( github.com/containernetworking/cni v1.1.2 github.com/containernetworking/plugins v1.2.0 - github.com/hashicorp/consul-k8s/control-plane v0.0.0-20240326170414-f12a82a84667 - github.com/hashicorp/consul/sdk v0.16.0 - github.com/hashicorp/go-hclog v1.5.0 + github.com/hashicorp/consul-k8s/version v0.0.0 + github.com/hashicorp/consul/sdk v0.13.1 + github.com/hashicorp/go-hclog v1.2.2 github.com/stretchr/testify v1.8.4 - k8s.io/api v0.26.12 - k8s.io/apimachinery v0.26.12 - k8s.io/client-go v0.26.12 + k8s.io/api v0.29.8 + k8s.io/apimachinery v0.29.8 + k8s.io/client-go v0.29.8 ) require ( - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/fatih/color v1.16.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.1 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/imdario/mergo v0.3.13 // indirect @@ -36,9 +37,8 @@ require ( 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/onsi/ginkgo/v2 v2.6.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.24.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect @@ -51,12 +51,16 @@ require ( 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.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // 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/structured-merge-diff/v4 v4.4.1 // 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.21 + +toolchain go1.22.5 diff --git a/control-plane/cni/go.sum b/control-plane/cni/go.sum index e224d0ae93..e83c30aef0 100644 --- a/control-plane/cni/go.sum +++ b/control-plane/cni/go.sum @@ -1,80 +1,74 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -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= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ= github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v1.2.0 h1:SWgg3dQG1yzUo4d9iD8cwSVh1VqI+bP7mkPDoSfP9VU= github.com/containernetworking/plugins v1.2.0/go.mod h1:/VjX4uHecW5vVimFa1wkG4s+r/s9qIfPdqlLF4TW8c4= 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.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/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/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 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/go-logr/logr v1.2.0/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.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/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-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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.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= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 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.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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -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/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.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-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/hashicorp/consul-k8s/control-plane v0.0.0-20240326170414-f12a82a84667 h1:5pYrzowjHkDl81CdYWZm78LTf0pujXqVWPUXic37DrA= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20240326170414-f12a82a84667/go.mod h1:7V8Rj0bFQ6l33EdNT6fys6Ga/3np0vMV/S/zv5rPuRs= -github.com/hashicorp/consul/sdk v0.16.0 h1:SE9m0W6DEfgIVCJX7xU+iv/hUl4m/nxqMTnCdMxDpJ8= -github.com/hashicorp/consul/sdk v0.16.0/go.mod h1:7pxqqhqoaPqnBnzXD1StKed62LqJeClzVsUEy85Zr0A= -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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= @@ -87,17 +81,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.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/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/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/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/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -115,27 +114,30 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+ 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/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.6.1 h1:1xQPCjcqYw/J5LchOcp4/2q/jzJFjiAOc25chhnDw+Q= -github.com/onsi/ginkgo/v2 v2.6.1/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.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/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/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.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -148,17 +150,9 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -168,20 +162,20 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -193,6 +187,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -210,38 +205,24 @@ 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/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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/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= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/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= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= @@ -261,27 +242,24 @@ 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-20200615113413-eeeca48fe776/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= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.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.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.29.8 h1:ZBKg9clWnIGtQ5yGhNwMw2zyyrsIAQaXhZACcYNflQE= +k8s.io/api v0.29.8/go.mod h1:XlGIpmpzKGrtVca7GlgNryZJ19SvQdI808NN7fy1SgQ= +k8s.io/apimachinery v0.29.8 h1:uBHc9WuKiTHClIspJqtR84WNpG0aOGn45HWqxgXkk8Y= +k8s.io/apimachinery v0.29.8/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= +k8s.io/client-go v0.29.8 h1:QMRKcIzqE/qawknXcsi51GdIAYN8UP39S/M5KnFu/J0= +k8s.io/client-go v0.29.8/go.mod h1:ZzrAAVrqO2jVXMb8My/jTke8n0a/mIynnA3y/1y1UB0= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 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/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/control-plane/cni/main.go b/control-plane/cni/main.go index eb710ff9cb..801674b0dd 100644 --- a/control-plane/cni/main.go +++ b/control-plane/cni/main.go @@ -13,8 +13,8 @@ import ( "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" current "github.com/containernetworking/cni/pkg/types/100" - "github.com/containernetworking/cni/pkg/version" - cpv "github.com/hashicorp/consul-k8s/control-plane/version" + cniv "github.com/containernetworking/cni/pkg/version" + "github.com/hashicorp/consul-k8s/version" "github.com/hashicorp/consul/sdk/iptables" "github.com/hashicorp/go-hclog" corev1 "k8s.io/api/core/v1" @@ -34,10 +34,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" @@ -73,10 +69,6 @@ type CNIArgs struct { K8S_POD_NAMESPACE types.UnmarshallableString // K8S_POD_INFRA_CONTAINER_ID is the runtime container ID that the pod runs under. K8S_POD_INFRA_CONTAINER_ID types.UnmarshallableString - - // CONSUL_IPTABLES_CONFIG is the runtime iptables configuration passed by - // orchestrator (ex. the Nomad client agent) - CONSUL_IPTABLES_CONFIG types.UnmarshallableString } // PluginConf is is the configuration used by the plugin. @@ -100,8 +92,9 @@ type PluginConf struct { Multus bool `json:"multus"` // Kubeconfig file name. Can be set as a cli flag. Kubeconfig string `json:"kubeconfig"` - // LogLevel is the logging level. Can be set as a cli flag. + // LogLevl is the logging level. Can be set as a cli flag. LogLevel string `json:"log_level"` + // } // parseConfig parses the supplied CNI configuration (and prevResult) from stdin. @@ -114,7 +107,7 @@ func parseConfig(stdin []byte) (*PluginConf, error) { // The previous result is passed from the previously run plugin to our plugin. We do not // do anything with the result but instead just pass it on when our plugin is finished. - if err := version.ParsePrevResult(&cfg.NetConf); err != nil { + if err := cniv.ParsePrevResult(&cfg.NetConf); err != nil { return nil, fmt.Errorf("could not parse prevResult: %w", err) } @@ -136,11 +129,9 @@ func (c *Command) cmdAdd(args *skel.CmdArgs) error { podNamespace := string(cniArgs.K8S_POD_NAMESPACE) podName := string(cniArgs.K8S_POD_NAME) - cniArgsIPTablesCfg := string(cniArgs.CONSUL_IPTABLES_CONFIG) - // We should never encounter this unless there has been an error in the - // kubelet. A good safeguard. - if (podNamespace == "" || podName == "") && cniArgsIPTablesCfg == "" { + // We should never encounter this unless there has been an error in the kubelet. A good safeguard. + if podNamespace == "" || podName == "" { return fmt.Errorf("not running in a pod, namespace and pod should have values") } @@ -173,55 +164,49 @@ func (c *Command) cmdAdd(args *skel.CmdArgs) error { result = prevResult } - var iptablesCfg iptables.Config + ctx := context.Background() + if c.client == nil { - // If cniArgsIPTablesCfg is populated we're on Nomad, otherwise we're on K8s - if cniArgsIPTablesCfg != "" { - var err error - iptablesCfg, err = parseIPTablesFromCNIArgs(cniArgsIPTablesCfg) + // Connect to kubernetes. + restConfig, err := clientcmd.BuildConfigFromFlags("", filepath.Join(cfg.CNINetDir, cfg.Kubeconfig)) if err != nil { - return err - } - } else { - if c.client == nil { - if err := c.createK8sClient(cfg); err != nil { - return err - } + return fmt.Errorf("could not get rest config from kubernetes api: %s", err) } - ctx := context.Background() - pod, err := c.client.CoreV1().Pods(podNamespace).Get(ctx, podName, metav1.GetOptions{}) + c.client, err = kubernetes.NewForConfig(restConfig) if err != nil { - return fmt.Errorf("error retrieving pod: %s", err) + return fmt.Errorf("error initializing Kubernetes client: %s", err) } + } - // Skip traffic redirection if the correct annotations are not on the pod. - if skipTrafficRedirection(*pod) { - logger.Debug("skipping traffic redirection because the pod is either not injected or transparent proxy is disabled: %s", pod.Name) - return types.PrintResult(result, cfg.CNIVersion) - } + pod, err := c.client.CoreV1().Pods(podNamespace).Get(ctx, podName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("error retrieving pod: %s", err) + } - // We do not throw an error here because kubernetes will often throw a - // benign error where the pod has been updated in between the get and - // update of the annotation. Eventually kubernetes will update the - // annotation - ok := c.updateTransparentProxyStatusAnnotation(podName, podNamespace, waiting) - if !ok { - logger.Info("unable to update %s pod annotation to waiting", keyTransparentProxyStatus) - } + // Skip traffic redirection if the correct annotations are not on the pod. + if skipTrafficRedirection(*pod) { + logger.Debug("skipping traffic redirection because the pod is either not injected or transparent proxy is disabled: %s", pod.Name) + return types.PrintResult(result, cfg.CNIVersion) + } - // Parse the cni-proxy-config annotation into an iptables.Config object. - iptablesCfg, err = parseAnnotation(*pod, annotationRedirectTraffic) - if err != nil { - return err - } + // We do not throw an error here because kubernetes will often throw a benign error where the pod has been + // updated in between the get and update of the annotation. Eventually kubernetes will update the annotation + ok := c.updateTransparentProxyStatusAnnotation(podName, podNamespace, waiting) + if !ok { + logger.Info("unable to update %s pod annotation to waiting", keyTransparentProxyStatus) + } + + // Parse the cni-proxy-config annotation into an iptables.Config object. + iptablesCfg, err := parseAnnotation(*pod, annotationRedirectTraffic) + if err != nil { + return err } // Set NetNS passed through the CNI. iptablesCfg.NetNS = args.Netns - // Set the provider to a fake provider in testing, otherwise use the default - // iptables.Provider + // Set the provider to a fake provider in testing, otherwise use the default iptables.Provider if c.iptablesProvider != nil { iptablesCfg.IptablesProvider = c.iptablesProvider } @@ -232,21 +217,15 @@ func (c *Command) cmdAdd(args *skel.CmdArgs) error { return fmt.Errorf("could not apply iptables setup: %v", err) } - if cniArgsIPTablesCfg == "" { - - // We do not throw an error here because kubernetes will often throw a - // benign error where the pod has been updated in between the get and update - // of the annotation. Eventually kubernetes will update the annotation - ok := c.updateTransparentProxyStatusAnnotation(podName, podNamespace, complete) - if !ok { - logger.Info("unable to update %s pod annotation to complete", keyTransparentProxyStatus) - } + // We do not throw an error here because kubernetes will often throw a benign error where the pod has been + // updated in between the get and update of the annotation. Eventually kubernetes will update the annotation + ok = c.updateTransparentProxyStatusAnnotation(podName, podNamespace, complete) + if !ok { + logger.Info("unable to update %s pod annotation to complete", keyTransparentProxyStatus) } - logger.Debug("traffic redirect rules applied to pod: %s", podName) - - // Pass through the result for the next plugin even if we are the final - // plugin in the chain. + logger.Debug("traffic redirect rules applied to pod: %s", pod.Name) + // Pass through the result for the next plugin even though we are the final plugin in the chain. return types.PrintResult(result, cfg.CNIVersion) } @@ -264,35 +243,16 @@ func cmdCheck(_ *skel.CmdArgs) error { func main() { c := &Command{} - bv.BuildVersion = cpv.GetHumanVersion() - skel.PluginMain(c.cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("consul-cni")) -} - -// createK8sClient configures the command's Kubernetes API client if it doesn't -// already exist -func (c *Command) createK8sClient(cfg *PluginConf) error { - restConfig, err := clientcmd.BuildConfigFromFlags("", filepath.Join(cfg.CNINetDir, cfg.Kubeconfig)) - if err != nil { - return fmt.Errorf("could not get rest config from kubernetes api: %s", err) - } - - c.client, err = kubernetes.NewForConfig(restConfig) - if err != nil { - return fmt.Errorf("error initializing Kubernetes client: %s", err) - } - return nil + bv.BuildVersion = version.GetHumanVersion() + skel.PluginMain(c.cmdAdd, cmdCheck, cmdDel, cniv.All, bv.BuildString("consul-cni")) } // 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 == "" { @@ -301,15 +261,6 @@ func skipTrafficRedirection(pod corev1.Pod) bool { return false } -func parseIPTablesFromCNIArgs(args string) (iptables.Config, error) { - cfg := iptables.Config{} - err := json.Unmarshal([]byte(args), &cfg) - if err != nil { - return cfg, fmt.Errorf("could not unmarshal CNI args: %w", err) - } - return cfg, nil -} - // parseAnnotation parses the cni-proxy-config annotation into an iptables.Config object. func parseAnnotation(pod corev1.Pod, annotation string) (iptables.Config, error) { anno, ok := pod.Annotations[annotation] diff --git a/control-plane/cni/main_test.go b/control-plane/cni/main_test.go index d86d1adff7..7c289a9825 100644 --- a/control-plane/cni/main_test.go +++ b/control-plane/cni/main_test.go @@ -51,7 +51,6 @@ func Test_cmdAdd(t *testing.T) { cmd *Command podName string stdInData string - cmdArgs *skel.CmdArgs configuredPod func(*corev1.Pod, *Command) *corev1.Pod expectedRules bool expectedErr error @@ -67,6 +66,22 @@ func Test_cmdAdd(t *testing.T) { expectedErr: fmt.Errorf("not running in a pod, namespace and pod should have values"), expectedRules: false, // Rules won't be applied because the command will throw an error first }, + { + name: "Missing prevResult in stdin data, should throw error", + cmd: &Command{ + client: fake.NewSimpleClientset(), + }, + podName: "missing-prev-result", + stdInData: missingPrevResultStdinData, + configuredPod: func(pod *corev1.Pod, cmd *Command) *corev1.Pod { + _, err := cmd.client.CoreV1().Pods(defaultNamespace).Create(context.Background(), pod, metav1.CreateOptions{}) + require.NoError(t, err) + + return pod + }, + expectedErr: fmt.Errorf("must be called as final chained plugin"), + expectedRules: false, // Rules won't be applied because the command will throw an error first + }, { name: "Missing IPs in prevResult in stdin data, should throw error", cmd: &Command{ @@ -128,33 +143,12 @@ func Test_cmdAdd(t *testing.T) { expectedErr: nil, expectedRules: true, // Rules will be applied }, - { - name: "Parsing iptables from CNI_ARGs as in Nomad", - cmd: &Command{ - client: fake.NewSimpleClientset(), - iptablesProvider: &fakeIptablesProvider{}, - }, - cmdArgs: &skel.CmdArgs{ContainerID: "some-container-id", - IfName: "eth0", - Args: fmt.Sprintf("CONSUL_IPTABLES_CONFIG=%s", minimalIPTablesJSON(t)), - Path: "/some/bin/path", - }, - stdInData: nomadStdinData, - expectedErr: nil, - expectedRules: true, - }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - if c.cmdArgs != nil { - c.cmdArgs.StdinData = []byte(c.stdInData) - err := c.cmd.cmdAdd(c.cmdArgs) - require.Equal(t, c.expectedErr, err) - } else { - _ = c.configuredPod(minimalPod(c.podName), c.cmd) - err := c.cmd.cmdAdd(minimalSkelArgs(c.podName, defaultNamespace, c.stdInData)) - require.Equal(t, c.expectedErr, err) - } + _ = c.configuredPod(minimalPod(c.podName), c.cmd) + err := c.cmd.cmdAdd(minimalSkelArgs(c.podName, defaultNamespace, c.stdInData)) + require.Equal(t, c.expectedErr, err) // Check to see that rules have been generated if c.expectedErr == nil && c.expectedRules { @@ -180,15 +174,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 { @@ -342,7 +327,7 @@ const goodStdinData = `{ "type": "consul-cni" }` -const missingIPsStdinData = `{ +const missingPrevResultStdinData = `{ "cniVersion": "0.3.1", "name": "kindnet", "type": "kindnet", @@ -358,17 +343,6 @@ const missingIPsStdinData = `{ "search": ["search"], "options": ["option"] }, - "prevResult": { - "cniversion": "0.3.1", - "interfaces": [ - { - "name": "eth0", - "sandbox": "/tmp" - } - ], - "routes": [] - - }, "cni_bin_dir": "/opt/cni/bin", "cni_net_dir": "/etc/cni/net.d", "kubeconfig": "ZZZ-consul-cni-kubeconfig", @@ -378,24 +352,28 @@ const missingIPsStdinData = `{ "type": "consul-cni" }` -const nomadStdinData = `{ - "cniVersion": "0.4.0", - "dns": {}, +const missingIPsStdinData = `{ + "cniVersion": "0.3.1", + "name": "kindnet", + "type": "kindnet", + "capabilities": { + "testCapability": false + }, + "ipam": { + "type": "host-local" + }, + "dns": { + "nameservers": ["nameserver"], + "domain": "domain", + "search": ["search"], + "options": ["option"] + }, "prevResult": { - "cniversion": "0.4.0", + "cniversion": "0.3.1", "interfaces": [ { "name": "eth0", - "mac": "aa:bb:cc:dd:ee:ff", - "sandbox": "/var/rum/netns/16c" - } - ], - "ips": [ - { - "version": "4", - "address": "10.0.0.2/24", - "gateway": "10.0.0.1", - "interface": 0 + "sandbox": "/tmp" } ], "routes": [] @@ -403,26 +381,9 @@ const nomadStdinData = `{ }, "cni_bin_dir": "/opt/cni/bin", "cni_net_dir": "/etc/cni/net.d", + "kubeconfig": "ZZZ-consul-cni-kubeconfig", "log_level": "info", - "name": "nomad", + "multus": false, + "name": "consul-cni", "type": "consul-cni" -} -` - -func minimalIPTablesJSON(t *testing.T) string { - cfg := iptables.Config{ - ConsulDNSIP: "127.0.0.1", - ConsulDNSPort: 8600, - ProxyUserID: "101", - ProxyInboundPort: 20000, - ProxyOutboundPort: 15001, - ExcludeInboundPorts: []string{"9000"}, - ExcludeOutboundPorts: []string{"15002"}, - ExcludeOutboundCIDRs: []string{"10.0.0.0/24"}, - ExcludeUIDs: []string{"1", "42"}, - NetNS: "/some/netns/path", - } - buf, err := json.Marshal(cfg) - require.NoError(t, err) - return string(buf) -} +}` diff --git a/control-plane/commands.go b/control-plane/commands.go index 01f5163bc3..c1419c49ed 100644 --- a/control-plane/commands.go +++ b/control-plane/commands.go @@ -6,28 +6,23 @@ 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" cmdTLSInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/tls-init" 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/hashicorp/consul-k8s/version" + "github.com/mitchellh/cli" ) // Commands is the mapping of all available consul-k8s commands. @@ -45,10 +40,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 +48,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 +90,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 ca29923851..0000000000 --- a/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml +++ /dev/null @@ -1,260 +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: - headers: - items: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - type: array - 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 - headers: - items: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - type: array - 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..753bce78d0 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,12 @@ # 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.14.0 name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -37,14 +38,19 @@ spec: 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' + 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' + 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 @@ -52,11 +58,13 @@ 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. + 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. + 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 @@ -70,12 +78,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 @@ -95,8 +99,9 @@ spec: 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' + 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 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 c2a857db34..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml +++ /dev/null @@ -1,213 +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 - metrics: - description: Metrics defines how to configure the metrics for a gateway. - properties: - enabled: - description: Enable metrics for this class of gateways. If unspecified, - will inherit behavior from the global Helm configuration. - type: boolean - path: - description: The path used for metrics. - type: string - port: - description: The port used for metrics. - format: int32 - maximum: 65535 - minimum: 1024 - 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 - 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..d361a3965f 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,12 @@ # 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.14.0 name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -37,14 +38,19 @@ spec: description: IngressGateway is the Schema for the ingressgateways 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' + 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' + 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 @@ -55,64 +61,68 @@ spec: description: Defaults is default configuration for all upstream services properties: maxConcurrentRequests: - description: The maximum number of concurrent requests that will - be allowed at a single point in time. Use this to limit HTTP/2 - traffic, since HTTP/2 has many requests per connection. + description: |- + The maximum number of concurrent requests that + will be allowed at a single point in time. Use this to limit HTTP/2 traffic, + since HTTP/2 has many requests per connection. format: int32 type: integer maxConnections: - description: The maximum number of connections a service instance - will be allowed to establish against the given upstream. Use - this to limit HTTP/1.1 traffic, since HTTP/1.1 has a request - per connection. + description: |- + The maximum number of connections a service instance + will be allowed to establish against the given upstream. Use this to limit + HTTP/1.1 traffic, since HTTP/1.1 has a request per connection. format: int32 type: integer maxPendingRequests: - description: The maximum number of requests that will be queued + description: |- + The maximum number of requests that will be queued 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. + 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. + 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. + 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. + 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. + 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. + 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 - listen on, and what services to associated to those ports. + description: |- + Listeners declares what ports the ingress gateway should listen on, and + what services to associated to those ports. items: description: IngressListener manages the configuration for a listener on a specific port. @@ -122,110 +132,119 @@ spec: should listen for traffic. type: integer protocol: - description: 'Protocol declares what type of traffic this listener - is expected to receive. Depending on the protocol, a listener - might support multiplexing services over a single port, or - additional discovery chain features. The current supported - values are: (tcp | http | http2 | grpc).' + description: |- + Protocol declares what type of traffic this listener is expected to + receive. Depending on the protocol, a listener might support multiplexing + services over a single port, or additional discovery chain features. The + current supported values are: (tcp | http | http2 | grpc). type: string services: - description: Services declares the set of services to which - the listener forwards traffic. For "tcp" protocol listeners, - only a single service is allowed. For "http" listeners, multiple - services can be declared. + description: |- + Services declares the set of services to which the listener forwards + traffic. + For "tcp" protocol listeners, only a single service is allowed. + For "http" listeners, multiple services can be declared. items: - description: IngressService manages configuration for services - that are exposed to ingress traffic. + description: |- + IngressService manages configuration for services that are exposed to + ingress traffic. properties: hosts: - description: "Hosts is a list of hostnames which should - be associated to this service on the defined listener. - Only allowed on layer 7 protocols, this will be used - to route traffic to the service by matching the Host - header of the HTTP request. \n If a host is provided - for a service that also has a wildcard specifier defined, - the host will override the wildcard-specifier-provided - \".*\" domain for that listener. \n This - cannot be specified when using the wildcard specifier, - \"*\", or when using a \"tcp\" listener." + description: |- + Hosts is a list of hostnames which should be associated to this service on + the defined listener. Only allowed on layer 7 protocols, this will be used + to route traffic to the service by matching the Host header of the HTTP + request. + + + If a host is provided for a service that also has a wildcard specifier + defined, the host will override the wildcard-specifier-provided + ".*" domain for that listener. + + + This cannot be specified when using the wildcard specifier, "*", or when + using a "tcp" listener. items: type: string type: array maxConcurrentRequests: - description: The maximum number of concurrent requests - that will be allowed at a single point in time. Use - this to limit HTTP/2 traffic, since HTTP/2 has many - requests per connection. + description: |- + The maximum number of concurrent requests that + will be allowed at a single point in time. Use this to limit HTTP/2 traffic, + since HTTP/2 has many requests per connection. format: int32 type: integer maxConnections: - description: The maximum number of connections a service - instance will be allowed to establish against the given - upstream. Use this to limit HTTP/1.1 traffic, since - HTTP/1.1 has a request per connection. + description: |- + The maximum number of connections a service instance + will be allowed to establish against the given upstream. Use this to limit + HTTP/1.1 traffic, since HTTP/1.1 has a request per connection. format: int32 type: integer maxPendingRequests: - description: The maximum number of requests that will - be queued while waiting for a connection to be established. + description: |- + The maximum number of requests that will be queued + while waiting for a connection to be established. format: int32 type: integer name: - description: "Name declares the service to which traffic - should be forwarded. \n This can either be a specific - service, or the wildcard specifier, \"*\". If the wildcard - specifier is provided, the listener must be of \"http\" - protocol and means that the listener will forward traffic - to all services. \n A name can be specified on multiple - listeners, and will be exposed on both of the listeners." + description: |- + Name declares the service to which traffic should be forwarded. + + + This can either be a specific service, or the wildcard specifier, + "*". If the wildcard specifier is provided, the listener must be of "http" + protocol and means that the listener will forward traffic to all services. + + + A name can be specified on multiple listeners, and will be exposed on both + of the listeners. type: string namespace: - description: Namespace is the namespace where the service - is located. Namespacing is a Consul Enterprise feature. + description: |- + Namespace is the namespace where the service is located. + Namespacing is a Consul Enterprise feature. type: string partition: - description: Partition is the admin-partition where the - service is located. Partitioning is a Consul Enterprise - feature. + description: |- + Partition is the admin-partition where the 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. + 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. + 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. + 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 + 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. + 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. + description: |- + MaxFailures is the count of consecutive failures that results in a host + being removed from the pool. format: int32 type: integer type: object @@ -235,50 +254,52 @@ spec: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that - should be appended to the request or response (i.e. - allowing duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that - should be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that - should be added to the request or response, overwriting - any existing header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting any existing header values of the same name. type: object type: object responseHeaders: - description: HTTPHeaderModifiers is a set of rules for - HTTP header modification that should be performed by - proxies as the request passes through them. It can operate - on either request or response headers depending on the - context in which it is used. + description: |- + HTTPHeaderModifiers is a set of rules for HTTP header modification that + should be performed by proxies as the request passes through them. It can + operate on either request or response headers depending on the context in + which it is used. properties: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that - should be appended to the request or response (i.e. - allowing duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that - should be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that - should be added to the request or response, overwriting - any existing header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting any existing header values of the same name. type: object type: object tls: @@ -295,10 +316,9 @@ spec: from the SDS service. type: string clusterName: - description: ClusterName is the SDS cluster name - to connect to, to retrieve certificates. This - cluster must be specified in the Gateway's bootstrap - configuration. + description: |- + ClusterName is the SDS cluster name to connect to, to retrieve certificates. + This cluster must be specified in the Gateway's bootstrap configuration. type: string type: object type: object @@ -308,9 +328,9 @@ spec: description: TLS config for this listener. properties: cipherSuites: - description: Define a subset of cipher suites to restrict - Only applicable to connections negotiated via TLS 1.2 - or earlier. + description: |- + Define a subset of cipher suites to restrict + Only applicable to connections negotiated via TLS 1.2 or earlier. items: type: string type: array @@ -328,24 +348,23 @@ spec: service. type: string clusterName: - description: ClusterName is the SDS cluster name to - connect to, to retrieve certificates. This cluster - must be specified in the Gateway's bootstrap configuration. + description: |- + ClusterName is the SDS cluster name to connect to, to retrieve certificates. + This cluster must be specified in the Gateway's bootstrap configuration. type: string type: object tlsMaxVersion: - description: TLSMaxVersion sets the default maximum TLS - version supported. Must be greater than or equal to `TLSMinVersion`. - One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or - `TLSv1_3`. If unspecified, Envoy will default to TLS 1.3 - as a max version for incoming connections. + description: |- + TLSMaxVersion sets the default maximum TLS version supported. Must be greater than or equal to `TLSMinVersion`. + One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. + If unspecified, Envoy will default to TLS 1.3 as a max version for incoming connections. type: string tlsMinVersion: - description: TLSMinVersion sets the default minimum TLS - version supported. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, - `TLSv1_2`, or `TLSv1_3`. If unspecified, Envoy v1.22.0 - and newer will default to TLS 1.2 as a min version, while - older releases of Envoy default to TLS 1.0. + description: |- + TLSMinVersion sets the default minimum TLS version supported. + One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. + If unspecified, Envoy v1.22.0 and newer will default to TLS 1.2 as a min version, + while older releases of Envoy default to TLS 1.0. type: string required: - enabled @@ -356,8 +375,9 @@ spec: description: TLS holds the TLS configuration for this gateway. properties: cipherSuites: - description: Define a subset of cipher suites to restrict Only - applicable to connections negotiated via TLS 1.2 or earlier. + description: |- + Define a subset of cipher suites to restrict + Only applicable to connections negotiated via TLS 1.2 or earlier. items: type: string type: array @@ -374,24 +394,23 @@ spec: when fetching the certificate from the SDS service. type: string clusterName: - description: ClusterName is the SDS cluster name to connect - to, to retrieve certificates. This cluster must be specified - in the Gateway's bootstrap configuration. + description: |- + ClusterName is the SDS cluster name to connect to, to retrieve certificates. + This cluster must be specified in the Gateway's bootstrap configuration. type: string type: object tlsMaxVersion: - description: TLSMaxVersion sets the default maximum TLS version - supported. Must be greater than or equal to `TLSMinVersion`. + description: |- + TLSMaxVersion sets the default maximum TLS version supported. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. - If unspecified, Envoy will default to TLS 1.3 as a max version - for incoming connections. + If unspecified, Envoy will default to TLS 1.3 as a max version for incoming connections. type: string tlsMinVersion: - description: TLSMinVersion sets the default minimum TLS version - supported. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, - or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer will default - to TLS 1.2 as a min version, while older releases of Envoy default - to TLS 1.0. + description: |- + TLSMinVersion sets the default minimum TLS version supported. + One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. + If unspecified, Envoy v1.22.0 and newer will default to TLS 1.2 as a min version, + while older releases of Envoy default to TLS 1.0. type: string required: - enabled @@ -403,8 +422,9 @@ spec: 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' + 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 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..fca3bcd935 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,12 @@ # 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.14.0 name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -35,25 +36,25 @@ spec: description: Mesh is the Schema for the mesh 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' + 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' + 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: 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: @@ -67,80 +68,73 @@ spec: mesh. properties: peerThroughMeshGateways: - description: PeerThroughMeshGateways determines whether peering - traffic between control planes should flow through mesh gateways. - If enabled, Consul servers will advertise mesh gateway addresses - as their own. Additionally, mesh gateways will configure themselves - to expose the local servers using a peering-specific SNI. + description: |- + PeerThroughMeshGateways determines whether peering traffic between + control planes should flow through mesh gateways. If enabled, + Consul servers will advertise mesh gateway addresses as their own. + Additionally, mesh gateways will configure themselves to expose + the local servers using a peering-specific SNI. type: boolean type: object tls: description: TLS defines the TLS configuration for the service mesh. properties: incoming: - description: Incoming defines the TLS configuration for inbound - mTLS connections targeting the public listener on Connect and - TerminatingGateway proxy kinds. + description: |- + Incoming defines the TLS configuration for inbound mTLS connections targeting + the public listener on Connect and TerminatingGateway proxy kinds. properties: cipherSuites: - description: CipherSuites sets the default list of TLS cipher - suites to support when negotiating connections using TLS - 1.2 or earlier. If unspecified, Envoy will use a default - server cipher list. The list of supported cipher suites - can be seen in https://github.com/hashicorp/consul/blob/v1.11.2/types/tls.go#L154-L169 - and is dependent on underlying support in Envoy. Future - releases of Envoy may remove currently-supported but insecure - cipher suites, and future releases of Consul may add new - supported cipher suites if any are added to Envoy. + description: |- + CipherSuites sets the default list of TLS cipher suites to support when negotiating connections using TLS 1.2 or earlier. + If unspecified, Envoy will use a default server cipher list. The list of supported cipher suites can be seen in + https://github.com/hashicorp/consul/blob/v1.11.2/types/tls.go#L154-L169 and is dependent on underlying support in Envoy. + Future releases of Envoy may remove currently-supported but insecure cipher suites, + and future releases of Consul may add new supported cipher suites if any are added to Envoy. items: type: string type: array tlsMaxVersion: - description: TLSMaxVersion sets the default maximum TLS version - supported. Must be greater than or equal to `TLSMinVersion`. + description: |- + TLSMaxVersion sets the default maximum TLS version supported. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. - If unspecified, Envoy will default to TLS 1.3 as a max version - for incoming connections. + If unspecified, Envoy will default to TLS 1.3 as a max version for incoming connections. type: string tlsMinVersion: - description: TLSMinVersion sets the default minimum TLS version - supported. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, - or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer will - default to TLS 1.2 as a min version, while older releases - of Envoy default to TLS 1.0. + description: |- + TLSMinVersion sets the default minimum TLS version supported. + One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. + If unspecified, Envoy v1.22.0 and newer will default to TLS 1.2 as a min version, + while older releases of Envoy default to TLS 1.0. type: string type: object outgoing: - description: Outgoing defines the TLS configuration for outbound - mTLS connections dialing upstreams from Connect and IngressGateway - proxy kinds. + description: |- + Outgoing defines the TLS configuration for outbound mTLS connections dialing upstreams + from Connect and IngressGateway proxy kinds. properties: cipherSuites: - description: CipherSuites sets the default list of TLS cipher - suites to support when negotiating connections using TLS - 1.2 or earlier. If unspecified, Envoy will use a default - server cipher list. The list of supported cipher suites - can be seen in https://github.com/hashicorp/consul/blob/v1.11.2/types/tls.go#L154-L169 - and is dependent on underlying support in Envoy. Future - releases of Envoy may remove currently-supported but insecure - cipher suites, and future releases of Consul may add new - supported cipher suites if any are added to Envoy. + description: |- + CipherSuites sets the default list of TLS cipher suites to support when negotiating connections using TLS 1.2 or earlier. + If unspecified, Envoy will use a default server cipher list. The list of supported cipher suites can be seen in + https://github.com/hashicorp/consul/blob/v1.11.2/types/tls.go#L154-L169 and is dependent on underlying support in Envoy. + Future releases of Envoy may remove currently-supported but insecure cipher suites, + and future releases of Consul may add new supported cipher suites if any are added to Envoy. items: type: string type: array tlsMaxVersion: - description: TLSMaxVersion sets the default maximum TLS version - supported. Must be greater than or equal to `TLSMinVersion`. + description: |- + TLSMaxVersion sets the default maximum TLS version supported. Must be greater than or equal to `TLSMinVersion`. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. - If unspecified, Envoy will default to TLS 1.3 as a max version - for incoming connections. + If unspecified, Envoy will default to TLS 1.3 as a max version for incoming connections. type: string tlsMinVersion: - description: TLSMinVersion sets the default minimum TLS version - supported. One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, - or `TLSv1_3`. If unspecified, Envoy v1.22.0 and newer will - default to TLS 1.2 as a min version, while older releases - of Envoy default to TLS 1.0. + description: |- + TLSMinVersion sets the default minimum TLS version supported. + One of `TLS_AUTO`, `TLSv1_0`, `TLSv1_1`, `TLSv1_2`, or `TLSv1_3`. + If unspecified, Envoy v1.22.0 and newer will default to TLS 1.2 as a min version, + while older releases of Envoy default to TLS 1.0. type: string type: object type: object @@ -149,13 +143,21 @@ spec: to proxies in "transparent" mode. Added in v1.10.0. properties: meshDestinationsOnly: - description: MeshDestinationsOnly determines whether sidecar proxies - operating in "transparent" mode can proxy traffic to IP addresses - not registered in Consul's catalog. If enabled, traffic will - only be proxied to upstreams with service registrations in the - catalog. + description: |- + MeshDestinationsOnly determines whether sidecar proxies operating in "transparent" mode can proxy traffic + to IP addresses not registered in Consul's catalog. If enabled, traffic will only be proxied to upstreams + with service registrations in the catalog. type: boolean type: object + validateClusters: + description: |- + ValidateClusters controls whether the clusters the route table refers to are validated. The default value is + false. When set to false and a route refers to a cluster that does not exist, the route table loads and routing + to a non-existent cluster results in a 404. When set to true and the route is set to a cluster that do not exist, + the route table will not load. For more information, refer to + [HTTP route configuration in the Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route.proto#envoy-v3-api-field-config-route-v3-routeconfiguration-validate-clusters) + for more details. + type: boolean type: object status: properties: @@ -163,8 +165,9 @@ spec: 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' + 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 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..67c9d7bae0 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,12 @@ # 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.14.0 name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -37,14 +38,19 @@ spec: description: PeeringAcceptor is the Schema for the peeringacceptors 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' + 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' + 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 @@ -80,8 +86,9 @@ spec: 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' + 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 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..3832dd5e54 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,12 @@ # 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.14.0 name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -37,14 +38,19 @@ spec: description: PeeringDialer is the Schema for the peeringdialers 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' + 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' + 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 @@ -80,8 +86,9 @@ spec: 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' + 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 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..9b28b00572 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,12 @@ # 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.14.0 name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -37,14 +38,19 @@ spec: description: ProxyDefaults is the Schema for the proxydefaults 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' + 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' + 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 @@ -56,37 +62,40 @@ spec: configuration. properties: disableListenerLogs: - description: DisableListenerLogs turns off just listener logs - for connections rejected by Envoy because they don't have a - matching listener filter. + 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.' + 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.' + 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" + 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 + 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: @@ -110,9 +119,9 @@ spec: for Envoy. properties: checks: - description: Checks defines whether paths associated with Consul - checks will be exposed. This flag triggers exposing all HTTP - and GRPC check paths registered for the service. + description: |- + Checks defines whether paths associated with Consul checks will be exposed. + This flag triggers exposing all HTTP and GRPC check paths registered for the service. type: boolean paths: description: Paths is the list of paths exposed through the proxy. @@ -131,87 +140,49 @@ spec: ie. "/metrics". type: string protocol: - description: Protocol describes the upstream's service protocol. + description: |- + Protocol describes the upstream's service protocol. Valid values are "http" and "http2", defaults to "http". type: string 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. properties: mode: - description: Mode is the mode that should be used for the upstream - connection. One of none, local, or remote. + description: |- + Mode is the mode that should be used for the upstream connection. + One of none, local, or remote. type: string type: object mode: - description: 'Mode can be one of "direct" or "transparent". "transparent" - represents that inbound and outbound application traffic is being - captured and redirected through the proxy. This mode does not enable - the traffic redirection itself. Instead it signals Consul to configure - Envoy as if traffic is already being redirected. "direct" represents - that the proxy''s listeners must be dialed directly by the local - application and other proxies. Note: This cannot be set using the - 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.' + description: |- + Mode can be one of "direct" or "transparent". "transparent" represents that inbound and outbound + application traffic is being captured and redirected through the proxy. This mode does not + enable the traffic redirection itself. Instead it signals Consul to configure Envoy as if + traffic is already being redirected. "direct" represents that the proxy's listeners must be + dialed directly by the local application and other proxies. + Note: This cannot be set using the CRD and should be set using annotations on the + services that are part of the mesh. 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 - CRD and should be set using annotations on the services that are - part of the mesh.' + description: |- + TransparentProxy controls configuration specific to proxies in transparent mode. + Note: This cannot be set using the CRD and should be set using annotations on the + services that are part of the mesh. properties: dialedDirectly: - description: DialedDirectly indicates whether transparent proxies - can dial this proxy instance directly. The discovery chain is - not considered when dialing a service instance directly. This - setting is useful when addressing stateful services, such as - a database cluster with a leader node. + description: |- + DialedDirectly indicates whether transparent proxies can dial this proxy instance directly. + The discovery chain is not considered when dialing a service instance directly. + This setting is useful when addressing stateful services, such as a database cluster with a leader node. type: boolean outboundListenerPort: - description: OutboundListenerPort is the port of the listener - where outbound application traffic is being redirected to. + description: |- + OutboundListenerPort is the port of the listener where outbound application + traffic is being redirected to. type: integer type: object type: object @@ -221,8 +192,9 @@ spec: 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' + 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 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..00f1c85ec5 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,12 @@ # 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.14.0 name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -37,14 +38,19 @@ spec: description: ServiceDefaults is the Schema for the servicedefaults 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' + 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' + 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 @@ -52,27 +58,29 @@ 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. + 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 - configured in "transparent" mode. Destinations live outside of Consul's - catalog, and because of this, they do not require an artificial - node to be created. + description: |- + Destination is an address(es)/port combination that represents an endpoint + outside the mesh. This is only valid when the mesh is configured in "transparent" + mode. Destinations live outside of Consul's catalog, and because of this, they + do not require an artificial node to be created. properties: addresses: - description: Addresses is a list of IPs and/or hostnames that - can be dialed and routed through a terminating gateway. + description: |- + Addresses is a list of IPs and/or hostnames that can be dialed + and routed through a terminating gateway. items: type: string type: array port: - description: Port is the port that can be dialed on any of the - addresses in this Destination. + description: |- + Port is the port that can be dialed on any of the addresses in this + Destination. format: int32 type: integer type: object @@ -97,9 +105,9 @@ spec: for Envoy. properties: checks: - description: Checks defines whether paths associated with Consul - checks will be exposed. This flag triggers exposing all HTTP - and GRPC check paths registered for the service. + description: |- + Checks defines whether paths associated with Consul checks will be exposed. + This flag triggers exposing all HTTP and GRPC check paths registered for the service. type: boolean paths: description: Paths is the list of paths exposed through the proxy. @@ -118,202 +126,130 @@ spec: ie. "/metrics". type: string protocol: - description: Protocol describes the upstream's service protocol. + description: |- + Protocol describes the upstream's service protocol. Valid values are "http" and "http2", defaults to "http". type: string type: object type: array type: object externalSNI: - description: ExternalSNI is an optional setting that allows for the - TLS SNI value to be changed to a non-connect value when federating - with an external system. + description: |- + ExternalSNI is an optional setting that allows for the TLS SNI value + to be changed to a non-connect value when federating 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: |- + 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: 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 + 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 - inbound connections to each service instance. Defaults to 0 (using - consul's default) if not set. + description: |- + MaxInboundConnections is the maximum number of concurrent inbound connections to + each service instance. Defaults to 0 (using consul's default) if not set. type: integer meshGateway: description: MeshGateway controls the default mesh gateway configuration for this service. properties: mode: - description: Mode is the mode that should be used for the upstream - connection. One of none, local, or remote. + description: |- + Mode is the mode that should be used for the upstream connection. + One of none, local, or remote. type: string type: object mode: - description: 'Mode can be one of "direct" or "transparent". "transparent" - represents that inbound and outbound application traffic is being - captured and redirected through the proxy. This mode does not enable - the traffic redirection itself. Instead it signals Consul to configure - Envoy as if traffic is already being redirected. "direct" represents - that the proxy''s listeners must be dialed directly by the local - application and other proxies. Note: This cannot be set using the - 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.' + description: |- + Mode can be one of "direct" or "transparent". "transparent" represents that inbound and outbound + application traffic is being captured and redirected through the proxy. This mode does not + enable the traffic redirection itself. Instead it signals Consul to configure Envoy as if + traffic is already being redirected. "direct" represents that the proxy's listeners must be + dialed directly by the local application and other proxies. + Note: This cannot be set using the CRD and should be set using annotations on the + services that are part of the mesh. 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. + 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 - CRD and should be set using annotations on the services that are - part of the mesh.' + description: |- + TransparentProxy controls configuration specific to proxies in transparent mode. + Note: This cannot be set using the CRD and should be set using annotations on the + services that are part of the mesh. properties: dialedDirectly: - description: DialedDirectly indicates whether transparent proxies - can dial this proxy instance directly. The discovery chain is - not considered when dialing a service instance directly. This - setting is useful when addressing stateful services, such as - a database cluster with a leader node. + description: |- + DialedDirectly indicates whether transparent proxies can dial this proxy instance directly. + The discovery chain is not considered when dialing a service instance directly. + This setting is useful when addressing stateful services, such as a database cluster with a leader node. type: boolean outboundListenerPort: - description: OutboundListenerPort is the port of the listener - where outbound application traffic is being redirected to. + description: |- + OutboundListenerPort is the port of the listener where outbound application + traffic is being redirected to. type: integer type: object upstreamConfig: - description: UpstreamConfig controls default configuration settings - that apply across all upstreams, and per-upstream configuration - overrides. Note that per-upstream configuration applies across all - federated datacenters to the pairing of source and upstream destination - services. + description: |- + UpstreamConfig controls default configuration settings that apply across all upstreams, + and per-upstream configuration overrides. Note that per-upstream configuration applies + across all federated datacenters to the pairing of source and upstream destination services. properties: defaults: - description: Defaults contains default configuration for all upstreams - of a given service. The name field must be empty. + description: |- + Defaults contains default configuration for all upstreams of a given + service. The name field must be empty. properties: connectTimeoutMs: - description: ConnectTimeoutMs is the number of milliseconds - to timeout making a new connection to this upstream. Defaults - to 5000 (5 seconds) if not set. + description: |- + ConnectTimeoutMs is the number of milliseconds to timeout making a new + connection to this upstream. Defaults to 5000 (5 seconds) if not set. type: integer envoyClusterJSON: - description: 'EnvoyClusterJSON is a complete override ("escape - hatch") for the upstream''s cluster. The Connect client - TLS certificate and context will be injected overriding - any TLS settings present. Note: This escape hatch is NOT - compatible with the discovery chain and will be ignored - if a discovery chain is active.' + description: |- + EnvoyClusterJSON is a complete override ("escape hatch") for the upstream's + cluster. The Connect client TLS certificate and context will be injected + overriding any TLS settings present. + Note: This escape hatch is NOT compatible with the discovery chain and + will be ignored if a discovery chain is active. type: string envoyListenerJSON: - description: 'EnvoyListenerJSON is a complete override ("escape - hatch") for the upstream''s listener. Note: This escape - hatch is NOT compatible with the discovery chain and will - be ignored if a discovery chain is active.' + description: |- + EnvoyListenerJSON is a complete override ("escape hatch") for the upstream's + listener. + Note: This escape hatch is NOT compatible with the discovery chain and + will be ignored if a discovery chain is active. type: string limits: - description: Limits are the set of limits that are applied - to the proxy for a specific upstream of a service instance. + description: |- + Limits are the set of limits that are applied to the proxy for a specific upstream of a + service instance. properties: maxConcurrentRequests: - description: MaxConcurrentRequests is the maximum number - of in-flight requests that will be allowed to the upstream - cluster at a point in time. This is mostly applicable - to HTTP/2 clusters since all HTTP/1.1 requests are limited - by MaxConnections. + description: |- + MaxConcurrentRequests is the maximum number of in-flight requests that will be allowed + to the upstream cluster at a point in time. This is mostly applicable to HTTP/2 + clusters since all HTTP/1.1 requests are limited by MaxConnections. type: integer maxConnections: - description: MaxConnections is the maximum number of connections - the local proxy can make to the upstream service. + description: |- + MaxConnections is the maximum number of connections the local proxy can + make to the upstream service. type: integer maxPendingRequests: - description: MaxPendingRequests is the maximum number - of requests that will be queued waiting for an available - connection. This is mostly applicable to HTTP/1.1 clusters - since all HTTP/2 requests are streamed over a single + description: |- + MaxPendingRequests is the maximum number of requests that will be queued + waiting for an available connection. This is mostly applicable to HTTP/1.1 + clusters since all HTTP/2 requests are streamed over a single connection. type: integer type: object @@ -322,8 +258,9 @@ spec: are configured and used. properties: mode: - description: Mode is the mode that should be used for - the upstream connection. One of none, local, or remote. + description: |- + Mode is the mode that should be used for the upstream connection. + One of none, local, or remote. type: string type: object name: @@ -339,42 +276,40 @@ spec: config entry. type: string passiveHealthCheck: - description: PassiveHealthCheck configuration determines how - upstream proxy instances will be monitored for removal from - the load balancing pool. + 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. + 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. + 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. + 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. + 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. + description: |- + MaxFailures is the count of consecutive failures that results in a host + being removed from the pool. format: int32 type: integer type: object @@ -383,59 +318,61 @@ spec: config entry. type: string protocol: - description: Protocol describes the upstream's service protocol. - Valid values are "tcp", "http" and "grpc". Anything else - is treated as tcp. This enables protocol aware features - like per-request metrics and connection pooling, tracing, + description: |- + Protocol describes the upstream's service protocol. Valid values are "tcp", + "http" and "grpc". Anything else is treated as tcp. This enables protocol + aware features like per-request metrics and connection pooling, tracing, routing etc. type: string type: object overrides: - description: Overrides is a slice of per-service configuration. - The name field is required. + description: |- + Overrides is a slice of per-service configuration. The name field is + required. items: properties: connectTimeoutMs: - description: ConnectTimeoutMs is the number of milliseconds - to timeout making a new connection to this upstream. Defaults - to 5000 (5 seconds) if not set. + description: |- + ConnectTimeoutMs is the number of milliseconds to timeout making a new + connection to this upstream. Defaults to 5000 (5 seconds) if not set. type: integer envoyClusterJSON: - description: 'EnvoyClusterJSON is a complete override ("escape - hatch") for the upstream''s cluster. The Connect client - TLS certificate and context will be injected overriding - any TLS settings present. Note: This escape hatch is NOT - compatible with the discovery chain and will be ignored - if a discovery chain is active.' + description: |- + EnvoyClusterJSON is a complete override ("escape hatch") for the upstream's + cluster. The Connect client TLS certificate and context will be injected + overriding any TLS settings present. + Note: This escape hatch is NOT compatible with the discovery chain and + will be ignored if a discovery chain is active. type: string envoyListenerJSON: - description: 'EnvoyListenerJSON is a complete override ("escape - hatch") for the upstream''s listener. Note: This escape - hatch is NOT compatible with the discovery chain and will - be ignored if a discovery chain is active.' + description: |- + EnvoyListenerJSON is a complete override ("escape hatch") for the upstream's + listener. + Note: This escape hatch is NOT compatible with the discovery chain and + will be ignored if a discovery chain is active. type: string limits: - description: Limits are the set of limits that are applied - to the proxy for a specific upstream of a service instance. + description: |- + Limits are the set of limits that are applied to the proxy for a specific upstream of a + service instance. properties: maxConcurrentRequests: - description: MaxConcurrentRequests is the maximum number - of in-flight requests that will be allowed to the - upstream cluster at a point in time. This is mostly - applicable to HTTP/2 clusters since all HTTP/1.1 requests - are limited by MaxConnections. + description: |- + MaxConcurrentRequests is the maximum number of in-flight requests that will be allowed + to the upstream cluster at a point in time. This is mostly applicable to HTTP/2 + clusters since all HTTP/1.1 requests are limited by MaxConnections. type: integer maxConnections: - description: MaxConnections is the maximum number of - connections the local proxy can make to the upstream - service. + description: |- + MaxConnections is the maximum number of connections the local proxy can + make to the upstream service. type: integer maxPendingRequests: - description: MaxPendingRequests is the maximum number - of requests that will be queued waiting for an available - connection. This is mostly applicable to HTTP/1.1 - clusters since all HTTP/2 requests are streamed over - a single connection. + description: |- + MaxPendingRequests is the maximum number of requests that will be queued + waiting for an available connection. This is mostly applicable to HTTP/1.1 + clusters since all HTTP/2 requests are streamed over a single + connection. type: integer type: object meshGateway: @@ -443,8 +380,9 @@ spec: are configured and used. properties: mode: - description: Mode is the mode that should be used for - the upstream connection. One of none, local, or remote. + description: |- + Mode is the mode that should be used for the upstream connection. + One of none, local, or remote. type: string type: object name: @@ -460,43 +398,40 @@ spec: config entry. type: string passiveHealthCheck: - description: PassiveHealthCheck configuration determines - how upstream proxy instances will be monitored for removal - from the load balancing pool. + 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. + 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. + 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. + 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. + 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. + description: |- + MaxFailures is the count of consecutive failures that results in a host + being removed from the pool. format: int32 type: integer type: object @@ -505,10 +440,10 @@ spec: config entry. type: string protocol: - description: Protocol describes the upstream's service protocol. - Valid values are "tcp", "http" and "grpc". Anything else - is treated as tcp. This enables protocol aware features - like per-request metrics and connection pooling, tracing, + description: |- + Protocol describes the upstream's service protocol. Valid values are "tcp", + "http" and "grpc". Anything else is treated as tcp. This enables protocol + aware features like per-request metrics and connection pooling, tracing, routing etc. type: string type: object @@ -521,8 +456,9 @@ spec: 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' + 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 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..e2b66a3621 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,12 @@ # 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.14.0 name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -37,14 +38,19 @@ spec: description: ServiceIntentions is the Schema for the serviceintentions 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' + 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' + 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 @@ -56,75 +62,38 @@ spec: the authorization granted to. properties: name: - 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. + 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 namespace: - description: Namespace specifies the namespace the config entry - will apply to. This may be set to the wildcard character (*) - to match all services in all namespaces that don't otherwise - have intentions defined. + description: |- + Namespace specifies the namespace the config entry will apply to. + This may be set to the wildcard character (*) to match all services + in all namespaces that don't otherwise 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 - 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. + description: |- + Sources is the list of all intention sources and the authorization granted to those sources. + 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. items: properties: action: - description: 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. + description: |- + 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. type: string description: description: Description for the intention. This is not used by Consul, but is presented in API responses to assist tooling. type: string name: - description: Name is the source of the intention. This is the - name of a Consul service. The service doesn't need to be registered. + description: |- + Name is the source of the intention. This is the name of a + Consul service. The service doesn't need to be registered. type: string namespace: description: Namespace is the namespace for the Name parameter. @@ -133,34 +102,32 @@ 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 - that extend the intention match criteria. Permission precedence - is applied top to bottom. For any given request the first - permission to match in the list is terminal and stops further - evaluation. As with L4 intentions, traffic that fails to match - any of the provided permissions in this intention will be - subject to the default intention behavior is defined by the - default ACL policy. This should be omitted for an L4 intention + description: |- + Permissions is the list of all additional L7 attributes that extend the intention match criteria. + Permission precedence is applied top to bottom. For any given request the first permission to match + in the list is terminal and stops further evaluation. As with L4 intentions, traffic that fails to + match any of the provided permissions in this intention will be subject to the default intention + behavior is defined by the default ACL policy. This should be omitted for an L4 intention as it is mutually exclusive with the Action field. items: properties: action: - description: Action is one of "allow" or "deny" for the - action that should be taken if this permission matches - a request. + description: |- + Action is one of "allow" or "deny" for the action that + should be taken if this permission matches a request. type: string http: description: HTTP is a set of HTTP-specific authorization criteria. properties: header: - description: Header is a set of criteria that can - match on HTTP request headers. If more than one - is configured all must match for the overall match - to apply. + description: |- + Header is a set of criteria that can match on HTTP request headers. + If more than one is configured all must match for the overall match to apply. items: properties: exact: @@ -194,10 +161,9 @@ spec: type: object type: array methods: - description: Methods is a list of HTTP methods for - which this match applies. If unspecified all HTTP - methods are matched. If provided the names must - be a valid method. + description: |- + Methods is a list of HTTP methods for which this match applies. If unspecified + all HTTP methods are matched. If provided the names must be a valid method. items: type: string type: array @@ -214,50 +180,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 @@ -267,8 +191,9 @@ spec: 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' + 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 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..a4476fa28a 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,12 @@ # 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.14.0 name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -37,14 +38,19 @@ spec: description: ServiceResolver is the Schema for the serviceresolvers 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' + 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' + 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 @@ -52,12 +58,14 @@ spec: description: ServiceResolverSpec defines the desired state of ServiceResolver. properties: connectTimeout: - description: ConnectTimeout is the timeout for establishing new network - connections to this service. + description: |- + ConnectTimeout is the timeout for establishing new network connections + to this service. type: string defaultSubset: - description: DefaultSubset is the subset to use when no explicit subset - is requested. If empty the unnamed subset is used. + description: |- + DefaultSubset is the subset to use when no explicit subset is requested. + If empty the unnamed subset is used. type: string failover: additionalProperties: @@ -69,38 +77,20 @@ spec: type: string type: array namespace: - description: Namespace is the namespace to resolve the requested - 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. + description: |- + Namespace is the namespace to resolve the requested service from to form + the failover group of instances. If empty the current namespace is used. type: string service: - description: Service is the service to resolve instead of the - default as the failover group of instances during failover. + description: |- + Service is the service to resolve instead of the default as the failover + group of instances during failover. type: string serviceSubset: - description: ServiceSubset is the named subset of the requested - service to resolve as the failover group of instances. If - empty the default subset for the requested service is used. + description: |- + ServiceSubset is the named subset of the requested service to resolve as + the failover group of instances. If empty the default subset for the + requested service is used. type: string targets: description: Targets specifies a fixed list of failover targets @@ -134,21 +124,25 @@ spec: type: object type: array type: object - description: Failover controls when and how to reroute traffic to - an alternate pool of service instances. The map is keyed by the - service subset it applies to and the special string "*" is a wildcard - that applies to any subset not otherwise specified here. + description: |- + Failover controls when and how to reroute traffic to an alternate pool of + service instances. + The map is keyed by the service subset it applies to and the special + string "*" is a wildcard that applies to any subset not otherwise + specified here. type: object loadBalancer: - description: LoadBalancer determines the load balancing policy and - configuration for services issuing requests to this upstream service. + description: |- + LoadBalancer determines the load balancing policy and configuration for services + issuing requests to this upstream service. properties: hashPolicies: - description: HashPolicies is a list of hash policies to use for - hashing load balancing algorithms. Hash policies are evaluated - individually and combined such that identical lists result in - the same hash. If no hash policies are present, or none are - successfully evaluated, then a random backend host will be selected. + description: |- + HashPolicies is a list of hash policies to use for hashing load balancing algorithms. + Hash policies are evaluated individually and combined such that identical lists + result in the same hash. + If no hash policies are present, or none are successfully evaluated, + then a random backend host will be selected. items: properties: cookieConfig: @@ -168,26 +162,27 @@ spec: type: string type: object field: - description: Field is the attribute type to hash on. Must - be one of "header", "cookie", or "query_parameter". Cannot - be specified along with sourceIP. + description: |- + Field is the attribute type to hash on. + Must be one of "header", "cookie", or "query_parameter". + Cannot be specified along with sourceIP. type: string fieldValue: - description: FieldValue is the value to hash. ie. header - name, cookie name, URL query parameter name Cannot be - specified along with sourceIP. + description: |- + FieldValue is the value to hash. + ie. header name, cookie name, URL query parameter name + Cannot be specified along with sourceIP. type: string sourceIP: - description: SourceIP determines whether the hash should - be of the source IP rather than of a field and field value. + description: |- + SourceIP determines whether the hash should be of the source IP rather than of a field and field value. Cannot be specified along with field or fieldValue. type: boolean terminal: - description: Terminal will short circuit the computation - of the hash when multiple hash policies are present. If - a hash is computed when a Terminal policy is evaluated, - then that hash will be used and subsequent hash policies - will be ignored. + description: |- + Terminal will short circuit the computation of the hash when multiple hash policies are present. + If a hash is computed when a Terminal policy is evaluated, + then that hash will be used and subsequent hash policies will be ignored. type: boolean type: object type: array @@ -221,81 +216,74 @@ 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 - redirect EXCEPT when the redirect has already been applied. When - substituting the supplied redirect, all other fields besides Kind, - Name, and Redirect will be ignored. + description: |- + Redirect when configured, all attempts to resolve the service this + resolver defines will be substituted for the supplied redirect + EXCEPT when the redirect has already been applied. + When substituting the supplied redirect, all other fields besides + Kind, Name, and Redirect will be ignored. properties: datacenter: - description: Datacenter is the datacenter to resolve the service - from instead of the current one. + description: |- + Datacenter is the datacenter to resolve the service from instead of the + current one. type: string namespace: - description: Namespace is the Consul namespace to resolve the - service from instead of the current namespace. If empty the - current namespace is assumed. + description: |- + Namespace is the Consul namespace to resolve the service from instead of + the current namespace. If empty the current namespace is assumed. type: string partition: - description: Partition is the Consul partition to resolve the - service from instead of the current partition. If empty the - current partition is assumed. + description: |- + Partition is the Consul partition to resolve the service from instead of + the current partition. If empty the current partition is assumed. type: string peer: - 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. + description: |- + Peer is the name of the cluster peer 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. type: string serviceSubset: - description: ServiceSubset is a named subset of the given service - to resolve instead of one defined as that service's DefaultSubset - If empty the default subset is used. + description: |- + ServiceSubset is a named subset of the given service to resolve instead + of one defined as that service's DefaultSubset 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. + description: |- + RequestTimeout is the timeout for receiving an HTTP response from this + service before the connection is terminated. type: string subsets: additionalProperties: properties: filter: - description: Filter is the filter expression to be used for - selecting instances of the requested service. If empty all - healthy instances are returned. This expression can filter - on the same selectors as the Health API endpoint. + description: |- + Filter is the filter expression to be used for selecting instances of the + requested service. If empty all healthy instances are returned. This + expression can filter on the same selectors as the Health API endpoint. type: string onlyPassing: - description: OnlyPassing specifies the behavior of the resolver's - health check interpretation. If this is set to false, instances - with checks in the passing as well as the warning states will - be considered healthy. If this is set to true, only instances - with checks in the passing state will be considered healthy. + description: |- + OnlyPassing specifies the behavior of the resolver's health check + interpretation. If this is set to false, instances with checks in the + passing as well as the warning states will be considered healthy. If this + is set to true, only instances with checks in the passing state will be + considered healthy. type: boolean type: object - description: Subsets is map of subset name to subset definition for - all usable named subsets of this service. The map key is the name - of the subset and all names must be valid DNS subdomain elements. - This may be empty, in which case only the unnamed default subset - will be usable. + description: |- + Subsets is map of subset name to subset definition for all usable named + subsets of this service. The map key is the name of the subset and all + names must be valid DNS subdomain elements. + This may be empty, in which case only the unnamed default subset will + be usable. type: object type: object status: @@ -304,8 +292,9 @@ spec: 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' + 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 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..01c870c0fd 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,12 @@ # 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.14.0 name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -37,14 +38,19 @@ spec: description: ServiceRouter is the Schema for the servicerouters 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' + 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' + 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 @@ -52,10 +58,11 @@ spec: description: ServiceRouterSpec defines the desired state of ServiceRouter. properties: routes: - description: Routes are the list of routes to consider when processing - L7 requests. The first route to match in the list is terminal and - stops further evaluation. Traffic that fails to match any of the - provided routes will be routed to the default service. + description: |- + Routes are the list of routes to consider when processing L7 requests. + The first route to match in the list is terminal and stops further + evaluation. Traffic that fails to match any of the provided routes will + be routed to the default service. items: properties: destination: @@ -63,13 +70,14 @@ spec: request(s) to a service. properties: idleTimeout: - description: IdleTimeout is total amount of time permitted + 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 - empty the current namespace is assumed. + description: |- + Namespace is the Consul namespace to resolve the service from instead of + the current namespace. If empty the current namespace is assumed. type: string numRetries: description: NumRetries is the number of times to retry @@ -77,13 +85,14 @@ spec: format: int32 type: integer partition: - description: Partition is the Consul partition to resolve - the service from instead of the current partition. If - empty the current partition is assumed. + description: |- + Partition is the Consul partition to resolve the service from instead of + the current partition. If empty the current partition is assumed. type: string prefixRewrite: - description: PrefixRewrite defines how to rewrite the HTTP - request path before proxying it to its final destination. + description: |- + PrefixRewrite defines how to rewrite the HTTP request path before proxying + it to its final destination. This requires that either match.http.pathPrefix or match.http.pathExact be configured on this route. type: string @@ -93,61 +102,63 @@ spec: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that - should be appended to the request or response (i.e. - allowing duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that - should be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that - should be added to the request or response, overwriting - any existing header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting any existing header values of the same name. type: object type: object requestTimeout: - description: RequestTimeout is the total amount of time - permitted for the entire downstream request (and retries) - to be processed. + description: |- + RequestTimeout is the total amount of time permitted for the entire + downstream request (and retries) to be processed. type: string responseHeaders: - description: HTTPHeaderModifiers is a set of rules for HTTP - header modification that should be performed by proxies - as the request passes through them. It can operate on - either request or response headers depending on the context - in which it is used. + description: |- + HTTPHeaderModifiers is a set of rules for HTTP header modification that + should be performed by proxies as the request passes through them. It can + operate on either request or response headers depending on the context in + which it is used. properties: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that - should be appended to the request or response (i.e. - allowing duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that - should be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that - should be added to the request or response, overwriting - any existing header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting 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' + 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 @@ -163,32 +174,29 @@ spec: type: integer type: array service: - description: Service is the service to resolve instead of - the default service. If empty then the default service - name is used. + description: |- + Service is the service to resolve instead of the default service. + If empty then the default service name is used. type: string serviceSubset: - description: ServiceSubset is a named subset of the given - service to resolve instead of the one defined as that - service's DefaultSubset. If empty, the default subset - is used. + description: |- + ServiceSubset is a named subset of the given service to resolve instead + of the one defined as that service's DefaultSubset. + If empty, the default subset is used. type: string type: object match: - description: Match is a set of criteria that can match incoming - L7 requests. If empty or omitted it acts as a catch-all. + description: |- + Match is a set of criteria that can match incoming L7 requests. + If empty or omitted it acts as a catch-all. properties: 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 - all must match for the overall match to apply. + description: |- + Header is a set of criteria that can match on HTTP request headers. + If more than one is configured all must match for the overall match to apply. items: properties: exact: @@ -223,9 +231,9 @@ spec: type: object type: array methods: - description: Methods is a list of HTTP methods for which - this match applies. If unspecified all http methods - are matched. + description: |- + Methods is a list of HTTP methods for which this match applies. + If unspecified all http methods are matched. items: type: string type: array @@ -242,10 +250,9 @@ spec: on the HTTP request path. type: string queryParam: - description: QueryParam is a set of criteria that can - match on HTTP query parameters. If more than one is - configured all must match for the overall match to - apply. + description: |- + QueryParam is a set of criteria that can match on HTTP query parameters. + If more than one is configured all must match for the overall match to apply. items: properties: exact: @@ -257,8 +264,9 @@ spec: to match on. type: string present: - description: Present will match if the query parameter - with the given name is present with any value. + description: |- + Present will match if the query parameter with the given name is present + with any value. type: boolean regex: description: Regex will match if the query parameter @@ -279,8 +287,9 @@ spec: 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' + 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 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..492231ea58 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,12 @@ # 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.14.0 name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -37,14 +38,19 @@ spec: description: ServiceSplitter is the Schema for the servicesplitters 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' + 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' + 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 @@ -52,20 +58,20 @@ spec: description: ServiceSplitterSpec defines the desired state of ServiceSplitter. properties: splits: - description: Splits defines how much traffic to send to which set - of service instances during a traffic split. The sum of weights - across all splits must add up to 100. + description: |- + Splits defines how much traffic to send to which set of service instances during a traffic split. + The sum of weights across all splits must add up to 100. items: properties: namespace: - description: Namespace is the Consul namespace to resolve the - service from instead of the current namespace. If empty the - current namespace is assumed. + description: |- + Namespace is the Consul namespace to resolve the service from instead of + the current namespace. If empty the current namespace is assumed. type: string partition: - description: Partition is the Consul partition to resolve the - service from instead of the current partition. If empty the - current partition is assumed. + description: |- + Partition is the Consul partition to resolve the service from instead of + the current partition. If empty the current partition is assumed. type: string requestHeaders: description: Allow HTTP header manipulation to be configured. @@ -73,50 +79,52 @@ spec: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that should - be appended to the request or response (i.e. allowing - duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that should - be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that should - be added to the request or response, overwriting any existing - header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting any existing header values of the same name. type: object type: object responseHeaders: - description: HTTPHeaderModifiers is a set of rules for HTTP - header modification that should be performed by proxies as - the request passes through them. It can operate on either - request or response headers depending on the context in which - it is used. + description: |- + HTTPHeaderModifiers is a set of rules for HTTP header modification that + should be performed by proxies as the request passes through them. It can + operate on either request or response headers depending on the context in + which it is used. properties: add: additionalProperties: type: string - description: Add is a set of name -> value pairs that should - be appended to the request or response (i.e. allowing - duplicates if the same header already exists). + description: |- + Add is a set of name -> value pairs that should be appended to the request + or response (i.e. allowing duplicates if the same header already exists). type: object remove: - description: Remove is the set of header names that should - be stripped from the request or response. + description: |- + Remove is the set of header names that should be stripped from the request + or response. items: type: string type: array set: additionalProperties: type: string - description: Set is a set of name -> value pairs that should - be added to the request or response, overwriting any existing - header values of the same name. + description: |- + Set is a set of name -> value pairs that should be added to the request or + response, overwriting any existing header values of the same name. type: object type: object service: @@ -124,13 +132,13 @@ spec: default. type: string serviceSubset: - description: ServiceSubset is a named subset of the given service - to resolve instead of one defined as that service's DefaultSubset. - If empty the default subset is used. + description: |- + ServiceSubset is a named subset of the given service to resolve instead of one defined + as that service's DefaultSubset. If empty the default subset is used. type: string weight: - description: Weight is a value between 0 and 100 reflecting - what portion of traffic should be directed to this split. + description: |- + Weight is a value between 0 and 100 reflecting what portion of traffic should be directed to this split. The smallest representable weight is 1/10000 or .01%. type: number type: object @@ -142,8 +150,9 @@ spec: 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' + 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 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 1b8ab32cd6..8c2f9a907a 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,12 @@ # 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.14.0 name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -38,14 +39,19 @@ spec: 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' + 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' + 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 @@ -60,22 +66,19 @@ spec: gateway. properties: caFile: - description: CAFile is the optional path to a CA certificate - to use for TLS connections from the gateway to the linked - service. + description: |- + CAFile is the optional path to a CA certificate to use for TLS connections + from the gateway to the linked service. type: string certFile: - description: CertFile is the optional path to a client certificate - to use for TLS connections from the gateway to the linked - service. + description: |- + CertFile is the optional path to a client certificate to use for TLS connections + from the gateway to the linked service. type: string - disableAutoHostRewrite: - description: DisableAutoHostRewrite disables terminating gateways - auto host rewrite feature when set to true. - type: boolean keyFile: - description: KeyFile is the optional path to a private key to - use for TLS connections from the gateway to the linked service. + description: |- + KeyFile is the optional path to a private key to use for TLS connections + from the gateway to the linked service. type: string name: description: Name is the name of the service, as defined in @@ -97,8 +100,9 @@ spec: 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' + 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 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 7b0d2a54b9..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_apigateways.yaml +++ /dev/null @@ -1,297 +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 - 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: - addresses: - items: - properties: - type: - default: IPAddress - type: string - value: - type: string - required: - - type - - value - type: object - type: array - listeners: - items: - properties: - attachedRoutes: - format: int32 - type: integer - name: - type: string - 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 - required: - - attachedRoutes - - name - type: object - type: array - 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 - 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 ca2b05d062..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_gatewayclasses.yaml +++ /dev/null @@ -1,117 +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 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: - 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 - required: - - name - type: object - required: - - controllerName - - parametersRef - 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 ff00bd86e5..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml +++ /dev/null @@ -1,601 +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. \n For more details on potential values of this - field, see documentation for Service.ServicePort." - 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 - 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. - For north/south this is TBD. \n For more details - on potential values of this field, see documentation - for Service.ServicePort." - 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 - 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 ae41db0016..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml +++ /dev/null @@ -1,657 +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. \n For more details on potential values of this - field, see documentation for Service.ServicePort." - 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 - 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. - For north/south this is TBD. \n For more details - on potential values of this field, see documentation - for Service.ServicePort." - 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 - 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 dbfb0c9b20..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml +++ /dev/null @@ -1,262 +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. \n For more details on potential values of this - field, see documentation for Service.ServicePort." - 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 - 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. - For north/south this is TBD. \n For more details - on potential values of this field, see documentation - for Service.ServicePort." - 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 - 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 36020e3639..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: v2 - 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..86d0b516ca 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -25,46 +25,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 +65,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 +145,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 +265,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..c2615ddc3a 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -7,111 +7,6 @@ kind: MutatingWebhookConfiguration metadata: 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 +49,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 +133,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 +259,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 f89c6348d0..0000000000 --- a/control-plane/connect-inject/common/annotation_processor.go +++ /dev/null @@ -1,266 +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 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), - }, - 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), - }, - 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 77053540e3..0000000000 --- a/control-plane/connect-inject/common/annotation_processor_test.go +++ /dev/null @@ -1,987 +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(""), - }, - 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(""), - // }, - // 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(""), - // }, - // 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", - // }, - // 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", - }, - 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", - // }, - // 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", - }, - 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(""), - }, - 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", - }, - 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", - // }, - // 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(""), - // }, - // 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", - // }, - // 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", - // }, - // 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", - }, - 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(""), - }, - 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", - }, - 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(""), - }, - 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", - }, - 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", - }, - 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(""), - }, - 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(""), - }, - 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(""), - // }, - // 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", - // }, - // 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", - // }, - // 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(""), - // }, - // 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", - // }, - // 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", - // }, - // 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..a99d9fd12e 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -8,17 +8,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 +64,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 +90,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 2c35315b89..79a9294fe2 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -4,27 +4,13 @@ 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 +153,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,299 +259,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", - }, - } - - 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/common/openshift.go b/control-plane/connect-inject/common/openshift.go deleted file mode 100644 index e8e2f555e8..0000000000 --- a/control-plane/connect-inject/common/openshift.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Function copied from: -// https://github.com/openshift/apiserver-library-go/blob/release-4.17/pkg/securitycontextconstraints/sccmatching/matcher.go -// Apache 2.0 license: https://github.com/openshift/apiserver-library-go/blob/release-4.17/LICENSE - -// A namespace in OpenShift has the following annotations: -// Annotations: openshift.io/sa.scc.mcs: s0:c27,c4 -// openshift.io/sa.scc.uid-range: 1000710000/10000 -// openshift.io/sa.scc.supplemental-groups: 1000710000/10000 -// -// Note: Even though the annotation is named 'range', it is not a range but the ID you should use. All pods in a -// namespace should use the same UID/GID. (1000710000/1000710000 above) - -package common - -import ( - "fmt" - "strconv" - "strings" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - corev1 "k8s.io/api/core/v1" -) - -// GetOpenShiftUID gets the user id from the OpenShift annotation 'openshift.io/sa.scc.uid-range'. -func GetOpenShiftUID(ns *corev1.Namespace) (int64, error) { - annotation, ok := ns.Annotations[constants.AnnotationOpenShiftUIDRange] - if !ok { - return 0, fmt.Errorf("unable to find annotation %s", constants.AnnotationOpenShiftUIDRange) - } - if len(annotation) == 0 { - return 0, fmt.Errorf("found annotation %s but it was empty", constants.AnnotationOpenShiftUIDRange) - } - - uid, err := parseOpenShiftUID(annotation) - if err != nil { - return 0, err - } - - return uid, nil -} - -// parseOpenShiftUID parses the UID "range" from the annotation string. The annotation can either have a '/' or '-' -// as a separator. '-' is the old style of UID from when it used to be an actual range. -// Example annotation value: "1000700000/100000". -func parseOpenShiftUID(val string) (int64, error) { - var uid int64 - var err error - if strings.Contains(val, "/") { - str := strings.Split(val, "/") - uid, err = strconv.ParseInt(str[0], 10, 64) - if err != nil { - return 0, err - } - } - if strings.Contains(val, "-") { - str := strings.Split(val, "-") - uid, err = strconv.ParseInt(str[0], 10, 64) - if err != nil { - return 0, err - } - } - - if !strings.Contains(val, "/") && !strings.Contains(val, "-") { - return 0, fmt.Errorf( - "annotation %s contains an invalid format for value %s", - constants.AnnotationOpenShiftUIDRange, - val, - ) - } - - return uid, nil -} - -// GetOpenShiftGroup gets the group from OpenShift annotation 'openshift.io/sa.scc.supplemental-groups' -// Fall back to the UID annotation if the group annotation does not exist. The values should -// be the same. -func GetOpenShiftGroup(ns *corev1.Namespace) (int64, error) { - annotation, ok := ns.Annotations[constants.AnnotationOpenShiftGroups] - if !ok { - // fall back to UID annotation - annotation, ok = ns.Annotations[constants.AnnotationOpenShiftUIDRange] - if !ok { - return 0, fmt.Errorf( - "unable to find annotation %s or %s", - constants.AnnotationOpenShiftGroups, - constants.AnnotationOpenShiftUIDRange, - ) - } - } - if len(annotation) == 0 { - return 0, fmt.Errorf("found annotation %s but it was empty", constants.AnnotationOpenShiftGroups) - } - - uid, err := parseOpenShiftGroup(annotation) - if err != nil { - return 0, err - } - - return uid, nil -} - -// parseOpenShiftGroup parses the group from the annotation string. The annotation can either have a '/' or ',' -// as a separator. ',' is the old style of UID from when it used to be an actual range. -func parseOpenShiftGroup(val string) (int64, error) { - var group int64 - var err error - if strings.Contains(val, "/") { - str := strings.Split(val, "/") - group, err = strconv.ParseInt(str[0], 10, 64) - if err != nil { - return 0, err - } - } - if strings.Contains(val, ",") { - str := strings.Split(val, ",") - group, err = strconv.ParseInt(str[0], 10, 64) - if err != nil { - return 0, err - } - } - - if !strings.Contains(val, "/") && !strings.Contains(val, ",") { - return 0, fmt.Errorf("annotation %s contains an invalid format for value %s", constants.AnnotationOpenShiftGroups, val) - } - - return group, nil -} diff --git a/control-plane/connect-inject/common/openshift_test.go b/control-plane/connect-inject/common/openshift_test.go deleted file mode 100644 index e4a5178c7a..0000000000 --- a/control-plane/connect-inject/common/openshift_test.go +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 -package common - -import ( - "fmt" - "testing" - - "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" -) - -func TestOpenShiftUID(t *testing.T) { - cases := []struct { - Name string - Namespace func() *corev1.Namespace - Expected int64 - Err string - }{ - { - Name: "Valid uid annotation with slash", - Namespace: func() *corev1.Namespace { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "default", - Annotations: map[string]string{ - constants.AnnotationOpenShiftUIDRange: "1000700000/100000", - }, - }, - } - return ns - }, - Expected: 1000700000, - Err: "", - }, - { - Name: "Valid uid annotation with dash", - Namespace: func() *corev1.Namespace { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "default", - Annotations: map[string]string{ - constants.AnnotationOpenShiftUIDRange: "1234-1000", - }, - }, - } - return ns - }, - Expected: 1234, - Err: "", - }, - { - Name: "Invalid uid annotation missing slash or dash", - Namespace: func() *corev1.Namespace { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "default", - Annotations: map[string]string{ - // annotation should have a slash '/' or dash '-' - constants.AnnotationOpenShiftUIDRange: "5678", - }, - }, - } - return ns - }, - Expected: 0, - Err: fmt.Sprintf( - "annotation %s contains an invalid format for value %s", - constants.AnnotationOpenShiftUIDRange, - "5678", - ), - }, - { - Name: "Missing uid annotation", - Namespace: func() *corev1.Namespace { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "default", - }, - } - return ns - }, - Expected: 0, - Err: fmt.Sprintf("unable to find annotation %s", constants.AnnotationOpenShiftUIDRange), - }, - { - Name: "Empty", - Namespace: func() *corev1.Namespace { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "default", - Annotations: map[string]string{ - constants.AnnotationOpenShiftUIDRange: "", - }, - }, - } - return ns - }, - Expected: 0, - Err: "found annotation openshift.io/sa.scc.uid-range but it was empty", - }, - } - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - actual, err := GetOpenShiftUID(tt.Namespace()) - if tt.Err == "" { - require.NoError(err) - require.Equal(tt.Expected, actual) - } else { - require.EqualError(err, tt.Err) - } - }) - } -} - -func TestOpenShiftGroup(t *testing.T) { - cases := []struct { - Name string - Namespace func() *corev1.Namespace - Expected int64 - Err string - }{ - { - Name: "Valid group annotation with slash", - Namespace: func() *corev1.Namespace { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "default", - Annotations: map[string]string{ - constants.AnnotationOpenShiftGroups: "123456789/1000", - }, - }, - } - return ns - }, - Expected: 123456789, - Err: "", - }, - { - Name: "Valid group annotation with comma", - Namespace: func() *corev1.Namespace { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "default", - Annotations: map[string]string{ - constants.AnnotationOpenShiftGroups: "1234,1000", - }, - }, - } - return ns - }, - Expected: 1234, - Err: "", - }, - { - Name: "Invalid group annotation missing slash or comma", - Namespace: func() *corev1.Namespace { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "default", - Annotations: map[string]string{ - // annotation should have a slash '/' or comma ',' - constants.AnnotationOpenShiftGroups: "5678", - }, - }, - } - return ns - }, - Expected: 0, - Err: fmt.Sprintf( - "annotation %s contains an invalid format for value %s", - constants.AnnotationOpenShiftGroups, - "5678", - ), - }, - { - Name: "Missing group annotation, fall back to UID annotation", - Namespace: func() *corev1.Namespace { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "default", - Annotations: map[string]string{ - // annotation should have a slash '/' or comma ',' - constants.AnnotationOpenShiftUIDRange: "9012/1000", - }, - }, - } - return ns - }, - Expected: 9012, - Err: "", - }, - { - Name: "Missing both group and fallback uid annotation", - Namespace: func() *corev1.Namespace { - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: "default", - }, - } - return ns - }, - Expected: 0, - Err: fmt.Sprintf( - "unable to find annotation %s or %s", - constants.AnnotationOpenShiftGroups, - constants.AnnotationOpenShiftUIDRange, - ), - }, - } - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - actual, err := GetOpenShiftGroup(tt.Namespace()) - if tt.Err == "" { - require.NoError(err) - require.Equal(tt.Expected, actual) - } else { - require.EqualError(err, tt.Err) - } - }) - } -} diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index dca3c523a3..f3824bd071 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -25,8 +25,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 @@ -81,7 +80,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 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" @@ -118,10 +117,8 @@ const ( 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" - AnnotationSidecarProxyLifecycleStartupGracePeriodSeconds = "consul.hashicorp.com/sidecar-proxy-lifecycle-startup-grace-period-seconds" AnnotationSidecarProxyLifecycleGracefulPort = "consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-port" AnnotationSidecarProxyLifecycleGracefulShutdownPath = "consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-shutdown-path" - AnnotationSidecarProxyLifecycleGracefulStartupPath = "consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-startup-path" // annotations for sidecar volumes. AnnotationConsulSidecarUserVolume = "consul.hashicorp.com/consul-sidecar-user-volume" @@ -197,12 +194,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. @@ -224,59 +217,12 @@ 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" AnnotationPrometheusPath = "prometheus.io/path" AnnotationPrometheusPort = "prometheus.io/port" ) - -// Annotations used by OpenShift. -const ( - AnnotationOpenShiftGroups = "openshift.io/sa.scc.supplemental-groups" - AnnotationOpenShiftUIDRange = "openshift.io/sa.scc.uid-range" -) diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 57f276f949..f1d9347d7e 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -4,22 +4,8 @@ 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 +13,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,58 +27,4 @@ const ( // DefaultGracefulShutdownPath is the default path that consul-dataplane uses for graceful shutdown. DefaultGracefulShutdownPath = "/graceful_shutdown" - - // DefaultGracefulStartupPath is the default path that consul-dataplane uses for graceful startup. - DefaultGracefulStartupPath = "/graceful_startup" - - // 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..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 @@ -6,13 +6,12 @@ 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 +19,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..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 @@ -7,15 +7,14 @@ 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 +46,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 fe6c5aed0b..35be18ccd6 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -1,5 +1,6 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 + package endpoints import ( @@ -15,23 +16,23 @@ 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/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/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/helper/parsetags" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) const ( @@ -46,8 +47,8 @@ const ( meshGateway = "mesh-gateway" terminatingGateway = "terminating-gateway" ingressGateway = "ingress-gateway" - apiGateway = "api-gateway" + kubernetesSuccessReasonMsg = "Kubernetes health checks passing" envoyPrometheusBindAddr = "envoy_prometheus_bind_addr" envoyTelemetryCollectorBindSocketDir = "envoy_telemetry_collector_bind_socket_dir" defaultNS = "default" @@ -60,6 +61,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 { @@ -141,7 +148,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 } @@ -310,14 +317,6 @@ 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, "id", proxyServiceRegistration.Service.ID) _, err = apiClient.Catalog().Register(proxyServiceRegistration, nil) @@ -329,20 +328,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) error { @@ -439,11 +424,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 @@ -481,7 +461,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), @@ -492,8 +471,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), @@ -564,8 +543,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. @@ -693,8 +670,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), @@ -809,11 +786,9 @@ 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, %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 { @@ -833,8 +808,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, @@ -926,7 +901,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) @@ -1063,8 +1038,8 @@ func (r *Controller) getGracefulShutdownAndUpdatePodCheck(ctx context.Context, a // Service is nil since we are patching the health status Check: &api.AgentCheck{ CheckID: consulHealthCheckID(pod.Namespace, svc.ServiceID), - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: api.HealthCritical, ServiceID: svc.ServiceID, Output: fmt.Sprintf("Pod \"%s/%s\" is terminating", pod.Namespace, podName), @@ -1381,9 +1356,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 @@ -1417,7 +1391,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, @@ -1507,6 +1481,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 { @@ -1519,26 +1513,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 { @@ -1547,11 +1521,10 @@ 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 + return ok && anno != "" } // isTelemetryCollector checks whether a pod is part of a deployment for a Consul Telemetry Collector. If so, 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..8220f922d5 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 @@ -11,9 +11,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 +26,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 +171,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 +226,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 +445,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 +487,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 +1549,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 +1585,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("*"), @@ -1861,7 +1860,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 +1876,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("*"), @@ -2159,7 +2158,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, @@ -2176,7 +2175,7 @@ func TestReconcileDeleteGatewayWithNamespaces(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("*"), @@ -2232,7 +2231,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 d22dfe0dac..8dc92ead25 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -38,6 +38,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 { @@ -794,37 +847,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, }, }, }, @@ -1056,19 +1109,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, }, }, }, @@ -1145,10 +1198,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, }, }, }, @@ -1217,10 +1270,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, @@ -1290,10 +1343,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, @@ -1358,10 +1411,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, }, }, }, @@ -1425,10 +1478,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, }, }, }, @@ -1528,10 +1581,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, }, }, }, @@ -1631,10 +1684,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, }, }, }, @@ -1732,37 +1785,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, }, }, }, @@ -1873,28 +1926,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, }, }, }, @@ -1913,17 +1966,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", @@ -1944,7 +1986,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, }, } - return []runtime.Object{pod1, node, endpoint} + return []runtime.Object{pod1, endpoint} }, expectedConsulSvcInstances: []*api.CatalogService{ { @@ -1965,10 +2007,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{ @@ -1994,10 +2032,6 @@ 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", @@ -2017,19 +2051,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, }, }, }, @@ -2108,19 +2142,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, }, }, }, @@ -2186,7 +2220,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 { @@ -2203,7 +2236,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) } @@ -2234,36 +2266,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)) - }) -} - func TestReconcile_PodErrorPreservesToken(t *testing.T) { t.Parallel() cases := []struct { @@ -2352,19 +2354,19 @@ func TestReconcile_PodErrorPreservesToken(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, }, }, }, @@ -2569,8 +2571,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", @@ -2596,8 +2598,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", @@ -2621,19 +2623,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, }, }, }, @@ -2680,8 +2682,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", @@ -2707,8 +2709,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", @@ -2732,19 +2734,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, }, }, }, @@ -3022,37 +3024,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, }, }, }, @@ -3946,7 +3948,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", @@ -4011,7 +4013,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", @@ -6954,7 +6956,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{ @@ -7004,54 +7006,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 82a9904201..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ /dev/null @@ -1,643 +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, - }, - } -} - -// 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 93d41f0f11..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ /dev/null @@ -1,2361 +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 - -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..e2ae67aba4 100644 --- a/control-plane/connect-inject/controllers/peering/peering_acceptor_controller.go +++ b/control-plane/connect-inject/controllers/peering/peering_acceptor_controller.go @@ -11,16 +11,13 @@ import ( "time" "github.com/go-logr/logr" - consulv1alpha1 "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/api" 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/utils/pointer" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -28,7 +25,10 @@ import ( "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" + + consulv1alpha1 "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" ) // AcceptorController reconciles a PeeringAcceptor object. @@ -266,7 +266,7 @@ func (r *AcceptorController) updateStatus(ctx context.Context, acceptorObjKey ty return err } if acceptor.Status.LatestPeeringVersion == nil || *acceptor.Status.LatestPeeringVersion < peeringVersion { - acceptor.Status.LatestPeeringVersion = pointer.Uint64(peeringVersion) + acceptor.Status.LatestPeeringVersion = ptr.To(uint64(peeringVersion)) } } err := r.Status().Update(ctx, acceptor) @@ -341,7 +341,7 @@ func (r *AcceptorController) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&consulv1alpha1.PeeringAcceptor{}). Watches( - &source.Kind{Type: &corev1.Secret{}}, + &corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.requestsForPeeringTokens), builder.WithPredicates(predicate.NewPredicateFuncs(r.filterPeeringAcceptors)), ).Complete(r) @@ -375,12 +375,12 @@ func (r *AcceptorController) deletePeering(ctx context.Context, apiClient *api.C // the list of acceptors and creates a request for the acceptor that has the same secret as it's // secretRef and that of the updated secret that is being watched. // We compare it to the secret in the status as the resource has created the secret. -func (r *AcceptorController) requestsForPeeringTokens(object client.Object) []reconcile.Request { +func (r *AcceptorController) requestsForPeeringTokens(ctx context.Context, object client.Object) []reconcile.Request { r.Log.Info("received update for Peering Token Secret", "name", object.GetName(), "namespace", object.GetNamespace()) // Get the list of all acceptors. var acceptorList consulv1alpha1.PeeringAcceptorList - if err := r.Client.List(r.Context, &acceptorList); err != nil { + if err := r.Client.List(ctx, &acceptorList); err != nil { r.Log.Error(err, "failed to list Peering Acceptors") return []ctrl.Request{} } 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..7c5a778ac7 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 @@ -19,7 +19,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -244,7 +244,7 @@ func TestReconcile_CreateUpdatePeeringAcceptor(t *testing.T) { Backend: "kubernetes", }, }, - LatestPeeringVersion: pointer.Uint64(2), + LatestPeeringVersion: ptr.To(uint64(2)), }, expectedConsulPeerings: []*api.Peering{ { @@ -504,7 +504,10 @@ func TestReconcile_CreateUpdatePeeringAcceptor(t *testing.T) { s := runtime.NewScheme() corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(k8sObjects...). + WithStatusSubresource(&v1alpha1.PeeringAcceptor{}). + Build() // Create test consul server. testClient := test.TestServerWithMockConnMgrWatcher(t, nil) @@ -627,7 +630,10 @@ func TestReconcile_DeletePeeringAcceptor(t *testing.T) { s := runtime.NewScheme() corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(k8sObjects...). + WithStatusSubresource(&v1alpha1.PeeringAcceptor{}). + Build() // Create test consulServer server // Create test consul server. @@ -703,7 +709,7 @@ func TestReconcile_VersionAnnotation(t *testing.T) { }, ResourceVersion: "some-old-sha", }, - LatestPeeringVersion: pointer.Uint64(3), + LatestPeeringVersion: ptr.To(uint64(3)), }, }, "is no/op if annotation value is equal to value in status": { @@ -719,7 +725,7 @@ func TestReconcile_VersionAnnotation(t *testing.T) { }, ResourceVersion: "some-old-sha", }, - LatestPeeringVersion: pointer.Uint64(3), + LatestPeeringVersion: ptr.To(uint64(3)), }, }, "updates if annotation value is greater than value in status": { @@ -734,7 +740,7 @@ func TestReconcile_VersionAnnotation(t *testing.T) { Backend: "kubernetes", }, }, - LatestPeeringVersion: pointer.Uint64(4), + LatestPeeringVersion: ptr.To(uint64(4)), }, }, } @@ -765,7 +771,7 @@ func TestReconcile_VersionAnnotation(t *testing.T) { }, ResourceVersion: "some-old-sha", }, - LatestPeeringVersion: pointer.Uint64(3), + LatestPeeringVersion: ptr.To(uint64(3)), }, } secret := createSecret("acceptor-created-secret", "default", "data", "some-data") @@ -775,7 +781,10 @@ func TestReconcile_VersionAnnotation(t *testing.T) { s := runtime.NewScheme() corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(k8sObjects...). + WithStatusSubresource(&v1alpha1.PeeringAcceptor{}). + Build() // Create test consul server. testClient := test.TestServerWithMockConnMgrWatcher(t, nil) @@ -1089,7 +1098,10 @@ func TestAcceptorUpdateStatus(t *testing.T) { s := runtime.NewScheme() corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(k8sObjects...). + WithStatusSubresource(&v1alpha1.PeeringAcceptor{}). + Build() // Create the peering acceptor controller. pac := &AcceptorController{ Client: fakeClient, @@ -1202,7 +1214,10 @@ func TestAcceptorUpdateStatusError(t *testing.T) { s := runtime.NewScheme() corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringAcceptor{}, &v1alpha1.PeeringAcceptorList{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(k8sObjects...). + WithStatusSubresource(&v1alpha1.PeeringAcceptor{}). + Build() // Create the peering acceptor controller. controller := &AcceptorController{ Client: fakeClient, @@ -1219,6 +1234,7 @@ func TestAcceptorUpdateStatusError(t *testing.T) { } err := fakeClient.Get(context.Background(), acceptorName, acceptor) require.NoError(t, err) + require.Len(t, acceptor.Status.Conditions, 1) require.Equal(t, tt.expStatus.Conditions[0].Message, acceptor.Status.Conditions[0].Message) }) @@ -1487,12 +1503,15 @@ func TestAcceptor_RequestsForPeeringTokens(t *testing.T) { 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() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(tt.secret, &tt.acceptors). + WithStatusSubresource(&v1alpha1.PeeringAcceptor{}). + Build() controller := AcceptorController{ Client: fakeClient, Log: logrtest.New(t), } - result := controller.requestsForPeeringTokens(tt.secret) + result := controller.requestsForPeeringTokens(context.Background(), tt.secret) require.Equal(t, tt.result, result) }) 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..69b70631d8 100644 --- a/control-plane/connect-inject/controllers/peering/peering_dialer_controller.go +++ b/control-plane/connect-inject/controllers/peering/peering_dialer_controller.go @@ -11,16 +11,13 @@ import ( "time" "github.com/go-logr/logr" - consulv1alpha1 "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/api" 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/utils/pointer" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -28,7 +25,10 @@ import ( "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" + + consulv1alpha1 "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" ) // PeeringDialerController reconciles a PeeringDialer object. @@ -235,7 +235,7 @@ func (r *PeeringDialerController) updateStatus(ctx context.Context, dialerObjKey return err } if dialer.Status.LatestPeeringVersion == nil || *dialer.Status.LatestPeeringVersion < peeringVersion { - dialer.Status.LatestPeeringVersion = pointer.Uint64(peeringVersion) + dialer.Status.LatestPeeringVersion = ptr.To(uint64(peeringVersion)) } } err := r.Status().Update(ctx, dialer) @@ -272,7 +272,7 @@ func (r *PeeringDialerController) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&consulv1alpha1.PeeringDialer{}). Watches( - &source.Kind{Type: &corev1.Secret{}}, + &corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.requestsForPeeringTokens), builder.WithPredicates(predicate.NewPredicateFuncs(r.filterPeeringDialers)), ).Complete(r) @@ -320,12 +320,12 @@ func (r *PeeringDialerController) versionAnnotationUpdated(dialer *consulv1alpha // the list of dialers and creates a request for the dialer that has the same secret as it's // secret and that of the updated secret that is being watched. // We compare it to the secret in the spec as the resource is dependent on the secret. -func (r *PeeringDialerController) requestsForPeeringTokens(object client.Object) []reconcile.Request { +func (r *PeeringDialerController) requestsForPeeringTokens(ctx context.Context, object client.Object) []reconcile.Request { r.Log.Info("received update for Peering Token Secret", "name", object.GetName(), "namespace", object.GetNamespace()) // Get the list of all dialers. var dialerList consulv1alpha1.PeeringDialerList - if err := r.Client.List(r.Context, &dialerList); err != nil { + if err := r.Client.List(ctx, &dialerList); err != nil { r.Log.Error(err, "failed to list PeeringDialers") return []ctrl.Request{} } 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..e759ca4e4c 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 @@ -20,7 +20,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -246,7 +246,7 @@ func TestReconcile_CreateUpdatePeeringDialer(t *testing.T) { Backend: "kubernetes", }, }, - LatestPeeringVersion: pointer.Uint64(2), + LatestPeeringVersion: ptr.To(uint64(2)), }, peeringExists: true, }, @@ -321,7 +321,10 @@ func TestReconcile_CreateUpdatePeeringDialer(t *testing.T) { s := runtime.NewScheme() corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(k8sObjects...). + WithStatusSubresource(&v1alpha1.PeeringDialer{}). + Build() // Create the peering dialer controller controller := &PeeringDialerController{ @@ -397,7 +400,7 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { Backend: "kubernetes", }, }, - LatestPeeringVersion: pointer.Uint64(3), + LatestPeeringVersion: ptr.To(uint64(3)), }, }, "is no/op if annotation value is equal to value in status": { @@ -412,7 +415,7 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { Backend: "kubernetes", }, }, - LatestPeeringVersion: pointer.Uint64(3), + LatestPeeringVersion: ptr.To(uint64(3)), }, }, "updates if annotation value is greater than value in status": { @@ -427,7 +430,7 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { Backend: "kubernetes", }, }, - LatestPeeringVersion: pointer.Uint64(4), + LatestPeeringVersion: ptr.To(uint64(4)), }, }, } @@ -479,7 +482,7 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { }, ResourceVersion: "latest-version", }, - LatestPeeringVersion: pointer.Uint64(3), + LatestPeeringVersion: ptr.To(uint64(3)), }, } // Create fake k8s client @@ -537,7 +540,10 @@ func TestReconcile_VersionAnnotationPeeringDialer(t *testing.T) { s := runtime.NewScheme() corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(k8sObjects...). + WithStatusSubresource(&v1alpha1.PeeringDialer{}). + Build() // Create the peering dialer controller controller := &PeeringDialerController{ @@ -754,7 +760,10 @@ func TestReconcileDeletePeeringDialer(t *testing.T) { s := runtime.NewScheme() corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(k8sObjects...). + WithStatusSubresource(&v1alpha1.PeeringDialer{}). + Build() // Create test consul server. testClient := test.TestServerWithMockConnMgrWatcher(t, nil) @@ -897,7 +906,10 @@ func TestDialerUpdateStatus(t *testing.T) { s := runtime.NewScheme() corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(k8sObjects...). + WithStatusSubresource(&v1alpha1.PeeringDialer{}). + Build() // Create the peering dialer controller. controller := &PeeringDialerController{ Client: fakeClient, @@ -1010,7 +1022,11 @@ func TestDialerUpdateStatusError(t *testing.T) { s := runtime.NewScheme() corev1.AddToScheme(s) s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.PeeringDialer{}, &v1alpha1.PeeringDialerList{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(k8sObjects...).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(k8sObjects...). + WithStatusSubresource(&v1alpha1.PeeringDialer{}). + Build() + // Create the peering dialer controller. controller := &PeeringDialerController{ Client: fakeClient, @@ -1027,6 +1043,7 @@ func TestDialerUpdateStatusError(t *testing.T) { } err := fakeClient.Get(context.Background(), dialerName, dialer) require.NoError(t, err) + require.Len(t, dialer.Status.Conditions, 1) require.Equal(t, tt.expStatus.Conditions[0].Message, dialer.Status.Conditions[0].Message) }) @@ -1295,12 +1312,15 @@ func TestDialer_RequestsForPeeringTokens(t *testing.T) { 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() + fakeClient := fake.NewClientBuilder().WithScheme(s). + WithRuntimeObjects(tt.secret, &tt.dialers). + WithStatusSubresource(&v1alpha1.PeeringDialer{}). + Build() controller := PeeringDialerController{ Client: fakeClient, Log: logrtest.New(t), } - result := controller.requestsForPeeringTokens(tt.secret) + result := controller.requestsForPeeringTokens(context.Background(), tt.secret) require.Equal(t, tt.result, result) }) 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 12e2c2124d..0000000000 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ /dev/null @@ -1,754 +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, - }, - } -} - -func getProxyConfigurationID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - }, - } -} - -func getHealthStatusID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbcatalog.HealthStatusType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - }, - } -} - -func getDestinationsID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbmesh.DestinationsType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - }, - } -} - -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 489010eeb8..0000000000 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ /dev/null @@ -1,2142 +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(""), - }, - 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", - }, - 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(""), - }, - 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", - }, - 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(""), - }, - 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", - }, - 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(""), - }, - 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 98e5c949c5..0000000000 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go +++ /dev/null @@ -1,193 +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, - }, - } -} - -// 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/lifecycle/lifecycle_configuration.go b/control-plane/connect-inject/lifecycle/lifecycle_configuration.go index 54000a2b01..651d4eecae 100644 --- a/control-plane/connect-inject/lifecycle/lifecycle_configuration.go +++ b/control-plane/connect-inject/lifecycle/lifecycle_configuration.go @@ -17,10 +17,8 @@ type Config struct { DefaultEnableProxyLifecycle bool DefaultEnableShutdownDrainListeners bool DefaultShutdownGracePeriodSeconds int - DefaultStartupGracePeriodSeconds int DefaultGracefulPort string DefaultGracefulShutdownPath string - DefaultGracefulStartupPath string } // EnableProxyLifecycle returns whether proxy lifecycle management is enabled either via the default value in the meshWebhook, or if it's been @@ -65,20 +63,6 @@ func (lc Config) ShutdownGracePeriodSeconds(pod corev1.Pod) (int, error) { return shutdownGracePeriodSeconds, nil } -// StartupGracePeriodSeconds returns how long to block application startup waiting for the sidecar proxy to be ready, either via the default value in the meshWebhook, or if it's been -// overridden via the annotation. -func (lc Config) StartupGracePeriodSeconds(pod corev1.Pod) (int, error) { - startupGracePeriodSeconds := lc.DefaultStartupGracePeriodSeconds - if startupGracePeriodSecondsAnnotation, ok := pod.Annotations[constants.AnnotationSidecarProxyLifecycleStartupGracePeriodSeconds]; ok { - val, err := strconv.ParseUint(startupGracePeriodSecondsAnnotation, 10, 64) - if err != nil { - return 0, fmt.Errorf("unable to parse annotation %q: %w", constants.AnnotationSidecarProxyLifecycleStartupGracePeriodSeconds, err) - } - startupGracePeriodSeconds = int(val) - } - return startupGracePeriodSeconds, 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) { @@ -109,17 +93,3 @@ func (lc Config) GracefulShutdownPath(pod corev1.Pod) string { return lc.DefaultGracefulShutdownPath } - -// GracefulStartupPath returns the path on which consul-dataplane should serve the graceful startup HTTP endpoint, either via the default value in the meshWebhook, or -// if it's been overridden via the annotation. -func (lc Config) GracefulStartupPath(pod corev1.Pod) string { - if raw, ok := pod.Annotations[constants.AnnotationSidecarProxyLifecycleGracefulStartupPath]; ok && raw != "" { - return raw - } - - if lc.DefaultGracefulStartupPath == "" { - return constants.DefaultGracefulStartupPath - } - - return lc.DefaultGracefulStartupPath -} diff --git a/control-plane/connect-inject/lifecycle/lifecycle_configuration_test.go b/control-plane/connect-inject/lifecycle/lifecycle_configuration_test.go index 4da4d47171..64157a3d55 100644 --- a/control-plane/connect-inject/lifecycle/lifecycle_configuration_test.go +++ b/control-plane/connect-inject/lifecycle/lifecycle_configuration_test.go @@ -200,72 +200,6 @@ func TestLifecycleConfig_ShutdownGracePeriodSeconds(t *testing.T) { } } -func TestLifecycleConfig_StartupGracePeriodSeconds(t *testing.T) { - cases := []struct { - Name string - Pod func(*corev1.Pod) *corev1.Pod - LifecycleConfig Config - Expected int - Err string - }{ - { - Name: "Sidecar proxy startup grace period set via meshWebhook", - Pod: func(pod *corev1.Pod) *corev1.Pod { - return pod - }, - LifecycleConfig: Config{ - DefaultStartupGracePeriodSeconds: 10, - }, - Expected: 10, - Err: "", - }, - { - Name: "Sidecar proxy startup grace period set via annotation", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationSidecarProxyLifecycleStartupGracePeriodSeconds] = "20" - return pod - }, - LifecycleConfig: Config{ - DefaultStartupGracePeriodSeconds: 10, - }, - Expected: 20, - Err: "", - }, - { - Name: "Sidecar proxy startup grace period configured via invalid annotation, negative number", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationSidecarProxyLifecycleStartupGracePeriodSeconds] = "-1" - return pod - }, - Err: "unable to parse annotation \"consul.hashicorp.com/sidecar-proxy-lifecycle-startup-grace-period-seconds\": strconv.ParseUint: parsing \"-1\": invalid syntax", - }, - { - Name: "Sidecar proxy startup grace period configured via invalid annotation, not-parseable string", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationSidecarProxyLifecycleStartupGracePeriodSeconds] = "not-int" - return pod - }, - Err: "unable to parse annotation \"consul.hashicorp.com/sidecar-proxy-lifecycle-startup-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.StartupGracePeriodSeconds(*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 @@ -393,59 +327,6 @@ func TestLifecycleConfig_GracefulShutdownPath(t *testing.T) { } } -func TestLifecycleConfig_GracefulStartupPath(t *testing.T) { - cases := []struct { - Name string - Pod func(*corev1.Pod) *corev1.Pod - LifecycleConfig Config - Expected string - Err string - }{ - { - Name: "Sidecar proxy lifecycle graceful startup path defaults to /graceful_startup", - Pod: func(pod *corev1.Pod) *corev1.Pod { - return pod - }, - Expected: "/graceful_startup", - Err: "", - }, - { - Name: "Sidecar proxy lifecycle graceful startup path set via meshWebhook", - Pod: func(pod *corev1.Pod) *corev1.Pod { - return pod - }, - LifecycleConfig: Config{ - DefaultGracefulStartupPath: "/start", - }, - Expected: "/start", - Err: "", - }, - { - Name: "Sidecar proxy lifecycle graceful startup path set via annotation", - Pod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationSidecarProxyLifecycleGracefulStartupPath] = "/custom-startup-path" - return pod - }, - LifecycleConfig: Config{ - DefaultGracefulStartupPath: "/start", - }, - Expected: "/custom-startup-path", - Err: "", - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - lc := tt.LifecycleConfig - - actual := lc.GracefulStartupPath(*tt.Pod(minimal())) - - require.Equal(tt.Expected, actual) - }) - } -} - func minimal() *corev1.Pod { return &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/connect-inject/metrics/metrics_configuration.go b/control-plane/connect-inject/metrics/metrics_configuration.go index 2f217f233d..6f9c29c85b 100644 --- a/control-plane/connect-inject/metrics/metrics_configuration.go +++ b/control-plane/connect-inject/metrics/metrics_configuration.go @@ -8,10 +8,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/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 5617dcc130..50f23dc472 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -10,13 +10,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" + "k8s.io/utils/ptr" ) const ( @@ -201,15 +200,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 @@ -220,58 +210,29 @@ func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod cor // 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 { - // In non-OpenShift environments we set the User and group ID for the sidecar to our values. - if !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), - AllowPrivilegeEscalation: pointer.Bool(false), - ReadOnlyRootFilesystem: pointer.Bool(true), - } - } else { - // Transparent proxy is set in OpenShift. There is an annotation on the namespace that tells us what - // the user and group ids should be for the sidecar. - uid, err := common.GetOpenShiftUID(&namespace) - if err != nil { - return corev1.Container{}, err - } - group, err := common.GetOpenShiftGroup(&namespace) - if err != nil { - return corev1.Container{}, err + 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) } - container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(uid), - RunAsGroup: pointer.Int64(group), - RunAsNonRoot: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - ReadOnlyRootFilesystem: pointer.Bool(true), + } + // 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: ptr.To(int64(sidecarUserAndGroupID)), + RunAsGroup: ptr.To(int64(sidecarUserAndGroupID)), + RunAsNonRoot: ptr.To(true), + AllowPrivilegeEscalation: ptr.To(false), + ReadOnlyRootFilesystem: ptr.To(true), + } } + return container, nil } @@ -334,7 +295,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") @@ -382,16 +343,10 @@ func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, mpi mu args = append(args, fmt.Sprintf("-shutdown-grace-period-seconds=%d", shutdownGracePeriodSeconds)) gracefulShutdownPath := w.LifecycleConfig.GracefulShutdownPath(pod) - args = append(args, fmt.Sprintf("-graceful-shutdown-path=%s", gracefulShutdownPath)) - - startupGracePeriodSeconds, err := w.LifecycleConfig.StartupGracePeriodSeconds(pod) if err != nil { - return nil, fmt.Errorf("unable to determine proxy lifecycle startup grace period: %w", err) + return nil, fmt.Errorf("unable to determine proxy lifecycle graceful shutdown path: %w", err) } - args = append(args, fmt.Sprintf("-startup-grace-period-seconds=%d", startupGracePeriodSeconds)) - - gracefulStartupPath := w.LifecycleConfig.GracefulStartupPath(pod) - args = append(args, fmt.Sprintf("-graceful-startup-path=%s", gracefulStartupPath)) + args = append(args, fmt.Sprintf("-graceful-shutdown-path=%s", gracefulShutdownPath)) } // Set a default scrape path that can be overwritten by the annotation. @@ -610,37 +565,3 @@ func (w *MeshWebhook) getLivenessFailureSeconds(pod corev1.Pod) int32 { } 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 1c1f3cdbf7..430b082a5c 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar_test.go @@ -9,17 +9,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" + "k8s.io/utils/ptr" ) const nodeName = "test-node" @@ -305,6 +303,7 @@ func TestHandlerConsulDataplaneSidecar_Concurrency(t *testing.T) { // 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 @@ -365,6 +364,7 @@ func TestHandlerConsulDataplaneSidecar_DNSProxy(t *testing.T) { 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}, @@ -802,22 +802,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), - AllowPrivilegeEscalation: pointer.Bool(false), + RunAsUser: ptr.To(int64(sidecarUserAndGroupID)), + RunAsGroup: ptr.To(int64(sidecarUserAndGroupID)), + RunAsNonRoot: ptr.To(true), + ReadOnlyRootFilesystem: ptr.To(true), + AllowPrivilegeEscalation: ptr.To(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), + RunAsUser: ptr.To(int64(sidecarUserAndGroupID)), + RunAsGroup: ptr.To(int64(sidecarUserAndGroupID)), + RunAsNonRoot: ptr.To(true), + ReadOnlyRootFilesystem: ptr.To(true), + AllowPrivilegeEscalation: ptr.To(false), }, }, "tproxy disabled; openshift enabled": { @@ -829,28 +829,15 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { tproxyEnabled: true, openShiftEnabled: true, expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(1000700000), - RunAsGroup: pointer.Int64(1000700000), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), + RunAsUser: ptr.To(int64(sidecarUserAndGroupID)), + RunAsGroup: ptr.To(int64(sidecarUserAndGroupID)), + RunAsNonRoot: ptr.To(true), + ReadOnlyRootFilesystem: ptr.To(true), + AllowPrivilegeEscalation: ptr.To(false), }, }, } for name, c := range cases { - ns := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: k8sNamespace, - Namespace: k8sNamespace, - Annotations: map[string]string{}, - Labels: map[string]string{}, - }, - } - - if c.openShiftEnabled { - ns.Annotations[constants.AnnotationOpenShiftUIDRange] = "1000700000/100000" - ns.Annotations[constants.AnnotationOpenShiftGroups] = "1000700000/100000" - } t.Run(name, func(t *testing.T) { w := MeshWebhook{ EnableTransparentProxy: c.tproxyEnabled, @@ -859,7 +846,6 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { } pod := corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Namespace: ns.Name, Annotations: map[string]string{ constants.AnnotationService: "foo", }, @@ -873,7 +859,7 @@ func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { }, }, } - ec, err := w.consulDataplaneSidecar(ns, pod, multiPortInfo{}) + ec, err := w.consulDataplaneSidecar(testNS, pod, multiPortInfo{}) require.NoError(t, err) require.Equal(t, c.expSecurityContext, ec.SecurityContext) }) @@ -895,15 +881,12 @@ func TestHandlerConsulDataplaneSidecar_FailsWithDuplicatePodSecurityContextUID(t }, }, SecurityContext: &corev1.PodSecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsUser: ptr.To(int64(sidecarUserAndGroupID)), }, }, } _, err := w.consulDataplaneSidecar(testNS, pod, multiPortInfo{}) - require.EqualError( - err, - fmt.Sprintf("pod's security context cannot have the same UID as consul-dataplane: %v", sidecarUserAndGroupID), - ) + 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 @@ -926,26 +909,23 @@ func TestHandlerConsulDataplaneSidecar_FailsWithDuplicateContainerSecurityContex Name: "web", // Setting RunAsUser: 1 should succeed. SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(1), + RunAsUser: ptr.To(int64(1)), }, }, { Name: "app", // Setting RunAsUser: 5995 should fail. SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsUser: ptr.To(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, - ), + 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", @@ -956,14 +936,14 @@ func TestHandlerConsulDataplaneSidecar_FailsWithDuplicateContainerSecurityContex Name: "web", // Setting RunAsUser: 1 should succeed. SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(1), + RunAsUser: ptr.To(int64(1)), }, }, { Name: "sidecar", // Setting RunAsUser: 5995 should succeed if the image matches h.ImageConsulDataplane. SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), + RunAsUser: ptr.To(int64(sidecarUserAndGroupID)), }, Image: "envoy", }, @@ -1309,7 +1289,6 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { name string pod corev1.Pod expCmdArgs string - expPorts []corev1.ContainerPort expErr string }{ { @@ -1332,37 +1311,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", @@ -1383,13 +1331,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", @@ -1408,11 +1349,7 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { }, }, expCmdArgs: "", - expErr: fmt.Sprintf( - "must set one of %q or %q when providing prometheus TLS config", - constants.AnnotationPrometheusCAFile, - constants.AnnotationPrometheusCAPath, - ), + 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", @@ -1458,12 +1395,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 != "" { @@ -1472,9 +1403,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) - } } }) } @@ -1482,10 +1410,8 @@ func TestHandlerConsulDataplaneSidecar_Metrics(t *testing.T) { func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { gracefulShutdownSeconds := 10 - gracefulStartupSeconds := 10 gracefulPort := "20307" gracefulShutdownPath := "/exit" - gracefulStartupPath := "/start" cases := []struct { name string @@ -1507,14 +1433,12 @@ func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { DefaultEnableProxyLifecycle: true, DefaultEnableShutdownDrainListeners: true, DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultStartupGracePeriodSeconds: gracefulStartupSeconds, DefaultGracefulPort: gracefulPort, DefaultGracefulShutdownPath: gracefulShutdownPath, - DefaultGracefulStartupPath: gracefulStartupPath, }, }, annotations: nil, - expCmdArgs: "graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit -startup-grace-period-seconds=10 -graceful-startup-path=/start", + expCmdArgs: "graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit", }, { name: "no defaults, all annotations", @@ -1523,12 +1447,10 @@ func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { constants.AnnotationEnableSidecarProxyLifecycle: "true", constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "true", constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds), - constants.AnnotationSidecarProxyLifecycleStartupGracePeriodSeconds: fmt.Sprint(gracefulStartupSeconds), constants.AnnotationSidecarProxyLifecycleGracefulPort: gracefulPort, constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: gracefulShutdownPath, - constants.AnnotationSidecarProxyLifecycleGracefulStartupPath: gracefulStartupPath, }, - expCmdArgs: "-graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit -startup-grace-period-seconds=10 -graceful-startup-path=/start", + expCmdArgs: "-graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit", }, { name: "annotations override defaults", @@ -1537,22 +1459,18 @@ func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { DefaultEnableProxyLifecycle: false, DefaultEnableShutdownDrainListeners: true, DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultStartupGracePeriodSeconds: gracefulStartupSeconds, DefaultGracefulPort: gracefulPort, DefaultGracefulShutdownPath: gracefulShutdownPath, - DefaultGracefulStartupPath: gracefulStartupPath, }, }, annotations: map[string]string{ constants.AnnotationEnableSidecarProxyLifecycle: "true", constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "false", constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds + 5), - constants.AnnotationSidecarProxyLifecycleStartupGracePeriodSeconds: fmt.Sprint(gracefulStartupSeconds + 5), constants.AnnotationSidecarProxyLifecycleGracefulPort: "20317", constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: "/foo", - constants.AnnotationSidecarProxyLifecycleGracefulStartupPath: "/bar", }, - expCmdArgs: "-graceful-port=20317 -shutdown-grace-period-seconds=15 -graceful-shutdown-path=/foo -startup-grace-period-seconds=15 -graceful-startup-path=/bar", + expCmdArgs: "-graceful-port=20317 -shutdown-grace-period-seconds=15 -graceful-shutdown-path=/foo", }, { name: "lifecycle disabled, no annotations", @@ -1561,10 +1479,8 @@ func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { DefaultEnableProxyLifecycle: false, DefaultEnableShutdownDrainListeners: true, DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultStartupGracePeriodSeconds: gracefulStartupSeconds, DefaultGracefulPort: gracefulPort, DefaultGracefulShutdownPath: gracefulShutdownPath, - DefaultGracefulStartupPath: gracefulStartupPath, }, }, annotations: nil, @@ -1587,10 +1503,8 @@ func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { DefaultEnableProxyLifecycle: true, DefaultEnableShutdownDrainListeners: true, DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultStartupGracePeriodSeconds: gracefulStartupSeconds, DefaultGracefulPort: gracefulPort, DefaultGracefulShutdownPath: gracefulShutdownPath, - DefaultGracefulStartupPath: gracefulStartupPath, }, }, annotations: map[string]string{ diff --git a/control-plane/connect-inject/webhook/container_init.go b/control-plane/connect-inject/webhook/container_init.go index 24c71461af..3586848023 100644 --- a/control-plane/connect-inject/webhook/container_init.go +++ b/control-plane/connect-inject/webhook/container_init.go @@ -13,7 +13,7 @@ import ( "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/utils/pointer" + "k8s.io/utils/ptr" ) const ( @@ -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: 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, }) } @@ -245,51 +245,26 @@ func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod, // 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), + RunAsUser: ptr.To(int64(rootUserAndGroupID)), + RunAsGroup: ptr.To(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), + RunAsNonRoot: ptr.To(false), + Privileged: ptr.To(privileged), Capabilities: &corev1.Capabilities{ Add: []corev1.Capability{netAdminCapability}, }, } } else { - if !w.EnableOpenShift { - 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), - } - } else { - // Transparent proxy + CNI is set in OpenShift. There is an annotation on the namespace that tells us what - // the user and group ids should be for the sidecar. - uid, err := common.GetOpenShiftUID(&namespace) - if err != nil { - return corev1.Container{}, err - } - group, err := common.GetOpenShiftGroup(&namespace) - if err != nil { - return corev1.Container{}, err - } - container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(uid), - RunAsGroup: pointer.Int64(group), - RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(false), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{"ALL"}, - }, - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - } - + container.SecurityContext = &corev1.SecurityContext{ + RunAsUser: ptr.To(int64(initContainersUserAndGroupID)), + RunAsGroup: ptr.To(int64(initContainersUserAndGroupID)), + RunAsNonRoot: ptr.To(true), + Privileged: ptr.To(privileged), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + ReadOnlyRootFilesystem: ptr.To(true), + AllowPrivilegeEscalation: ptr.To(false), } } } diff --git a/control-plane/connect-inject/webhook/container_init_test.go b/control-plane/connect-inject/webhook/container_init_test.go index 5896c0c0eb..dac78d8ad8 100644 --- a/control-plane/connect-inject/webhook/container_init_test.go +++ b/control-plane/connect-inject/webhook/container_init_test.go @@ -16,7 +16,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" ) const k8sNamespace = "k8snamespace" @@ -293,56 +293,30 @@ func TestHandlerContainerInit_transparentProxy(t *testing.T) { } var expectedSecurityContext *corev1.SecurityContext - if c.cniEnabled && !c.openShiftEnabled { + if c.cniEnabled { expectedSecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(initContainersUserAndGroupID), - RunAsGroup: pointer.Int64(initContainersUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(privileged), + RunAsUser: ptr.To(int64(initContainersUserAndGroupID)), + RunAsGroup: ptr.To(int64(initContainersUserAndGroupID)), + RunAsNonRoot: ptr.To(true), + Privileged: ptr.To(privileged), Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), + ReadOnlyRootFilesystem: ptr.To(true), + AllowPrivilegeEscalation: ptr.To(false), } } else if c.expTproxyEnabled { expectedSecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(0), - RunAsGroup: pointer.Int64(0), - RunAsNonRoot: pointer.Bool(false), - Privileged: pointer.Bool(privileged), + RunAsUser: ptr.To(int64(0)), + RunAsGroup: ptr.To(int64(0)), + RunAsNonRoot: ptr.To(false), + Privileged: ptr.To(privileged), Capabilities: &corev1.Capabilities{ Add: []corev1.Capability{netAdminCapability}, }, } - } else if c.cniEnabled && c.openShiftEnabled { - // When cni + openShift - expectedSecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(1000700000), - RunAsGroup: pointer.Int64(1000700000), - RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(privileged), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{"ALL"}, - }, - ReadOnlyRootFilesystem: pointer.Bool(true), - AllowPrivilegeEscalation: pointer.Bool(false), - } - } - ns := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: k8sNamespace, - Namespace: k8sNamespace, - Annotations: map[string]string{}, - Labels: map[string]string{}, - }, - } - - if c.openShiftEnabled { - ns.Annotations[constants.AnnotationOpenShiftUIDRange] = "1000700000/100000" - ns.Annotations[constants.AnnotationOpenShiftGroups] = "1000700000/100000" } - + ns := testNS ns.Labels = c.namespaceLabel container, err := w.containerInit(ns, *pod, multiPortInfo{}) require.NoError(t, err) @@ -811,8 +785,7 @@ func TestHandlerContainerInit_Multiport(t *testing.T) { serviceName: "web-admin", }, }, - []string{ - `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + []string{`/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ -log-level=info \ -log-json=false \ -multiport=true \ @@ -850,8 +823,7 @@ func TestHandlerContainerInit_Multiport(t *testing.T) { serviceName: "web-admin", }, }, - []string{ - `/bin/sh -ec consul-k8s-control-plane connect-init -pod-name=${POD_NAME} -pod-namespace=${POD_NAMESPACE} \ + []string{`/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" \ @@ -950,6 +922,7 @@ func TestHandlerContainerInit_WithTLSAndCustomPorts(t *testing.T) { } } } + }) } } diff --git a/control-plane/connect-inject/webhook/dns.go b/control-plane/connect-inject/webhook/dns.go index 3f73928ece..9f2bde1cdf 100644 --- a/control-plane/connect-inject/webhook/dns.go +++ b/control-plane/connect-inject/webhook/dns.go @@ -9,7 +9,7 @@ import ( "github.com/miekg/dns" corev1 "k8s.io/api/core/v1" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" ) const ( @@ -55,13 +55,13 @@ func (w *MeshWebhook) configureDNS(pod *corev1.Pod, k8sNS string) error { if cfg.Timeout != defaultDNSOptionTimeout { options = append(options, corev1.PodDNSConfigOption{ Name: "timeout", - Value: pointer.String(strconv.Itoa(cfg.Timeout)), + Value: ptr.To(strconv.Itoa(cfg.Timeout)), }) } if cfg.Attempts != defaultDNSOptionAttempts { options = append(options, corev1.PodDNSConfigOption{ Name: "attempts", - Value: pointer.String(strconv.Itoa(cfg.Attempts)), + Value: ptr.To(strconv.Itoa(cfg.Attempts)), }) } diff --git a/control-plane/connect-inject/webhook/dns_test.go b/control-plane/connect-inject/webhook/dns_test.go index e8d718557e..c5a8c976b9 100644 --- a/control-plane/connect-inject/webhook/dns_test.go +++ b/control-plane/connect-inject/webhook/dns_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" ) func TestMeshWebhook_configureDNS(t *testing.T) { @@ -40,15 +40,15 @@ options ndots:5 timeout:6 attempts:3`, Options: []corev1.PodDNSConfigOption{ { Name: "ndots", - Value: pointer.String("5"), + Value: ptr.To("5"), }, { Name: "timeout", - Value: pointer.String("6"), + Value: ptr.To("6"), }, { Name: "attempts", - Value: pointer.String("3"), + Value: ptr.To("3"), }, }, }, @@ -65,7 +65,7 @@ options ndots:5`, Options: []corev1.PodDNSConfigOption{ { Name: "ndots", - Value: pointer.String("5"), + Value: ptr.To("5"), }, }, }, diff --git a/control-plane/connect-inject/webhook/mesh_webhook.go b/control-plane/connect-inject/webhook/mesh_webhook.go index 7d7233baa6..a97880736c 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook.go +++ b/control-plane/connect-inject/webhook/mesh_webhook.go @@ -15,6 +15,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/version" "gomodules.xyz/jsonpatch/v2" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -22,15 +29,8 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" + ctrl "sigs.k8s.io/controller-runtime" "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 ( @@ -57,7 +57,7 @@ var kubeSystemNamespaces = mapset.NewSetWith(metav1.NamespaceSystem, metav1.Name type MeshWebhook struct { Clientset kubernetes.Interface - // ConsulClientConfig is the config to create a Consul API client. + // ConsulConfig is the config to create a Consul API client. ConsulConfig *consul.Config // ConsulServerConnMgr is the watcher for the Consul server addresses. @@ -298,10 +298,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 +315,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 +331,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 +386,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 +513,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 +595,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 @@ -727,9 +698,9 @@ func (w *MeshWebhook) checkUnsupportedMultiPortCases(ns corev1.Namespace, pod co return nil } -func (w *MeshWebhook) InjectDecoder(d *admission.Decoder) error { - w.decoder = d - return nil +func (w *MeshWebhook) SetupWithManager(mgr ctrl.Manager) { + w.decoder = admission.NewDecoder(mgr.GetScheme()) + mgr.GetWebhookServer().Register("/mutate", &admission.Webhook{Handler: w}) } func sliceContains(slice []string, entry string) bool { 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..fc1729c8d9 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_ent_test.go @@ -9,10 +9,8 @@ import ( "context" "testing" - "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testing" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" + mapset "github.com/deckarep/golang-set" + logrtest "github.com/go-logr/logr/testr" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" @@ -22,6 +20,9 @@ import ( "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 @@ -40,8 +41,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) { } s := runtime.NewScheme() s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) cases := []struct { Name string @@ -52,7 +52,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 +74,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 +96,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 +118,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 +140,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 +163,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 +186,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 +210,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, @@ -286,8 +286,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) cases := []struct { Name string @@ -298,7 +297,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 +320,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 +343,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 +366,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 +389,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 +413,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 +437,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 +462,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, @@ -518,7 +517,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) { Rules: crossNamespaceRules, } - _, _, err = client.ACL().PolicyCreate(&policyTmpl, &api.WriteOptions{}) + _, _, err := client.ACL().PolicyCreate(&policyTmpl, &api.WriteOptions{}) require.NoError(t, err) // Mutate! @@ -597,13 +596,10 @@ func TestHandler_MutateWithNamespaces_Annotation(t *testing.T) { 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) + decoder := admission.NewDecoder(s) 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..4796fa2f51 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_test.go @@ -6,13 +6,16 @@ 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/version" "github.com/stretchr/testify/require" "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" @@ -24,13 +27,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) { @@ -47,8 +43,7 @@ func TestHandlerHandle(t *testing.T) { Group: "", Version: "v1", }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) cases := []struct { Name string @@ -142,73 +137,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 +174,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 +267,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 +322,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 +396,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 +456,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 +541,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 +678,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 +741,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 +800,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 +870,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 +942,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 +975,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 +986,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 +1007,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 +1034,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 +1063,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 +1090,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 e6de09f448..b0cbefeeaa 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic.go +++ b/control-plane/connect-inject/webhook/redirect_traffic.go @@ -8,18 +8,17 @@ 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. // 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 or read from namespace when using OpenShift +// 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 @@ -27,18 +26,8 @@ import ( // ExcludeOutboundCIDRs: pod annotations // ExcludeUIDs: pod annotations func (w *MeshWebhook) iptablesConfigJSON(pod corev1.Pod, ns corev1.Namespace) (string, error) { - cfg := iptables.Config{} - - if !w.EnableOpenShift { - cfg.ProxyUserID = strconv.Itoa(sidecarUserAndGroupID) - } else { - // When using OpenShift, the uid and group are saved as an annotation on the namespace - uid, err := common.GetOpenShiftUID(&ns) - if err != nil { - return "", err - } - cfg.ProxyUserID = strconv.FormatInt(uid, 10) - + cfg := iptables.Config{ + ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), } // Set the proxy's inbound port. @@ -73,24 +62,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..374cc84199 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic_test.go +++ b/control-plane/connect-inject/webhook/redirect_traffic_test.go @@ -11,8 +11,6 @@ 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/consul" "github.com/hashicorp/consul/sdk/iptables" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -21,6 +19,9 @@ import ( "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 ( @@ -34,8 +35,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { Group: "", Version: "v1", }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) + decoder := admission.NewDecoder(s) cases := []struct { name string webhook MeshWebhook @@ -389,7 +389,7 @@ func TestAddRedirectTrafficConfig(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - err = c.webhook.addRedirectTrafficConfigAnnotation(c.pod, c.namespace) + err := c.webhook.addRedirectTrafficConfigAnnotation(c.pod, c.namespace) // Only compare annotation and iptables config on successful runs if c.expErr == nil { 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 d94dbeaaac..0000000000 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go +++ /dev/null @@ -1,529 +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) - args = append(args, fmt.Sprintf("-graceful-shutdown-path=%s", gracefulShutdownPath)) - - startupGracePeriodSeconds, err := w.LifecycleConfig.StartupGracePeriodSeconds(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine proxy lifecycle startup grace period: %w", err) - } - args = append(args, fmt.Sprintf("-startup-grace-period-seconds=%d", startupGracePeriodSeconds)) - - gracefulStartupPath := w.LifecycleConfig.GracefulStartupPath(pod) - args = append(args, fmt.Sprintf("-graceful-startup-path=%s", gracefulStartupPath)) - } - - // 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 3b5fb3c0c7..0000000000 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go +++ /dev/null @@ -1,1291 +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 - gracefulStartupSeconds := 10 - gracefulPort := "20307" - gracefulShutdownPath := "/exit" - gracefulStartupPath := "/start" - - 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, - DefaultStartupGracePeriodSeconds: gracefulStartupSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - DefaultGracefulStartupPath: gracefulStartupPath, - }, - }, - annotations: nil, - expCmdArgs: "graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit -startup-grace-period-seconds=10 -graceful-startup-path=/start", - }, - { - name: "no defaults, all annotations", - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationEnableSidecarProxyLifecycle: "true", - constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "true", - constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds), - constants.AnnotationSidecarProxyLifecycleStartupGracePeriodSeconds: fmt.Sprint(gracefulStartupSeconds), - constants.AnnotationSidecarProxyLifecycleGracefulPort: gracefulPort, - constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: gracefulShutdownPath, - constants.AnnotationSidecarProxyLifecycleGracefulStartupPath: gracefulStartupPath, - }, - expCmdArgs: "-graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit -startup-grace-period-seconds=10 -graceful-startup-path=/start", - }, - { - name: "annotations override defaults", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: false, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultStartupGracePeriodSeconds: gracefulStartupSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - DefaultGracefulStartupPath: gracefulStartupPath, - }, - }, - annotations: map[string]string{ - constants.AnnotationEnableSidecarProxyLifecycle: "true", - constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "false", - constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds + 5), - constants.AnnotationSidecarProxyLifecycleStartupGracePeriodSeconds: fmt.Sprint(gracefulStartupSeconds + 5), - constants.AnnotationSidecarProxyLifecycleGracefulPort: "20317", - constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: "/foo", - constants.AnnotationSidecarProxyLifecycleGracefulStartupPath: "/bar", - }, - expCmdArgs: "-graceful-port=20317 -shutdown-grace-period-seconds=15 -graceful-shutdown-path=/foo -startup-grace-period-seconds=15 -graceful-startup-path=/bar", - }, - { - name: "lifecycle disabled, no annotations", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: false, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultStartupGracePeriodSeconds: gracefulStartupSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - DefaultGracefulStartupPath: gracefulStartupPath, - }, - }, - 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, - DefaultStartupGracePeriodSeconds: gracefulStartupSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - DefaultGracefulStartupPath: gracefulStartupPath, - }, - }, - 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..830b204366 100644 --- a/control-plane/consul/consul.go +++ b/control-plane/consul/consul.go @@ -8,10 +8,9 @@ import ( "net/http" "time" + "github.com/hashicorp/consul-k8s/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 +20,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 +69,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 +80,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..5f721d59fd 100644 --- a/control-plane/consul/consul_test.go +++ b/control-plane/consul/consul_test.go @@ -9,7 +9,7 @@ import ( "net/http/httptest" "testing" - "github.com/hashicorp/consul-k8s/control-plane/version" + "github.com/hashicorp/consul-k8s/version" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" ) 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 233000cee8..0000000000 --- a/control-plane/consul/dataplane_client_test.go +++ /dev/null @@ -1,199 +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", - }, - } - - 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", - }, - } - - 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/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 f1d28b27da..0000000000 --- a/control-plane/consul/resource_client_test.go +++ /dev/null @@ -1,109 +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, - }, - }, - Data: proto, - }, - } - return req -} diff --git a/control-plane/controllers/configentries/configentry_controller.go b/control-plane/controller/configentry_controller.go similarity index 77% rename from control-plane/controllers/configentries/configentry_controller.go rename to control-plane/controller/configentry_controller.go index 9e9459308f..e41ad424b2 100644 --- a/control-plane/controllers/configentries/configentry_controller.go +++ b/control-plane/controller/configentry_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" @@ -11,6 +11,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,10 +25,6 @@ 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 ( @@ -36,8 +35,8 @@ const ( 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 @@ -183,7 +182,7 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont } // Check to see if consul has config entry with the same name - entry, _, err := consulClient.ConfigEntries().Get(configEntry.ConsulKind(), configEntry.ConsulName(), &capi.QueryOptions{ + entryFromConsul, _, err := consulClient.ConfigEntries().Get(configEntry.ConsulKind(), configEntry.ConsulName(), &capi.QueryOptions{ Namespace: r.consulNamespace(consulEntry, configEntry.ConsulMirroringNS(), configEntry.ConsulGlobalResource()), }) // If a config entry with this name does not exist @@ -212,7 +211,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) } @@ -223,37 +221,31 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont return r.syncFailed(ctx, logger, crdCtrl, configEntry, ConsulAgentError, err) } - requiresMigration := false - sourceDatacenter := entry.GetMeta()[common.DatacenterKey] - + sourceDatacenter := entryFromConsul.GetMeta()[common.DatacenterKey] + managedByThisDC := sourceDatacenter == r.DatacenterName // Check if the config entry is managed by our datacenter. // Do not process resource if the entry was not created within our datacenter // as it was created in a different cluster which will be managing that config entry. - if sourceDatacenter != r.DatacenterName { - - // Note that there is a special case where we will migrate a config entry - // that wasn't created by the controller if it has the migrate-entry annotation set to true. - // This functionality exists to help folks who are upgrading from older helm - // chart versions where they had previously created config entries themselves but - // now want to manage them through custom resources. - if configEntry.GetObjectMeta().Annotations[common.MigrateEntryKey] != common.MigrateEntryTrue { - return r.syncFailed(ctx, logger, crdCtrl, configEntry, ExternallyManagedConfigError, - sourceDatacenterMismatchErr(sourceDatacenter)) - } - - requiresMigration = true - } - - if !configEntry.MatchesConsul(entry) { - if requiresMigration { - // If we're migrating this config entry but the custom resource - // doesn't match what's in Consul currently we error out so that - // it doesn't overwrite something accidentally. - return r.syncFailed(ctx, logger, crdCtrl, configEntry, MigrationFailedError, - r.nonMatchingMigrationError(configEntry, entry)) - } - - logger.Info("config entry does not match consul", "modify-index", entry.GetModifyIndex()) + matchesConsul := configEntry.MatchesConsul(entryFromConsul) + // Note that there is a special case where we will migrate a config entry + // that wasn't created by the controller if it has the migrate-entry annotation set to true. + // This functionality exists to help folks who are upgrading from older helm + // chart versions where they had previously created config entries themselves but + // now want to manage them through custom resources. + hasMigrationKey := configEntry.GetObjectMeta().Annotations[common.MigrateEntryKey] == common.MigrateEntryTrue + + switch { + case !matchesConsul && !managedByThisDC && !hasMigrationKey: + return r.syncFailed(ctx, logger, crdCtrl, configEntry, ExternallyManagedConfigError, + sourceDatacenterMismatchErr(sourceDatacenter)) + case !matchesConsul && hasMigrationKey: + // If we're migrating this config entry but the custom resource + // doesn't match what's in Consul currently we error out so that + // it doesn't overwrite something accidentally. + return r.syncFailed(ctx, logger, crdCtrl, configEntry, MigrationFailedError, + r.nonMatchingMigrationError(configEntry, entryFromConsul)) + case !matchesConsul: + logger.Info("config entry does not match consul", "modify-index", entryFromConsul.GetModifyIndex()) _, writeMeta, err := consulClient.ConfigEntries().Set(consulEntry, &capi.WriteOptions{ Namespace: r.consulNamespace(consulEntry, configEntry.ConsulMirroringNS(), configEntry.ConsulGlobalResource()), }) @@ -263,7 +255,7 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont } logger.Info("config entry updated", "request-time", writeMeta.RequestTime) return r.syncSuccessful(ctx, crdCtrl, configEntry) - } else if requiresMigration && entry.GetMeta()[common.DatacenterKey] != r.DatacenterName { + case hasMigrationKey && !managedByThisDC: // If we get here then we're doing a migration and the entry in Consul // matches the entry in Kubernetes. We just need to update the metadata // of the entry in Consul to say that it's now managed by Kubernetes. @@ -277,19 +269,10 @@ func (r *ConfigEntryController) ReconcileEntry(ctx context.Context, crdCtrl Cont } logger.Info("config entry migrated", "request-time", writeMeta.RequestTime) return r.syncSuccessful(ctx, crdCtrl, configEntry) - } else if configEntry.SyncedConditionStatus() != corev1.ConditionTrue { + case configEntry.SyncedConditionStatus() != corev1.ConditionTrue: 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 } @@ -406,75 +389,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..72c9a2fccb --- /dev/null +++ b/control-plane/controller/configentry_controller_ent_test.go @@ -0,0 +1,772 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build enterprise + +package controller_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/go-logr/logr" + logrtest "github.com/go-logr/logr/testr" + 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/controller" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" +) + +// 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). + WithStatusSubresource(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). + WithStatusSubresource(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). + WithStatusSubresource(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 81% rename from control-plane/controllers/configentries/configentry_controller_test.go rename to control-plane/controller/configentry_controller_test.go index faa153c323..7cb9acddd0 100644 --- a/control-plane/controllers/configentries/configentry_controller_test.go +++ b/control-plane/controller/configentry_controller_test.go @@ -1,10 +1,11 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" + "errors" "fmt" "testing" "time" @@ -13,21 +14,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 +432,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 { @@ -548,7 +441,7 @@ func TestConfigEntryControllers_createsConfigEntry(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResource) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.configEntryResource).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(c.configEntryResource).WithStatusSubresource(c.configEntryResource).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) @@ -1016,56 +909,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 { @@ -1075,7 +918,7 @@ func TestConfigEntryControllers_updatesConfigEntry(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResource) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.configEntryResource).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(c.configEntryResource).WithStatusSubresource(c.configEntryResource).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) @@ -1467,37 +1310,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 { @@ -1506,7 +1318,7 @@ func TestConfigEntryControllers_deletesConfigEntry(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(v1alpha1.GroupVersion, c.configEntryResourceWithDeletion) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.configEntryResourceWithDeletion).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(c.configEntryResourceWithDeletion).WithStatusSubresource(c.configEntryResourceWithDeletion).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) @@ -1567,7 +1379,7 @@ func TestConfigEntryControllers_errorUpdatesSyncStatus(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(v1alpha1.GroupVersion, svcDefaults) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(svcDefaults).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(svcDefaults).WithStatusSubresource(svcDefaults).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) @@ -1640,7 +1452,7 @@ func TestConfigEntryControllers_setsSyncedToTrue(t *testing.T) { s.AddKnownTypes(v1alpha1.GroupVersion, svcDefaults) // The config entry exists in kube but its status will be nil. - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(svcDefaults).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(svcDefaults).WithStatusSubresource(svcDefaults).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) @@ -1683,16 +1495,37 @@ func TestConfigEntryControllers_doesNotCreateUnownedConfigEntry(t *testing.T) { kubeNS := "default" cases := []struct { - datacenterAnnotation string - expErr string + name string + datacenterAnnotation string + expErr error + expReason string + makeDifferentFromConsul bool }{ { - datacenterAnnotation: "", - expErr: "config entry already exists in Consul", + name: "when dc annotation is blank and the config entry does not match consul, then error is thrown, entry is not synced and reason is it is externally managed.", + datacenterAnnotation: "", + makeDifferentFromConsul: true, + expErr: errors.New("config entry already exists in Consul"), + expReason: "ExternallyManagedConfigError", + }, + { + name: "when dc annotation is not blank and the config entry matches consul, then error is not thrown and it is marked as synced", + datacenterAnnotation: "", + makeDifferentFromConsul: false, + expErr: nil, }, { - datacenterAnnotation: "other-datacenter", - expErr: "config entry managed in different datacenter: \"other-datacenter\"", + name: "when dc annotation is not blank and the config entry does not match consul, then error is thrown, entry is not synced and reason is it is externally managed.", + datacenterAnnotation: "other-datacenter", + makeDifferentFromConsul: true, + expErr: errors.New("config entry managed in different datacenter: \"other-datacenter\""), + expReason: "ExternallyManagedConfigError", + }, + { + name: "when dc annotation is not blank and the config entry matches consul, then error is not thrown and it is marked as synced", + datacenterAnnotation: "other-datacenter", + makeDifferentFromConsul: false, + expErr: nil, }, } @@ -1712,7 +1545,12 @@ func TestConfigEntryControllers_doesNotCreateUnownedConfigEntry(t *testing.T) { }, } s.AddKnownTypes(v1alpha1.GroupVersion, svcDefaults) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(svcDefaults).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(svcDefaults).WithStatusSubresource(svcDefaults).Build() + + // Change the config entry so protocol is https instead of http if test case says to + if c.makeDifferentFromConsul { + svcDefaults.Spec.Protocol = "https" + } testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) @@ -1749,7 +1587,7 @@ func TestConfigEntryControllers_doesNotCreateUnownedConfigEntry(t *testing.T) { resp, err := reconciler.Reconcile(ctx, ctrl.Request{ NamespacedName: namespacedName, }) - req.EqualError(err, c.expErr) + req.Equal(err, c.expErr) req.False(resp.Requeue) // Now check that the object in Consul is as expected. @@ -1761,9 +1599,17 @@ func TestConfigEntryControllers_doesNotCreateUnownedConfigEntry(t *testing.T) { err = fakeClient.Get(ctx, namespacedName, svcDefaults) req.NoError(err) status, reason, errMsg := svcDefaults.SyncedCondition() - req.Equal(corev1.ConditionFalse, status) - req.Equal("ExternallyManagedConfigError", reason) - req.Equal(errMsg, c.expErr) + expectedStatus := corev1.ConditionFalse + if !c.makeDifferentFromConsul { + expectedStatus = corev1.ConditionTrue + } + req.Equal(expectedStatus, status) + if !c.makeDifferentFromConsul { + req.Equal(c.expReason, reason) + } + if c.expErr != nil { + req.Equal(errMsg, c.expErr.Error()) + } } }) } @@ -1796,7 +1642,7 @@ func TestConfigEntryControllers_doesNotDeleteUnownedConfig(t *testing.T) { }, } s.AddKnownTypes(v1alpha1.GroupVersion, svcDefaultsWithDeletion) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(svcDefaultsWithDeletion).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(svcDefaultsWithDeletion).WithStatusSubresource(svcDefaultsWithDeletion).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) @@ -1878,7 +1724,7 @@ func TestConfigEntryControllers_updatesStatusWhenDeleteFails(t *testing.T) { }, } - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(defaults, splitter).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(defaults, splitter).WithStatusSubresource(defaults, splitter).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) @@ -1927,8 +1773,7 @@ func TestConfigEntryControllers_updatesStatusWhenDeleteFails(t *testing.T) { require.NoError(t, err) // Update service-defaults with deletion timestamp so that it attempts deletion on reconcile. - defaults.ObjectMeta.DeletionTimestamp = &metav1.Time{Time: time.Now()} - err = fakeClient.Update(ctx, defaults) + err = fakeClient.Delete(ctx, defaults) require.NoError(t, err) // Reconcile should fail as the service-splitter still required the service-defaults causing the delete operation on Consul to fail. @@ -2010,7 +1855,7 @@ func TestConfigEntryController_Migration(t *testing.T) { s := runtime.NewScheme() s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.ServiceDefaults{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(&c.KubeResource).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(&c.KubeResource).WithStatusSubresource(&c.KubeResource).Build() testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) consulClient := testClient.APIClient @@ -2080,235 +1925,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 98% rename from control-plane/controllers/configentries/exportedservices_controller.go rename to control-plane/controller/exportedservices_controller.go index 474431832a..254b83a1bf 100644 --- a/control-plane/controllers/configentries/exportedservices_controller.go +++ b/control-plane/controller/exportedservices_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" diff --git a/control-plane/controllers/configentries/exportedservices_controller_ent_test.go b/control-plane/controller/exportedservices_controller_ent_test.go similarity index 92% rename from control-plane/controllers/configentries/exportedservices_controller_ent_test.go rename to control-plane/controller/exportedservices_controller_ent_test.go index 61ec75288f..900f8fca2f 100644 --- a/control-plane/controllers/configentries/exportedservices_controller_ent_test.go +++ b/control-plane/controller/exportedservices_controller_ent_test.go @@ -3,7 +3,7 @@ //go:build enterprise -package configentries_test +package controller_test import ( "context" @@ -11,7 +11,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 +24,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 @@ -102,13 +101,13 @@ func TestExportedServicesController_createsExportedServices(tt *testing.T) { testClient.TestServer.WaitForServiceIntentions(t) consulClient := testClient.APIClient - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).WithStatusSubresource(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 +195,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{ @@ -217,13 +216,13 @@ func TestExportedServicesController_updatesExportedServices(tt *testing.T) { testClient := test.TestServerWithMockConnMgrWatcher(t, nil) testClient.TestServer.WaitForServiceIntentions(t) consulClient := testClient.APIClient - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).WithStatusSubresource(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 +332,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{ @@ -355,13 +354,13 @@ func TestExportedServicesController_deletesExportedServices(tt *testing.T) { testClient.TestServer.WaitForServiceIntentions(t) consulClient := testClient.APIClient - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).Build() + fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(exportedServices).WithStatusSubresource(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/finalizer_patch.go b/control-plane/controller/finalizer_patch.go similarity index 95% rename from control-plane/controllers/configentries/finalizer_patch.go rename to control-plane/controller/finalizer_patch.go index a261220c8f..5ccf8527db 100644 --- a/control-plane/controllers/configentries/finalizer_patch.go +++ b/control-plane/controller/finalizer_patch.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller 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 configentries to be touching the finalizers of consul-k8s managed CRDs. +// other controllers to be touching the finalizers of consul-k8s managed CRDs. func (fp *FinalizerPatch) Type() types.PatchType { return types.MergePatchType } diff --git a/control-plane/controllers/configentries/finalizer_patch_test.go b/control-plane/controller/finalizer_patch_test.go similarity index 99% rename from control-plane/controllers/configentries/finalizer_patch_test.go rename to control-plane/controller/finalizer_patch_test.go index 70dc782d0e..6363077bb7 100644 --- a/control-plane/controllers/configentries/finalizer_patch_test.go +++ b/control-plane/controller/finalizer_patch_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "testing" diff --git a/control-plane/controllers/configentries/ingressgateway_controller.go b/control-plane/controller/ingressgateway_controller.go similarity index 98% rename from control-plane/controllers/configentries/ingressgateway_controller.go rename to control-plane/controller/ingressgateway_controller.go index d1cced515e..8f20293c3a 100644 --- a/control-plane/controllers/configentries/ingressgateway_controller.go +++ b/control-plane/controller/ingressgateway_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" diff --git a/control-plane/controllers/configentries/mesh_controller.go b/control-plane/controller/mesh_controller.go similarity index 98% rename from control-plane/controllers/configentries/mesh_controller.go rename to control-plane/controller/mesh_controller.go index 7593e287ad..4b0e19214c 100644 --- a/control-plane/controllers/configentries/mesh_controller.go +++ b/control-plane/controller/mesh_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" diff --git a/control-plane/controllers/configentries/proxydefaults_controller.go b/control-plane/controller/proxydefaults_controller.go similarity index 98% rename from control-plane/controllers/configentries/proxydefaults_controller.go rename to control-plane/controller/proxydefaults_controller.go index 0edea71136..a3a5af303c 100644 --- a/control-plane/controllers/configentries/proxydefaults_controller.go +++ b/control-plane/controller/proxydefaults_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" diff --git a/control-plane/controllers/configentries/servicedefaults_controller.go b/control-plane/controller/servicedefaults_controller.go similarity index 98% rename from control-plane/controllers/configentries/servicedefaults_controller.go rename to control-plane/controller/servicedefaults_controller.go index 00c5235889..580e413b92 100644 --- a/control-plane/controllers/configentries/servicedefaults_controller.go +++ b/control-plane/controller/servicedefaults_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" diff --git a/control-plane/controllers/configentries/serviceintentions_controller.go b/control-plane/controller/serviceintentions_controller.go similarity index 98% rename from control-plane/controllers/configentries/serviceintentions_controller.go rename to control-plane/controller/serviceintentions_controller.go index 95706fc960..9bff764897 100644 --- a/control-plane/controllers/configentries/serviceintentions_controller.go +++ b/control-plane/controller/serviceintentions_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" diff --git a/control-plane/controllers/configentries/serviceresolver_controller.go b/control-plane/controller/serviceresolver_controller.go similarity index 98% rename from control-plane/controllers/configentries/serviceresolver_controller.go rename to control-plane/controller/serviceresolver_controller.go index 7e2352a287..5dea11c95c 100644 --- a/control-plane/controllers/configentries/serviceresolver_controller.go +++ b/control-plane/controller/serviceresolver_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" diff --git a/control-plane/controllers/configentries/servicerouter_controller.go b/control-plane/controller/servicerouter_controller.go similarity index 98% rename from control-plane/controllers/configentries/servicerouter_controller.go rename to control-plane/controller/servicerouter_controller.go index 7f16addbf2..4eb8050e04 100644 --- a/control-plane/controllers/configentries/servicerouter_controller.go +++ b/control-plane/controller/servicerouter_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" diff --git a/control-plane/controllers/configentries/servicesplitter_controller.go b/control-plane/controller/servicesplitter_controller.go similarity index 98% rename from control-plane/controllers/configentries/servicesplitter_controller.go rename to control-plane/controller/servicesplitter_controller.go index 274020a8d8..54c7cf39ae 100644 --- a/control-plane/controllers/configentries/servicesplitter_controller.go +++ b/control-plane/controller/servicesplitter_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" diff --git a/control-plane/controllers/configentries/terminatinggateway_controller.go b/control-plane/controller/terminatinggateway_controller.go similarity index 98% rename from control-plane/controllers/configentries/terminatinggateway_controller.go rename to control-plane/controller/terminatinggateway_controller.go index f8e4a0bc0b..d8fc1c80fd 100644 --- a/control-plane/controllers/configentries/terminatinggateway_controller.go +++ b/control-plane/controller/terminatinggateway_controller.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configentries +package controller import ( "context" 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/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 87333beb6f..0000000000 --- a/control-plane/controllers/resources/api-gateway-controller.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - "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" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/gateways" -) - -// APIGatewayController reconciles a APIGateway object. -type APIGatewayController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Controller *ConsulResourceController - GatewayConfig gateways.GatewayConfig -} - -// +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) { - logger := r.Logger(req.NamespacedName) - logger.Info("Reconciling APIGateway") - - resource := &meshv2beta1.APIGateway{} - 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 := onDelete(ctx, req, r.Client, resource); err != nil { - return ctrl.Result{}, err - } - } else { - // Fetch GatewayClassConfig for the gateway - if resource.Namespace == "" { - resource.Namespace = "default" - } - - gcc, err := getGatewayClassConfigByGatewayClassName(ctx, r.Client, resource.Spec.GatewayClassName) - if err != nil { - r.Log.Error(err, "unable to get gatewayclassconfig for gateway: %s gatewayclass: %s", resource.Name, resource.Spec.GatewayClassName) - return ctrl.Result{}, err - } - - if err := onCreateUpdate(ctx, r.Client, gatewayConfigs{ - gcc: gcc, - gatewayConfig: r.GatewayConfig, - }, resource, gateways.APIGatewayAnnotationKind); err != nil { - logger.Error(err, "unable to create/update gateway") - return ctrl.Result{}, err - } - } - - 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 setupGatewayControllerWithManager[*meshv2beta1.APIGatewayList](mgr, &meshv2beta1.APIGateway{}, r.Client, r, APIGateway_GatewayClassIndex) -} diff --git a/control-plane/controllers/resources/api-gateway-controller_test.go b/control-plane/controllers/resources/api-gateway-controller_test.go deleted file mode 100644 index 6b907647ec..0000000000 --- a/control-plane/controllers/resources/api-gateway-controller_test.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "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/helper/test" - 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/protobuf/testing/protocmp" - - logrtest "github.com/go-logr/logr/testr" - 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/runtime/schema" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -func TestAPIGatewayController_ReconcileResourceExists(t *testing.T) { - t.Parallel() - ctx := context.Background() - - 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( - schema.GroupVersion{ - Group: "mesh.consul.hashicorp.com", - Version: pbmesh.Version, - }, - &v2beta1.APIGateway{}, - &v2beta1.GatewayClass{}, - &v2beta1.GatewayClassConfig{}, - ) - - apiGW := &v2beta1.APIGateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "api-gateway", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbmesh.APIGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.APIGatewayListener{ - { - Name: "http-listener", - Port: 9090, - Protocol: "http", - }, - }, - }, - } - - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(apiGW).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - gwCtrl := APIGatewayController{ - Client: fakeClient, - Log: logrtest.New(t), - Scheme: s, - Controller: &ConsulResourceController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - }, - } - - // ensure the resource is not in consul yet - { - req := &pbresource.ReadRequest{Id: apiGW.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - _, err := testClient.ResourceClient.Read(ctx, req) - require.Error(t, err) - } - - // now reconcile the resource - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: apiGW.KubernetesName(), - } - - // First get it, so we have the latest revision number. - err := fakeClient.Get(ctx, namespacedName, apiGW) - require.NoError(t, err) - - resp, err := gwCtrl.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. - { - expectedResource := &pbmesh.APIGateway{ - GatewayClassName: "consul", - Listeners: []*pbmesh.APIGatewayListener{ - { - Name: "http-listener", - Port: 9090, - Protocol: "http", - }, - }, - } - req := &pbresource.ReadRequest{Id: apiGW.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - res, err := testClient.ResourceClient.Read(ctx, req) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, apiGW.GetName(), res.GetResource().GetId().GetName()) - - data := res.GetResource().Data - actual := &pbmesh.APIGateway{} - require.NoError(t, data.UnmarshalTo(actual)) - - opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expectedResource, actual, opts...) - require.Equal(t, "", diff, "APIGateway does not match") - } -} - -func TestAPIGatewayController_ReconcileAPIGWDoesNotExistInK8s(t *testing.T) { - t.Parallel() - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{ - Group: "mesh.consul.hashicorp.com", - Version: pbmesh.Version, - }, &v2beta1.APIGateway{}, &v2beta1.APIGatewayList{}) - - fakeClient := fake.NewClientBuilder().WithScheme(s).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - gwCtrl := APIGatewayController{ - Client: fakeClient, - Log: logrtest.New(t), - Scheme: s, - Controller: &ConsulResourceController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - }, - } - - // now reconcile the resource - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: "api-gateway", - } - - resp, err := gwCtrl.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - - require.NoError(t, err) - require.False(t, resp.Requeue) - require.Equal(t, ctrl.Result{}, resp) - } - - // ensure the resource is not in consul - { - req := &pbresource.ReadRequest{Id: &pbresource.ID{ - Name: "api-gateway", - Type: pbmesh.APIGatewayType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }} - - _, err := testClient.ResourceClient.Read(ctx, req) - require.Error(t, err) - } -} 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 9690942194..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" - - multiclusterv2 "github.com/hashicorp/consul-k8s/control-plane/api/multicluster/v2" - "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, &multiclusterv2.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, &multiclusterv2.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 22084bbc56..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(_ context.Context, _ 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 5f2bc91ebe..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(_ context.Context, _ 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/gateway_controller_crud.go b/control-plane/controllers/resources/gateway_controller_crud.go deleted file mode 100644 index a2eae79811..0000000000 --- a/control-plane/controllers/resources/gateway_controller_crud.go +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - "fmt" - - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/gateways" - 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" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "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" -) - -type gatewayConfigs struct { - gcc *meshv2beta1.GatewayClassConfig - gatewayConfig gateways.GatewayConfig -} - -// onCreateUpdate is responsible for creating/updating all K8s resources that -// are required in order to run a meshv2beta1.XGateway. These are created/updated -// in dependency order. -// 1. ServiceAccount -// 2. Deployment -// 3. Service -// 4. Role -// 5. RoleBinding -func onCreateUpdate[T gateways.Gateway](ctx context.Context, k8sClient client.Client, cfg gatewayConfigs, resource T, gatewayKind string) error { - builder := gateways.NewGatewayBuilder[T](resource, cfg.gatewayConfig, cfg.gcc, gatewayKind) - - // Create ServiceAccount - desiredAccount := builder.ServiceAccount() - existingAccount := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: desiredAccount.Namespace, Name: desiredAccount.Name}} - - upsertOp := func(ctx context.Context, _, object client.Object) error { - _, err := controllerutil.CreateOrUpdate(ctx, k8sClient, object, func() error { return nil }) - return err - } - - err := opIfNewOrOwned(ctx, resource, k8sClient, existingAccount, desiredAccount, upsertOp) - if err != nil { - return fmt.Errorf("unable to create service account: %w", err) - } - - // Create Role - desiredRole := builder.Role() - existingRole := &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Namespace: desiredRole.Namespace, Name: desiredRole.Name}} - - err = opIfNewOrOwned(ctx, resource, k8sClient, existingRole, desiredRole, upsertOp) - if err != nil { - return fmt.Errorf("unable to create role: %w", err) - } - - // Create RoleBinding - desiredBinding := builder.RoleBinding() - existingBinding := &rbacv1.RoleBinding{ObjectMeta: metav1.ObjectMeta{Namespace: desiredBinding.Namespace, Name: desiredBinding.Name}} - - err = opIfNewOrOwned(ctx, resource, k8sClient, existingBinding, desiredBinding, upsertOp) - if err != nil { - return fmt.Errorf("unable to create role binding: %w", err) - } - - // Create Service - desiredService := builder.Service() - existingService := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: desiredService.Namespace, Name: desiredService.Name}} - - mergeServiceOp := func(ctx context.Context, existingObj, desiredObj client.Object) error { - existing := existingObj.(*corev1.Service) - desired := desiredObj.(*corev1.Service) - - _, err := controllerutil.CreateOrUpdate(ctx, k8sClient, existing, func() error { - gateways.MergeService(existing, desired) - return nil - }) - return err - } - - err = opIfNewOrOwned(ctx, resource, k8sClient, existingService, desiredService, mergeServiceOp) - if err != nil { - return fmt.Errorf("unable to create service: %w", err) - } - - // Create Deployment - desiredDeployment, err := builder.Deployment() - if err != nil { - return fmt.Errorf("unable to create deployment: %w", err) - } - existingDeployment := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: desiredDeployment.Namespace, Name: desiredDeployment.Name}} - - mergeDeploymentOp := func(ctx context.Context, existingObj, desiredObj client.Object) error { - existing := existingObj.(*appsv1.Deployment) - desired := desiredObj.(*appsv1.Deployment) - - _, err = controllerutil.CreateOrUpdate(ctx, k8sClient, existing, func() error { - gateways.MergeDeployment(existing, desired) - return nil - }) - return err - } - - err = opIfNewOrOwned(ctx, resource, k8sClient, existingDeployment, desiredDeployment, 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.XGateway is deleted. -func onDelete[T gateways.Gateway](ctx context.Context, req ctrl.Request, k8sClient client.Client, resource T) 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 XGateway 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, existing, desired 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 opIfNewOrOwned(ctx context.Context, gateway client.Object, k8sClient client.Client, existing, desired client.Object, op ownedObjectOp) error { - // Ensure owner reference is always set on objects that we write - if err := ctrl.SetControllerReference(gateway, desired, k8sClient.Scheme()); err != nil { - return err - } - - key := client.ObjectKey{ - Namespace: existing.GetNamespace(), - Name: existing.GetName(), - } - - exists := false - if err := k8sClient.Get(ctx, key, existing); 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, existing, desired) - } - - // Ensure the existing object was put there by us so that we don't overwrite random objects - owned := false - for _, reference := range existing.GetOwnerReferences() { - if reference.UID == gateway.GetUID() && reference.Name == gateway.GetName() { - owned = true - break - } - } - if !owned { - return errResourceNotOwned - } - return op(ctx, existing, desired) -} - -func getGatewayClassConfigByGatewayClassName(ctx context.Context, k8sClient client.Client, className string) (*meshv2beta1.GatewayClassConfig, error) { - gatewayClass, err := getGatewayClassByName(ctx, k8sClient, className) - if err != nil { - return nil, err - } - - if gatewayClass == nil { - return nil, nil - } - - gatewayClassConfig := &meshv2beta1.GatewayClassConfig{} - if ref := gatewayClass.Spec.ParametersRef; ref != nil { - if ref.Group != meshv2beta1.MeshGroup || ref.Kind != v2beta1.KindGatewayClassConfig { - // TODO @Gateway-Management additionally check for controller name when available - return nil, nil - } - - if err := k8sClient.Get(ctx, types.NamespacedName{Name: ref.Name}, gatewayClassConfig); err != nil { - return nil, client.IgnoreNotFound(err) - } - } - return gatewayClassConfig, nil -} - -func getGatewayClassByName(ctx context.Context, k8sClient client.Client, className string) (*meshv2beta1.GatewayClass, error) { - var gatewayClass meshv2beta1.GatewayClass - - if err := k8sClient.Get(ctx, types.NamespacedName{Name: className}, &gatewayClass); err != nil { - return nil, client.IgnoreNotFound(err) - } - return &gatewayClass, nil -} diff --git a/control-plane/controllers/resources/gateway_controller_setup.go b/control-plane/controllers/resources/gateway_controller_setup.go deleted file mode 100644 index 43f120a23c..0000000000 --- a/control-plane/controllers/resources/gateway_controller_setup.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/fields" - 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" -) - -type gatewayList interface { - *meshv2beta1.MeshGatewayList | *meshv2beta1.APIGatewayList - client.ObjectList - ReconcileRequests() []reconcile.Request -} - -func setupGatewayControllerWithManager[L gatewayList](mgr ctrl.Manager, obj client.Object, k8sClient client.Client, gwc reconcile.Reconciler, index indexName) error { - return ctrl.NewControllerManagedBy(mgr). - For(obj). - Owns(&appsv1.Deployment{}). - Owns(&rbacv1.Role{}). - Owns(&rbacv1.RoleBinding{}). - Owns(&corev1.Service{}). - Owns(&corev1.ServiceAccount{}). - Watches( - source.NewKindWithCache(&meshv2beta1.GatewayClass{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { - gc := o.(*meshv2beta1.GatewayClass) - if gc == nil { - return nil - } - - gateways, err := getGatewaysReferencingGatewayClass[L](context.Background(), k8sClient, gc.Name, index) - if err != nil { - return nil - } - - return gateways.ReconcileRequests() - })). - Watches( - source.NewKindWithCache(&meshv2beta1.GatewayClassConfig{}, mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { - gcc := o.(*meshv2beta1.GatewayClassConfig) - if gcc == nil { - return nil - } - - classes, err := getGatewayClassesReferencingGatewayClassConfig(context.Background(), k8sClient, gcc.Name) - if err != nil { - return nil - } - - var requests []reconcile.Request - for _, class := range classes.Items { - if class == nil { - continue - } - - gateways, err := getGatewaysReferencingGatewayClass[L](context.Background(), k8sClient, class.Name, index) - if err != nil { - continue - } - - requests = append(requests, gateways.ReconcileRequests()...) - } - - return requests - })). - Complete(gwc) -} - -// getGatewayClassesReferencingGatewayClassConfig queries all GatewayClass resources in the -// cluster and returns any that reference the given GatewayClassConfig by name. -func getGatewayClassesReferencingGatewayClassConfig(ctx context.Context, k8sClient client.Client, configName string) (*meshv2beta1.GatewayClassList, error) { - allClasses := &meshv2beta1.GatewayClassList{} - if err := k8sClient.List(ctx, allClasses, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(string(GatewayClass_GatewayClassConfigIndex), configName), - }); err != nil { - return nil, client.IgnoreNotFound(err) - } - - return allClasses, nil -} - -// getGatewaysReferencingGatewayClass queries all xGateway resources in the cluster -// and returns any that reference the given GatewayClass by name. -func getGatewaysReferencingGatewayClass[T gatewayList](ctx context.Context, k8sClient client.Client, className string, index indexName) (T, error) { - var allGateways T - if err := k8sClient.List(ctx, allGateways, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(string(index), className), - }); err != nil { - return nil, client.IgnoreNotFound(err) - } - - return allGateways, nil -} diff --git a/control-plane/controllers/resources/gateway_indices.go b/control-plane/controllers/resources/gateway_indices.go deleted file mode 100644 index 29e221d191..0000000000 --- a/control-plane/controllers/resources/gateway_indices.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -import ( - "context" - - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -type indexName string - -const ( - // Naming convention: TARGET_REFERENCE. - GatewayClass_GatewayClassConfigIndex indexName = "__v2_gatewayclass_referencing_gatewayclassconfig" - - APIGateway_GatewayClassIndex indexName = "__v2_api_gateway_referencing_gatewayclass" - MeshGateway_GatewayClassIndex indexName = "__v2_mesh_gateway_referencing_gatewayclass" -) - -// RegisterGatewayFieldIndexes registers all of the field indexes for the xGateway 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 RegisterGatewayFieldIndexes(ctx context.Context, mgr ctrl.Manager) error { - for _, index := range indexes { - if err := mgr.GetFieldIndexer().IndexField(ctx, index.target, string(index.name), index.indexerFunc); err != nil { - return err - } - } - return nil -} - -type index struct { - name indexName - target client.Object - indexerFunc client.IndexerFunc -} - -var indexes = []index{ - { - name: GatewayClass_GatewayClassConfigIndex, - target: &meshv2beta1.GatewayClass{}, - indexerFunc: func(o client.Object) []string { - gc := o.(*meshv2beta1.GatewayClass) - - pr := gc.Spec.ParametersRef - if pr != nil && pr.Kind == v2beta1.KindGatewayClassConfig { - return []string{pr.Name} - } - - return []string{} - }, - }, - { - name: APIGateway_GatewayClassIndex, - target: &meshv2beta1.APIGateway{}, - indexerFunc: func(o client.Object) []string { - g := o.(*meshv2beta1.APIGateway) - return []string{string(g.Spec.GatewayClassName)} - }, - }, - { - name: MeshGateway_GatewayClassIndex, - target: &meshv2beta1.MeshGateway{}, - indexerFunc: func(o client.Object) []string { - g := o.(*meshv2beta1.MeshGateway) - return []string{string(g.Spec.GatewayClassName)} - }, - }, -} 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 71bd4e3d46..0000000000 --- a/control-plane/controllers/resources/mesh_gateway_controller.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resources - -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" - "sigs.k8s.io/controller-runtime/pkg/client" - - 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 := onDelete(ctx, req, r.Client, resource); err != nil { - return ctrl.Result{}, err - } - } else { - // Fetch GatewayClassConfig for the gateway - gcc, err := getGatewayClassConfigByGatewayClassName(ctx, r.Client, resource.Spec.GatewayClassName) - if err != nil { - r.Log.Error(err, "unable to get gatewayclassconfig for gateway: %s gatewayclass: %s", resource.Name, resource.Spec.GatewayClassName) - return ctrl.Result{}, err - } - - if err := onCreateUpdate(ctx, r.Client, gatewayConfigs{ - gcc: gcc, - gatewayConfig: r.GatewayConfig, - }, resource, gateways.MeshGatewayAnnotationKind); 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 setupGatewayControllerWithManager[*meshv2beta1.MeshGatewayList](mgr, &meshv2beta1.MeshGateway{}, r.Client, r, MeshGateway_GatewayClassIndex) -} 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 35e8384b3f..0000000000 --- a/control-plane/gateways/builder.go +++ /dev/null @@ -1,38 +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" - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type Gateway interface { - *meshv2beta1.MeshGateway | *meshv2beta1.APIGateway - client.Object - ListenersToServicePorts(int32) []corev1.ServicePort - ListenersToContainerPorts(int32, int32) []corev1.ContainerPort -} - -// gatewayBuilder 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 gatewayBuilder[T Gateway] struct { - gateway T - gcc *meshv2beta1.GatewayClassConfig - config GatewayConfig - gatewayKind string -} - -// NewGatewayBuilder returns a new meshGatewayBuilder for the given MeshGateway, -// GatewayConfig, and GatewayClassConfig. -func NewGatewayBuilder[T Gateway](gateway T, gatewayConfig GatewayConfig, gatewayClassConfig *meshv2beta1.GatewayClassConfig, gatewayKind string) *gatewayBuilder[T] { - return &gatewayBuilder[T]{ - gateway: gateway, - config: gatewayConfig, - gcc: gatewayClassConfig, - gatewayKind: gatewayKind, - } -} 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 5bab84dec8..0000000000 --- a/control-plane/gateways/deployment.go +++ /dev/null @@ -1,208 +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" - APIGatewayAnnotationKind = "api-gateway" -) - -func (b *gatewayBuilder[T]) Deployment() (*appsv1.Deployment, error) { - spec, err := b.deploymentSpec() - return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.GetName(), - Namespace: b.gateway.GetNamespace(), - Labels: b.labelsForDeployment(), - Annotations: b.annotationsForDeployment(), - }, - Spec: *spec, - }, err -} - -func (b *gatewayBuilder[T]) 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: b.gatewayKind, - // 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.GetAnnotations()[constants.AnnotationGatewayWANSource], - // This annotation determines the WAN port for the Mesh Gateway service registration. - constants.AnnotationGatewayWANPort: b.gateway.GetAnnotations()[constants.AnnotationGatewayWANPort], - // This annotation determines the address for the gateway when the source annotation is "Static". - constants.AnnotationGatewayWANAddress: b.gateway.GetAnnotations()[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 -} - -// areDeploymentsEqual determines whether two Deployments are the same in -// the ways that we care about. This specifically ignores valid out-of-band -// changes such as initContainer injection. -func areDeploymentsEqual(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) -} - -// MergeDeployment is used to update an appsv1.Deployment without overwriting any -// existing annotations or labels that were placed there by other vendors. -// -// based on https://github.com/kubernetes-sigs/controller-runtime/blob/4000e996a202917ad7d40f02ed8a2079a9ce25e9/pkg/controller/controllerutil/example_test.go -func MergeDeployment(existing, desired *appsv1.Deployment) { - // Only overwrite fields if the Deployment doesn't exist yet - if existing.ObjectMeta.CreationTimestamp.IsZero() { - existing.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences - existing.Spec = desired.Spec - existing.Annotations = desired.Annotations - existing.Labels = desired.Labels - return - } - - // Make sure we don't reconcile forever by overwriting valid out-of-band - // changes such as init container injection. If the deployments are - // sufficiently equal, we only update the annotations. - if !areDeploymentsEqual(existing, desired) { - desired.Spec.Replicas = deploymentReplicaCount(nil, existing.Spec.Replicas) - existing.Spec = desired.Spec - } - - // If the Deployment already exists, add any desired annotations + labels to existing set - for k, v := range desired.ObjectMeta.Annotations { - existing.ObjectMeta.Annotations[k] = v - } - for k, v := range desired.ObjectMeta.Labels { - existing.ObjectMeta.Labels[k] = v - } -} diff --git a/control-plane/gateways/deployment_dataplane_container.go b/control-plane/gateways/deployment_dataplane_container.go deleted file mode 100644 index 630e337ad5..0000000000 --- a/control-plane/gateways/deployment_dataplane_container.go +++ /dev/null @@ -1,199 +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 *gatewayBuilder[T]) 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.GetName(), - 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), - }) - - container.Ports = append(container.Ports, b.gateway.ListenersToContainerPorts(containerConfig.PortModifier, containerConfig.HostPort)...) - - // 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 *gatewayBuilder[T]) 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.GetNamespace(), 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.GetNamespace(), b.gateway.GetName()), - ) - 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 beb7267005..0000000000 --- a/control-plane/gateways/deployment_init_container.go +++ /dev/null @@ -1,193 +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 *gatewayBuilder[T]) initContainer() (corev1.Container, error) { - data := initContainerCommandData{ - AuthMethod: b.config.AuthMethod, - LogLevel: b.logLevelForInitContainer(), - LogJSON: b.config.LogJSON, - ServiceName: b.gateway.GetName(), - 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.GetNamespace(), 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 24e5fa67a2..0000000000 --- a/control-plane/gateways/deployment_test.go +++ /dev/null @@ -1,1262 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gateways - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - 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" - - 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" -) - -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_gatewayBuilder_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", - 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", - }, - }, - 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, - Protocol: "tcp", - }, - }, - 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", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 443, - Protocol: "tcp", - }, - }, - }, - }, - 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, - Protocol: "tcp", - }, - }, - 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", - Listeners: []*pbmesh.MeshGatewayListener{ - { - Name: "wan", - Port: 443, - Protocol: "tcp", - }, - }, - }, - }, - 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, - Protocol: "tcp", - }, - }, - 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 := &gatewayBuilder[*meshv2beta1.MeshGateway]{ - gateway: tt.fields.gateway, - config: tt.fields.config, - gcc: tt.fields.gcc, - gatewayKind: MeshGatewayAnnotationKind, - } - got, err := b.Deployment() - if !tt.wantErr && (err != nil) { - assert.Errorf(t, err, "Error") - } - assert.Equalf(t, tt.want, got, "Deployment()") - }) - } -} - -func Test_MergeDeployment(t *testing.T) { - testCases := []struct { - name string - a, b *appsv1.Deployment - assertFn func(*testing.T, *appsv1.Deployment) - }{ - { - name: "new deployment gets desired annotations + labels + containers", - a: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "deployment"}}, - b: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "deployment", - Annotations: map[string]string{"b": "b"}, - Labels: map[string]string{"b": "b"}, - }}, - assertFn: func(t *testing.T, result *appsv1.Deployment) { - assert.Equal(t, map[string]string{"b": "b"}, result.Annotations) - assert.Equal(t, map[string]string{"b": "b"}, result.Labels) - }, - }, - { - name: "existing deployment keeps existing annotations + labels and gains desired annotations + labels + containers", - a: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "deployment", - CreationTimestamp: metav1.Now(), - Annotations: map[string]string{"a": "a"}, - Labels: map[string]string{"a": "a"}, - }}, - b: &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "deployment", - Annotations: map[string]string{"b": "b"}, - Labels: map[string]string{"b": "b"}, - }, - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{Name: "b"}}, - }, - }, - }, - }, - assertFn: func(t *testing.T, result *appsv1.Deployment) { - assert.Equal(t, map[string]string{"a": "a", "b": "b"}, result.Annotations) - assert.Equal(t, map[string]string{"a": "a", "b": "b"}, result.Labels) - - require.Len(t, result.Spec.Template.Spec.Containers, 1) - assert.Equal(t, "b", result.Spec.Template.Spec.Containers[0].Name) - }, - }, - { - name: "existing deployment with injected initContainer retains it", - a: &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "deployment", - CreationTimestamp: metav1.Now(), - Annotations: map[string]string{"a": "a"}, - Labels: map[string]string{"a": "a"}, - }, - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - InitContainers: []corev1.Container{{Name: "b"}}, - Containers: []corev1.Container{{Name: "b"}}, - }, - }, - }, - }, - b: &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "deployment", - Annotations: map[string]string{"b": "b"}, - Labels: map[string]string{"b": "b"}, - }, - Spec: appsv1.DeploymentSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{Name: "b"}}, - }, - }, - }, - }, - assertFn: func(t *testing.T, result *appsv1.Deployment) { - assert.Equal(t, map[string]string{"a": "a", "b": "b"}, result.Annotations) - assert.Equal(t, map[string]string{"a": "a", "b": "b"}, result.Labels) - - require.Len(t, result.Spec.Template.Spec.InitContainers, 1) - assert.Equal(t, "b", result.Spec.Template.Spec.InitContainers[0].Name) - - require.Len(t, result.Spec.Template.Spec.Containers, 1) - assert.Equal(t, "b", result.Spec.Template.Spec.Containers[0].Name) - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - MergeDeployment(testCase.a, testCase.b) - testCase.assertFn(t, testCase.a) - }) - } -} 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 4581efc232..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 *gatewayBuilder[T]) annotationsForDeployment() map[string]string { - if b.gcc == nil { - return map[string]string{} - } - return computeAnnotationsOrLabels(b.gateway.GetAnnotations(), b.gcc.Spec.Deployment.Annotations, b.gcc.Spec.Annotations) -} - -func (b *gatewayBuilder[T]) annotationsForRole() map[string]string { - if b.gcc == nil { - return map[string]string{} - } - return computeAnnotationsOrLabels(b.gateway.GetAnnotations(), b.gcc.Spec.Role.Annotations, b.gcc.Spec.Annotations) -} - -func (b *gatewayBuilder[T]) annotationsForRoleBinding() map[string]string { - if b.gcc == nil { - return map[string]string{} - } - return computeAnnotationsOrLabels(b.gateway.GetAnnotations(), b.gcc.Spec.RoleBinding.Annotations, b.gcc.Spec.Annotations) -} - -func (b *gatewayBuilder[T]) annotationsForService() map[string]string { - if b.gcc == nil { - return map[string]string{} - } - return computeAnnotationsOrLabels(b.gateway.GetAnnotations(), b.gcc.Spec.Service.Annotations, b.gcc.Spec.Annotations) -} - -func (b *gatewayBuilder[T]) annotationsForServiceAccount() map[string]string { - if b.gcc == nil { - return map[string]string{} - } - return computeAnnotationsOrLabels(b.gateway.GetAnnotations(), b.gcc.Spec.ServiceAccount.Annotations, b.gcc.Spec.Annotations) -} - -func (b *gatewayBuilder[T]) labelsForDeployment() map[string]string { - if b.gcc == nil { - return defaultLabels - } - - labels := computeAnnotationsOrLabels(b.gateway.GetLabels(), b.gcc.Spec.Deployment.Labels, b.gcc.Spec.Labels) - for k, v := range defaultLabels { - labels[k] = v - } - return labels -} - -func (b *gatewayBuilder[T]) 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 *gatewayBuilder[T]) 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 *gatewayBuilder[T]) labelsForRole() map[string]string { - if b.gcc == nil { - return defaultLabels - } - - labels := computeAnnotationsOrLabels(b.gateway.GetLabels(), b.gcc.Spec.Role.Labels, b.gcc.Spec.Labels) - for k, v := range defaultLabels { - labels[k] = v - } - return labels -} - -func (b *gatewayBuilder[T]) labelsForRoleBinding() map[string]string { - if b.gcc == nil { - return defaultLabels - } - - labels := computeAnnotationsOrLabels(b.gateway.GetLabels(), b.gcc.Spec.RoleBinding.Labels, b.gcc.Spec.Labels) - for k, v := range defaultLabels { - labels[k] = v - } - return labels -} - -func (b *gatewayBuilder[T]) labelsForService() map[string]string { - if b.gcc == nil { - return defaultLabels - } - - labels := computeAnnotationsOrLabels(b.gateway.GetLabels(), b.gcc.Spec.Service.Labels, b.gcc.Spec.Labels) - for k, v := range defaultLabels { - labels[k] = v - } - return labels -} - -func (b *gatewayBuilder[T]) labelsForServiceAccount() map[string]string { - if b.gcc == nil { - return defaultLabels - } - - labels := computeAnnotationsOrLabels(b.gateway.GetLabels(), 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 5b4861c1d0..0000000000 --- a/control-plane/gateways/metadata_test.go +++ /dev/null @@ -1,341 +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 TestGatewayBuilder_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 := NewGatewayBuilder[*meshv2beta1.MeshGateway](gateway, GatewayConfig{}, gatewayClassConfig, MeshGatewayAnnotationKind) - - 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 TestGatewayBuilder_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 := NewGatewayBuilder[*meshv2beta1.MeshGateway](gateway, GatewayConfig{}, gatewayClassConfig, MeshGatewayAnnotationKind) - - 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 TestGatewayBuilder_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 := NewGatewayBuilder(&meshv2beta1.MeshGateway{}, GatewayConfig{LogLevel: testCase.GatewayLogLevel}, gcc, MeshGatewayAnnotationKind) - - 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 0cec287b5b..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 *gatewayBuilder[T]) Role() *rbacv1.Role { - return &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.GetName(), - Namespace: b.gateway.GetNamespace(), - Labels: b.labelsForRole(), - Annotations: b.annotationsForRole(), - }, - Rules: []rbacv1.PolicyRule{}, - } -} - -func (b *gatewayBuilder[T]) RoleBinding() *rbacv1.RoleBinding { - return &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.gateway.GetName(), - Namespace: b.gateway.GetNamespace(), - Labels: b.labelsForRoleBinding(), - Annotations: b.annotationsForRoleBinding(), - }, - Subjects: []rbacv1.Subject{ - { - APIGroup: "", - Kind: rbacv1.ServiceAccountKind, - Name: b.gateway.GetName(), - Namespace: b.gateway.GetNamespace(), - }, - }, - 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 b60cc42019..0000000000 --- a/control-plane/gateways/service.go +++ /dev/null @@ -1,63 +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" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -func (b *gatewayBuilder[T]) 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.GetName(), - Namespace: b.gateway.GetNamespace(), - Labels: b.labelsForService(), - Annotations: b.annotationsForService(), - }, - Spec: corev1.ServiceSpec{ - Selector: b.labelsForDeployment(), - Type: serviceType, - Ports: b.gateway.ListenersToServicePorts(portModifier), - }, - } -} - -// MergeService is used to update a corev1.Service without overwriting any -// existing annotations or labels that were placed there by other vendors. -// -// based on https://github.com/kubernetes-sigs/controller-runtime/blob/4000e996a202917ad7d40f02ed8a2079a9ce25e9/pkg/controller/controllerutil/example_test.go -func MergeService(existing, desired *corev1.Service) { - existing.Spec = desired.Spec - - // Only overwrite fields if the Service doesn't exist yet - if existing.ObjectMeta.CreationTimestamp.IsZero() { - existing.ObjectMeta.OwnerReferences = desired.ObjectMeta.OwnerReferences - existing.Annotations = desired.Annotations - existing.Labels = desired.Labels - return - } - - // If the Service already exists, add any desired annotations + labels to existing set - for k, v := range desired.ObjectMeta.Annotations { - existing.ObjectMeta.Annotations[k] = v - } - for k, v := range desired.ObjectMeta.Labels { - existing.ObjectMeta.Labels[k] = v - } -} diff --git a/control-plane/gateways/service_test.go b/control-plane/gateways/service_test.go deleted file mode 100644 index 19e9a0e71d..0000000000 --- a/control-plane/gateways/service_test.go +++ /dev/null @@ -1,358 +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_gatewayBuilder_meshGateway_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 := &gatewayBuilder[*meshv2beta1.MeshGateway]{ - gateway: tt.fields.gateway, - config: tt.fields.config, - gcc: tt.fields.gcc, - } - result := b.Service() - assert.Equalf(t, tt.want, result, "Service()") - }) - } -} - -func Test_MergeService(t *testing.T) { - testCases := []struct { - name string - a, b *corev1.Service - assertFn func(*testing.T, *corev1.Service) - }{ - { - name: "new service gets desired annotations + labels", - a: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "service"}}, - b: &corev1.Service{ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "service", - Annotations: map[string]string{"b": "b"}, - Labels: map[string]string{"b": "b"}, - }}, - assertFn: func(t *testing.T, result *corev1.Service) { - assert.Equal(t, map[string]string{"b": "b"}, result.Annotations) - assert.Equal(t, map[string]string{"b": "b"}, result.Labels) - }, - }, - { - name: "existing service keeps existing annotations + labels and gains desired annotations + labels + type", - a: &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "service", - CreationTimestamp: metav1.Now(), - Annotations: map[string]string{"a": "a"}, - Labels: map[string]string{"a": "a"}, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeClusterIP, - }, - }, - b: &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "service", - Annotations: map[string]string{"b": "b"}, - Labels: map[string]string{"b": "b"}, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeLoadBalancer, - }, - }, - assertFn: func(t *testing.T, result *corev1.Service) { - assert.Equal(t, map[string]string{"a": "a", "b": "b"}, result.Annotations) - assert.Equal(t, map[string]string{"a": "a", "b": "b"}, result.Labels) - - assert.Equal(t, corev1.ServiceTypeLoadBalancer, result.Spec.Type) - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - MergeService(testCase.a, testCase.b) - testCase.assertFn(t, testCase.a) - }) - } -} - -func Test_gatewayBuilder_apiGateway_Service(t *testing.T) { - lbType := corev1.ServiceTypeLoadBalancer - - type fields struct { - gateway *meshv2beta1.APIGateway - 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.APIGateway{ - Spec: pbmesh.APIGateway{ - GatewayClassName: "test-gateway-class", - Listeners: []*pbmesh.APIGatewayListener{ - { - Name: "http-listener", - Port: 80, - Protocol: "http", - }, - }, - }, - }, - 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: "http-listener", - Port: int32(80), - TargetPort: intstr.IntOrString{ - IntVal: int32(8080), - }, - Protocol: "http", - }, - }, - }, - Status: corev1.ServiceStatus{}, - }, - }, - { - name: "create service resource crd - gcc is nil", - fields: fields{ - gateway: &meshv2beta1.APIGateway{ - Spec: pbmesh.APIGateway{ - GatewayClassName: "test-gateway-class", - Listeners: []*pbmesh.APIGatewayListener{ - { - Name: "http-listener", - Port: 80, - Protocol: "http", - }, - }, - }, - }, - 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: "http-listener", - Port: int32(80), - TargetPort: intstr.IntOrString{ - IntVal: int32(80), - }, - Protocol: "http", - }, - }, - }, - Status: corev1.ServiceStatus{}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := &gatewayBuilder[*meshv2beta1.APIGateway]{ - 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 8b740cbd76..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 *gatewayBuilder[T]) ServiceAccount() *corev1.ServiceAccount { - return &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.serviceAccountName(), - Namespace: b.gateway.GetNamespace(), - Labels: b.labelsForServiceAccount(), - Annotations: b.annotationsForServiceAccount(), - }, - } -} - -func (b *gatewayBuilder[T]) serviceAccountName() string { - return b.gateway.GetName() -} diff --git a/control-plane/gateways/serviceaccount_test.go b/control-plane/gateways/serviceaccount_test.go deleted file mode 100644 index 7436beb683..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 := NewGatewayBuilder(&meshv2beta1.MeshGateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "mesh-gateway", - }, - }, GatewayConfig{}, nil, MeshGatewayAnnotationKind) - - 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 9d23705050..700db25611 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/consul-k8s/control-plane -replace github.com/hashicorp/consul/api => github.com/hashicorp/consul/api v1.10.1-0.20240312203720-262f4358003f +replace github.com/hashicorp/consul-k8s/version => ../version require ( github.com/cenkalti/backoff v2.2.1+incompatible @@ -8,21 +8,19 @@ require ( 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 + github.com/go-logr/logr v1.3.0 + github.com/google/go-cmp v0.6.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20240226161840-f3842c41cb2b + github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d + github.com/hashicorp/consul-k8s/version v0.0.0 github.com/hashicorp/consul-server-connection-manager v0.1.6 - github.com/hashicorp/consul/api v1.28.2 - github.com/hashicorp/consul/proto-public v0.6.0 - github.com/hashicorp/consul/sdk v0.16.0 - github.com/hashicorp/go-bexpr v0.1.11 + github.com/hashicorp/consul/api v1.21.3 + github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 - github.com/hashicorp/go-hclog v1.5.0 + github.com/hashicorp/go-hclog v1.6.3 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.12.2 @@ -32,22 +30,16 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 github.com/stretchr/testify v1.8.4 - go.uber.org/zap v1.24.0 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/text v0.14.0 + go.uber.org/zap v1.25.0 + golang.org/x/text v0.17.0 golang.org/x/time v0.3.0 - gomodules.xyz/jsonpatch/v2 v2.3.0 - google.golang.org/grpc v1.58.3 - google.golang.org/protobuf v1.33.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 + gomodules.xyz/jsonpatch/v2 v2.4.0 + k8s.io/api v0.28.13 + k8s.io/apimachinery v0.28.13 + k8s.io/client-go v0.28.13 + k8s.io/klog/v2 v2.110.1 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b + sigs.k8s.io/controller-runtime v0.16.5 ) require ( @@ -72,23 +64,23 @@ require ( github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // 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.7.5 // indirect github.com/dimchansky/utfbom v1.1.0 // indirect - github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect - github.com/go-logr/zapr v1.2.3 // indirect + github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.1 // indirect + github.com/go-openapi/jsonreference v0.20.2 // 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.4 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.4 // indirect @@ -96,14 +88,16 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.11.0 // 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-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // 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/go-uuid v1.0.2 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/mdns v1.0.4 // indirect github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect @@ -117,20 +111,18 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mitchellh/pointerstructure v1.2.1 // 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/onsi/gomega v1.24.2 // indirect github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // 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/posener/complete v1.2.3 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.10.1 // 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.9.3 // indirect @@ -141,27 +133,33 @@ require ( github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.480 // 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.22.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.24.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/term v0.19.0 // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // 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 + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.28.3 // indirect + k8s.io/component-base v0.28.3 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // 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/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) go 1.21 + +toolchain go1.22.5 diff --git a/control-plane/go.sum b/control-plane/go.sum index b09b04cbf7..eaed8cdacc 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -1,40 +1,9 @@ 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 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.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= 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 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-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= @@ -64,7 +33,6 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ 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/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/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= @@ -72,7 +40,6 @@ 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/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -83,8 +50,9 @@ 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/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= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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= @@ -99,7 +67,6 @@ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqy github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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= @@ -118,9 +85,8 @@ github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl3 github.com/containernetworking/cni v1.1.2/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= @@ -132,9 +98,8 @@ 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/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/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= @@ -148,8 +113,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.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= 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= @@ -158,57 +123,41 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 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= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= 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-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/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= +github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= 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/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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/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/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 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 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= @@ -222,39 +171,28 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 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.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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/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.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/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -264,43 +202,36 @@ 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/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= 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-20240226161840-f3842c41cb2b h1:KWZfzPx9N7AvhnIOcc26YyER1fHMPILfLaYpig7G83s= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20240226161840-f3842c41cb2b/go.mod h1:9NKJHOcgmz/6P2y6MegNIOXhIKE/0ils/mHWd5sZgoU= +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.20240312203720-262f4358003f h1:8clIrMnJtO5ab5Kd1qF19s9s581cyGYhQxfPLVRaFZs= -github.com/hashicorp/consul/api v1.10.1-0.20240312203720-262f4358003f/go.mod h1:JnWx0qZd1Ffeoa42yVAxzv7/v7eaZyptkw0dG9F/gF4= -github.com/hashicorp/consul/proto-public v0.6.0 h1:9qrBujmoTB5gQQ84kQO+YWvhjgYoYBNrOoHdo4cpHHM= -github.com/hashicorp/consul/proto-public v0.6.0/go.mod h1:JF6983XNCzvw4wDNOLEwLqOq2IPw7iyT+pkswHSz08U= -github.com/hashicorp/consul/sdk v0.16.0 h1:SE9m0W6DEfgIVCJX7xU+iv/hUl4m/nxqMTnCdMxDpJ8= -github.com/hashicorp/consul/sdk v0.16.0/go.mod h1:7pxqqhqoaPqnBnzXD1StKed62LqJeClzVsUEy85Zr0A= +github.com/hashicorp/consul/api v1.21.3 h1:hHfaZwkrxbU6TjnlmxtSrvH0mXGep8aCXBrJ9nNhJug= +github.com/hashicorp/consul/api v1.21.3/go.mod h1:Kms78llSVzB8FtrLan+V+TSJTnfHkRGlYbhazvGzJUs= +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= 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 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.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/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-msgpack v0.5.5/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= @@ -308,8 +239,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 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-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-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 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.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= @@ -323,14 +254,13 @@ 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.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/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-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru 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/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/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= @@ -345,7 +275,6 @@ github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJ 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/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.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= @@ -360,27 +289,19 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm 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/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= -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= 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/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.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.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.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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= @@ -421,8 +342,6 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F 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/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= @@ -433,7 +352,6 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY 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/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/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -443,13 +361,13 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 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/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.6.1 h1:1xQPCjcqYw/J5LchOcp4/2q/jzJFjiAOc25chhnDw+Q= -github.com/onsi/ginkgo/v2 v2.6.1/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= 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/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= -github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= 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= @@ -459,20 +377,16 @@ 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= 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.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_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_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= @@ -481,23 +395,16 @@ 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.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/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= 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.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/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/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/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -508,14 +415,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/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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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/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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -539,36 +444,26 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.480/go.mod h1: github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= -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.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.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 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/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -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/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-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= @@ -576,45 +471,22 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/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/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/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-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/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.8.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/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -624,28 +496,12 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-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-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-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= -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-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-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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -653,41 +509,29 @@ 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-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/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-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.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -696,54 +540,29 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h 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-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-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-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-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= -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-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-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-20210112080510-489259a85091/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= 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-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -756,19 +575,17 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/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= @@ -777,126 +594,43 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 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/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/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-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-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-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.5/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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= 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= -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.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= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= 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= -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-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-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= -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-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= @@ -904,17 +638,9 @@ google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= 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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -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.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= @@ -929,7 +655,6 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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= @@ -941,7 +666,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 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/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= @@ -958,44 +682,32 @@ 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-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= 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.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= -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= +k8s.io/api v0.28.13 h1:0Sw8MjdkmrJAF/uVv09HXSZ3cQauVyZHQWKt8hiiKo4= +k8s.io/api v0.28.13/go.mod h1:7hlRF5wArzXf0qbRRT2TMtHRa5SHBEVJhA02JpTxj9Q= +k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= +k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= +k8s.io/apimachinery v0.28.13 h1:0O2mk2i0Yi+xkron0lK//biI21F1eGXb4eXECLU5v7g= +k8s.io/apimachinery v0.28.13/go.mod h1:zUG757HaKs6Dc3iGtKjzIpBfqTM4yiRsEe3/E7NX15o= +k8s.io/client-go v0.28.13 h1:kHgFOxWwAsa8VxL6Oylo10V6euobub9Jo0wyEWrhrWk= +k8s.io/client-go v0.28.13/go.mod h1:IudvInbWfd+6WLreEVnBnZJCGFaSROCFbny9jFTkk7g= +k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= +k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.16.5 h1:yr1cEJbX08xsTW6XEIzT13KHHmIyX8Umvme2cULvFZw= +sigs.k8s.io/controller-runtime v0.16.5/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= 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/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/control-plane/helper/go-discover/discover.go b/control-plane/helper/go-discover/discover.go index 140586634a..6bd56b3364 100644 --- a/control-plane/helper/go-discover/discover.go +++ b/control-plane/helper/go-discover/discover.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "github.com/hashicorp/consul-k8s/control-plane/version" + "github.com/hashicorp/consul-k8s/version" "github.com/hashicorp/go-discover" discoverk8s "github.com/hashicorp/go-discover/provider/k8s" "github.com/hashicorp/go-hclog" 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..093b1ef908 --- /dev/null +++ b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go @@ -0,0 +1,54 @@ +// 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/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..be1a3b5c64 --- /dev/null +++ b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go @@ -0,0 +1,47 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +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/test/test_util.go b/control-plane/helper/test/test_util.go index df51927e4c..e29e44de59 100644 --- a/control-plane/helper/test/test_util.go +++ b/control-plane/helper/test/test_util.go @@ -4,7 +4,6 @@ package test import ( - "context" "fmt" "net" "net/http" @@ -14,37 +13,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 +46,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 +58,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 +198,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 +241,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 +257,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 +302,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 +323,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..bf4cccfcae 100644 --- a/control-plane/main.go +++ b/control-plane/main.go @@ -7,9 +7,8 @@ import ( "log" "os" + "github.com/hashicorp/consul-k8s/version" "github.com/mitchellh/cli" - - "github.com/hashicorp/consul-k8s/control-plane/version" ) func main() { diff --git a/control-plane/namespaces/namespaces.go b/control-plane/namespaces/namespaces.go index 8378c3cc89..cadfb83aa5 100644 --- a/control-plane/namespaces/namespaces.go +++ b/control-plane/namespaces/namespaces.go @@ -55,29 +55,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. diff --git a/control-plane/namespaces/namespaces_test.go b/control-plane/namespaces/namespaces_test.go index 6e1305d959..a2c9989ae8 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.WaitForLeader(t) + consul.WaitForSerfCheck(t) consulClient, err := capi.NewClient(&capi.Config{ Address: consul.HTTPAddr, Token: masterToken, @@ -159,56 +159,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..77e8f87d87 100644 --- a/control-plane/subcommand/acl-init/command.go +++ b/control-plane/subcommand/acl-init/command.go @@ -18,6 +18,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 +29,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..acdafc939f 100644 --- a/control-plane/subcommand/acl-init/command_test.go +++ b/control-plane/subcommand/acl-init/command_test.go @@ -13,15 +13,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/common/common.go b/control-plane/subcommand/common/common.go index 4f0d278e55..1636c0b10e 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -13,13 +13,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 ( @@ -28,8 +27,6 @@ const ( // create-federation-secret commands and so lives in this common package. ACLReplicationTokenName = "acl-replication" - DatadogAgentTokenName = "datadog-agent-metrics" - // ACLTokenSecretKey is the key that we store the ACL tokens in when we // create Kubernetes secrets. ACLTokenSecretKey = "token" diff --git a/control-plane/subcommand/common/common_test.go b/control-plane/subcommand/common/common_test.go index 2925638c6e..9e50302b17 100644 --- a/control-plane/subcommand/common/common_test.go +++ b/control-plane/subcommand/common/common_test.go @@ -12,13 +12,12 @@ 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.go b/control-plane/subcommand/connect-init/command.go index 2d245797dc..a5fbe9066c 100644 --- a/control-plane/subcommand/connect-init/command.go +++ b/control-plane/subcommand/connect-init/command.go @@ -17,19 +17,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 +161,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 +186,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 +316,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..b3ef7109e0 100644 --- a/control-plane/subcommand/connect-init/command_ent_test.go +++ b/control-plane/subcommand/connect-init/command_ent_test.go @@ -12,12 +12,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..69abc8f1ad 100644 --- a/control-plane/subcommand/connect-init/command_test.go +++ b/control-plane/subcommand/connect-init/command_test.go @@ -14,14 +14,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 +615,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 +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", "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..a6b599dccd 100644 --- a/control-plane/subcommand/consul-logout/command.go +++ b/control-plane/subcommand/consul-logout/command.go @@ -7,13 +7,12 @@ 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..e7e3a00f38 100644 --- a/control-plane/subcommand/consul-logout/command_test.go +++ b/control-plane/subcommand/consul-logout/command_test.go @@ -9,13 +9,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 +54,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 +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", "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 +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", "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..52600aedda 100644 --- a/control-plane/subcommand/create-federation-secret/command.go +++ b/control-plane/subcommand/create-federation-secret/command.go @@ -15,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/mitchellh/cli" @@ -22,11 +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/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..6a7991b8fe 100644 --- a/control-plane/subcommand/create-federation-secret/command_test.go +++ b/control-plane/subcommand/create-federation-secret/command_test.go @@ -14,6 +14,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 +25,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 +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=10s", + "-consul-api-timeout=5s", "-log-level=invalid", }, expErr: "unknown log level: invalid", @@ -118,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", "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 +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", "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 +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", "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 +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", "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 +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", "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 +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", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 1, exitCode, ui.ErrorWriter.String()) } @@ -845,7 +844,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 +992,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 +1052,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..f6f594393d 100644 --- a/control-plane/subcommand/delete-completed-job/command.go +++ b/control-plane/subcommand/delete-completed-job/command.go @@ -10,16 +10,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/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..9368b95b3d 100644 --- a/control-plane/subcommand/flags/consul.go +++ b/control-plane/subcommand/flags/consul.go @@ -11,7 +11,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 +26,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 +93,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 +142,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..7f35dc8575 100644 --- a/control-plane/subcommand/flags/consul_test.go +++ b/control-plane/subcommand/flags/consul_test.go @@ -9,7 +9,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 +29,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 +89,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/http.go b/control-plane/subcommand/flags/http.go index 5421b4c672..21ccb45df1 100644 --- a/control-plane/subcommand/flags/http.go +++ b/control-plane/subcommand/flags/http.go @@ -9,9 +9,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/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 946e2d2703..0000000000 --- a/control-plane/subcommand/gateway-resources/command.go +++ /dev/null @@ -1,680 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package gatewayresources - -import ( - "context" - "encoding/json" - "errors" - "flag" - "fmt" - "io" - "os" - "strconv" - "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" - - 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 - - flagEnableMetrics string - flagMetricsPort string - flagMetricsPath string - - 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.flagEnableMetrics, "enable-metrics", "", "specify as 'true' or 'false' to enable or disable metrics collection") - c.flags.StringVar(&c.flagMetricsPath, "metrics-path", "", "specify to set the path used for metrics scraping") - c.flags.StringVar(&c.flagMetricsPort, "metrics-port", "", "specify to set the port used for metrics scraping") - - 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), - }, - } - - if metricsEnabled, isSet := getMetricsEnabled(c.flagEnableMetrics); isSet { - classConfig.Spec.Metrics.Enabled = &metricsEnabled - if port, isValid := getScrapePort(c.flagMetricsPort); isValid { - port32 := int32(port) - classConfig.Spec.Metrics.Port = &port32 - } - if path, isSet := getScrapePath(c.flagMetricsPath); isSet { - classConfig.Spec.Metrics.Path = &path - } - } - - 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) - } - } - - if c.flagEnableMetrics != "" { - if _, valid := getMetricsEnabled(c.flagEnableMetrics); !valid { - return errors.New("-enable-metrics must be either 'true' or 'false'") - } - } - - if c.flagMetricsPort != "" { - if _, valid := getScrapePort(c.flagMetricsPort); !valid { - return errors.New("-metrics-port must be a valid unprivileged port number") - } - } - - 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 - } - - class := &v2beta1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{Name: cfg.Name, Labels: labels}, - TypeMeta: metav1.TypeMeta{Kind: v2beta1.KindGatewayClass}, - Spec: v2beta1.GatewayClassSpec{ - ControllerName: controllerName, - ParametersRef: &v2beta1.ParametersReference{ - Group: v2beta1.MeshGroup, - Kind: v2beta1.KindGatewayClassConfig, - 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 getScrapePort(v string) (int, bool) { - port, err := strconv.Atoi(v) - if err != nil { - // we only use the port if it's actually valid - return 0, false - } - if port < 1024 || port > 65535 { - return 0, false - } - return port, true -} - -func getScrapePath(v string) (string, bool) { - if v == "" { - return "", false - } - return v, true -} - -func getMetricsEnabled(v string) (bool, bool) { - if v == "true" { - return true, true - } - if v == "false" { - return false, true - } - return false, false -} - -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 70eb1e3d90..0000000000 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ /dev/null @@ -1,648 +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" - - 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" -) - -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: v2beta1.KindGatewayClassConfig, - }, - 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..619f08625d 100644 --- a/control-plane/subcommand/get-consul-client-ca/command.go +++ b/control-plane/subcommand/get-consul-client-ca/command.go @@ -13,15 +13,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..64325ae8b3 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=10s", + "-consul-api-timeout=5s", "-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", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter.String()) @@ -157,7 +157,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) { @@ -176,7 +175,7 @@ func TestRun_ConsulServerAvailableLater(t *testing.T) { c.CertFile = certFile c.KeyFile = keyFile }) - require.NoError(t, err) + wg.Done() }() defer func() { if a != nil { @@ -189,11 +188,13 @@ func TestRun_ConsulServerAvailableLater(t *testing.T) { "-server-port", fmt.Sprintf("%d", randomPorts[2]), "-ca-file", caFile, "-output-file", outputFile.Name(), - "-consul-api-timeout", "10s", + "-consul-api-timeout", "5s", }) require.Equal(t, 0, exitCode, ui.ErrorWriter) wg.Wait() + require.NoError(t, err) + client, err := api.NewClient(&api.Config{ Address: a.HTTPSAddr, Scheme: "https", @@ -281,7 +282,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 +350,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..cf871eca69 100644 --- a/control-plane/subcommand/gossip-encryption-autogenerate/command.go +++ b/control-plane/subcommand/gossip-encryption-autogenerate/command.go @@ -11,16 +11,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/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index a9db25f3d8..a27c6b233a 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -27,21 +27,24 @@ 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" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" - authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - multiclusterv2 "github.com/hashicorp/consul-k8s/control-plane/api/multicluster/v2" + 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" ) -const ( - WebhookCAFilename = "ca.crt" -) +const WebhookCAFilename = "ca.crt" type Command struct { UI cli.Ui @@ -57,8 +60,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) @@ -87,10 +88,8 @@ type Command struct { flagDefaultEnableSidecarProxyLifecycle bool flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners bool flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds int - flagDefaultSidecarProxyLifecycleStartupGracePeriodSeconds int flagDefaultSidecarProxyLifecycleGracefulPort string flagDefaultSidecarProxyLifecycleGracefulShutdownPath string - flagDefaultSidecarProxyLifecycleGracefulStartupPath string flagDefaultSidecarProxyStartupFailureSeconds int flagDefaultSidecarProxyLivenessFailureSeconds int @@ -141,18 +140,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 } @@ -164,18 +151,9 @@ 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(multiclusterv2.AddMultiClusterToScheme(scheme)) - - // +kubebuilder:scaffold:scheme + //+kubebuilder:scaffold:scheme } func (c *Command) init() { @@ -241,10 +219,6 @@ 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 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.") @@ -256,10 +230,8 @@ func (c *Command) init() { 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.IntVar(&c.flagDefaultSidecarProxyLifecycleStartupGracePeriodSeconds, "default-sidecar-proxy-lifecycle-startup-grace-period-seconds", 0, "Default sidecar proxy startup 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.") - c.flagSet.StringVar(&c.flagDefaultSidecarProxyLifecycleGracefulStartupPath, "default-sidecar-proxy-lifecycle-graceful-startup-path", "/graceful_startup", "Default sidecar proxy lifecycle management graceful startup 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.") @@ -302,13 +274,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 } @@ -327,6 +353,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())) @@ -353,9 +383,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 @@ -372,14 +406,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. @@ -390,32 +424,339 @@ func (c *Command) Run(args []string) int { } mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - LeaderElection: true, - LeaderElectionID: "consul-controller-lock", - Host: listenSplits[0], - Port: port, - Logger: zapLogger, - MetricsBindAddress: "0.0.0.0:9444", + Scheme: scheme, + LeaderElection: true, + LeaderElectionID: "consul-controller-lock", + Logger: zapLogger, + Metrics: metricsserver.Options{ + BindAddress: "0.0.0.0:9444", + }, HealthProbeBindAddress: "0.0.0.0:9445", + WebhookServer: ctrlRuntimeWebhook.NewServer(ctrlRuntimeWebhook.Options{ + CertDir: c.flagCertDir, + Host: listenSplits[0], + Port: port, + }), }) if err != nil { setupLog.Error(err, "unable to start manager") 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, + LifecycleConfig: lifecycleConfig, + 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 } - 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 + } + + (&v1alpha1.PeeringAcceptorWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName("peering-acceptor"), + }).SetupWithManager(mgr) + + (&v1alpha1.PeeringDialerWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName("peering-dialer"), + }).SetupWithManager(mgr) + } + + (&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, + DefaultSidecarProxyStartupFailureSeconds: c.flagDefaultSidecarProxyStartupFailureSeconds, + DefaultSidecarProxyLivenessFailureSeconds: c.flagDefaultSidecarProxyLivenessFailureSeconds, + 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, + }).SetupWithManager(mgr) + + // Note: The path here should be identical to the one on the kubebuilder + // annotation in each webhook file. + (&v1alpha1.ServiceDefaultsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceDefaults), + ConsulMeta: consulMeta, + }).SetupWithManager(mgr) + + (&v1alpha1.ServiceResolverWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceResolver), + ConsulMeta: consulMeta, + }).SetupWithManager(mgr) + + (&v1alpha1.ProxyDefaultsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ProxyDefaults), + ConsulMeta: consulMeta, + }).SetupWithManager(mgr) + + (&v1alpha1.MeshWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.Mesh), + ConsulMeta: consulMeta, + }).SetupWithManager(mgr) + + (&v1alpha1.ExportedServicesWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ExportedServices), + ConsulMeta: consulMeta, + }).SetupWithManager(mgr) + + (&v1alpha1.ServiceRouterWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceRouter), + ConsulMeta: consulMeta, + }).SetupWithManager(mgr) + + (&v1alpha1.ServiceSplitterWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceSplitter), + ConsulMeta: consulMeta, + }).SetupWithManager(mgr) + + (&v1alpha1.ServiceIntentionsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceIntentions), + ConsulMeta: consulMeta, + }).SetupWithManager(mgr) + + (&v1alpha1.IngressGatewayWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.IngressGateway), + ConsulMeta: consulMeta, + }).SetupWithManager(mgr) + + (&v1alpha1.TerminatingGatewayWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.TerminatingGateway), + ConsulMeta: consulMeta, + }).SetupWithManager(mgr) + + 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") return 1 @@ -424,6 +765,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") @@ -435,19 +790,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'") } @@ -460,95 +802,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, @@ -559,7 +854,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..9c64020376 100644 --- a/control-plane/subcommand/inject-connect/command_test.go +++ b/control-plane/subcommand/inject-connect/command_test.go @@ -132,15 +132,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 288ba92189..0000000000 --- a/control-plane/subcommand/inject-connect/v1controllers.go +++ /dev/null @@ -1,498 +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, - DefaultStartupGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleStartupGracePeriodSeconds, - DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, - DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, - DefaultGracefulStartupPath: c.flagDefaultSidecarProxyLifecycleGracefulStartupPath, - } - - 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, - LifecycleConfig: lifecycleConfig, - 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), - EnableGatewayMetrics: c.flagEnableGatewayMetrics, - DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, - DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, - InitContainerResources: &c.initContainerResources, - }, - 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 20fbbb5119..0000000000 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ /dev/null @@ -1,382 +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, - DefaultStartupGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleStartupGracePeriodSeconds, - DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, - DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, - DefaultGracefulStartupPath: c.flagDefaultSidecarProxyLifecycleGracefulStartupPath, - } - - 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.RegisterGatewayFieldIndexes(ctx, mgr); err != nil { - setupLog.Error(err, "unable to register field indexes") - 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 - } - - 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, - } - - if err := (&resourceControllers.MeshGatewayController{ - Controller: consulResourceController, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.MeshGateway), - Scheme: mgr.GetScheme(), - GatewayConfig: gatewayConfig, - }).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(), - GatewayConfig: gatewayConfig, - }).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/command.go b/control-plane/subcommand/install-cni/command.go index c626ee006b..53abe7cda1 100644 --- a/control-plane/subcommand/install-cni/command.go +++ b/control-plane/subcommand/install-cni/command.go @@ -16,11 +16,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..d5ee65f928 100644 --- a/control-plane/subcommand/install-cni/command_test.go +++ b/control-plane/subcommand/install-cni/command_test.go @@ -12,11 +12,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/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 3567b36102..0000000000 --- a/control-plane/subcommand/mesh-init/command_test.go +++ /dev/null @@ -1,404 +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, - }, - } -} - -// 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, - }, - } -} - -// 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..72c4ceeff0 100644 --- a/control-plane/subcommand/partition-init/command.go +++ b/control-plane/subcommand/partition-init/command.go @@ -11,20 +11,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 +26,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 +51,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 +69,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 +114,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 +171,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..182412c8aa 100644 --- a/control-plane/subcommand/partition-init/command_ent_test.go +++ b/control-plane/subcommand/partition-init/command_ent_test.go @@ -7,21 +7,14 @@ 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 +34,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 +61,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) - } - - testCases := map[string]testCase{ - "v2tenancy false": { - v2tenancy: false, - experiments: []string{}, - - preCreatePartition: func(testClient *test.TestServerClient) { - consul, err := api.NewClient(testClient.Cfg.APIClientConfig) - 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) + server, err := testutil.NewTestServerConfigT(t, nil) + require.NoError(t, err) + server.WaitForLeader(t) + defer server.Stop() - 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) + consul, err := api.NewClient(&api.Config{ + Address: server.HTTPAddr, + }) + 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) + // 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) - 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_test.go b/control-plane/subcommand/server-acl-init/anonymous_token_test.go index 41689a88c7..06327c3a91 100644 --- a/control-plane/subcommand/server-acl-init/anonymous_token_test.go +++ b/control-plane/subcommand/server-acl-init/anonymous_token_test.go @@ -15,7 +15,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 diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index cf9283f531..0d162b18b5 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -16,6 +16,11 @@ 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" @@ -26,12 +31,6 @@ 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 { @@ -44,8 +43,6 @@ type Command struct { flagResourcePrefix string flagK8sNamespace string - flagResourceAPIs bool // Use V2 APIs - flagAllowDNS bool flagSetServerTokens bool @@ -60,7 +57,6 @@ type Command struct { flagBindingRuleSelector string flagCreateEntLicenseToken bool - flagCreateDDAgentToken bool flagSnapshotAgent bool @@ -68,6 +64,8 @@ type Command struct { flagIngressGatewayNames []string flagTerminatingGatewayNames []string + flagAPIGatewayController bool + // Flags to configure Consul connection. flagServerPort uint @@ -135,9 +133,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, @@ -171,6 +166,8 @@ func (c *Command) init() { "Name of a terminating gateway that needs an acl token. May be specified multiple times. "+ "[Enterprise Only] If using Consul namespaces and registering the gateway outside of the "+ "default namespace, specify the value in the form ..") + c.flags.BoolVar(&c.flagAPIGatewayController, "api-gateway-controller", false, + "Toggle for configuring ACL login for the API gateway controller.") c.flags.UintVar(&c.flagServerPort, "server-port", 8500, "The HTTP or HTTPS port of the Consul server. Defaults to 8500.") @@ -209,15 +206,11 @@ func (c *Command) init() { 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 Kubernetes secret for the bootstrap token. This token must have `ac::write` permission "+ + "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 Kubernetes secret containing the bootstrap token.") - c.flags.BoolVar(&c.flagCreateDDAgentToken, "create-dd-agent-token", false, - "Enable ACL token creation for datadog agent integration"+ - "Configures the following permissions to grant datadog agent metrics scraping permissions with Consul ACLs enabled"+ - "agent_prefix \"\" {\n policy = \"read\"\n}\nservice_prefix \"\" {\n policy = \"read\"\n}\nnode_prefix \"\" {\n policy = \"read\"\n}") + "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") @@ -585,6 +578,28 @@ func (c *Command) Run(args []string) int { } } + if c.flagAPIGatewayController { + rules, err := c.apiGatewayControllerRules() + if err != nil { + c.log.Error("Error templating api gateway rules", "err", err) + return 1 + } + serviceAccountName := c.withPrefix("api-gateway-controller") + + // API gateways require a global policy/token because they must + // create config-entry resources in the primary, even when deployed + // to a secondary datacenter + authMethodName := localComponentAuthMethodName + if !primary { + authMethodName = globalComponentAuthMethodName + } + err = c.createACLPolicyRoleAndBindingRule("api-gateway-controller", rules, consulDC, primaryDC, globalPolicy, primary, authMethodName, serviceAccountName, dynamicClient) + if err != nil { + c.log.Error(err.Error()) + return 1 + } + } + if c.flagMeshGateway { rules, err := c.meshGatewayRules() if err != nil { @@ -659,20 +674,6 @@ func (c *Command) Run(args []string) int { } } - if c.flagCreateDDAgentToken { - var err error - rules, err := c.datadogAgentRules() - if err != nil { - c.log.Error("Error templating datadog agent metrics token rules", "err", err) - return 1 - } - err = c.createLocalACL(common.DatadogAgentTokenName, rules, consulDC, primary, dynamicClient) - if err != nil { - c.log.Error(err.Error()) - return 1 - } - } - c.log.Info("server-acl-init completed successfully") return 0 } 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..ceaad9c834 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -15,6 +15,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 +26,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 +33,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 +54,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 +70,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 +96,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 +112,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 +120,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 +143,6 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { cases := map[string]struct { MirroringPrefix string ExtraFlags []string - V2BindingRule bool }{ "no prefix": { MirroringPrefix: "", @@ -187,16 +158,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 +211,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) }) } } @@ -315,7 +275,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 +335,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 +351,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 +381,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 +397,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 +445,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 +574,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 +592,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 +643,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 +682,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 +936,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 +1023,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 +1183,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 +1257,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 c7bcb79384..d974e370c5 100644 --- a/control-plane/subcommand/server-acl-init/command_test.go +++ b/control-plane/subcommand/server-acl-init/command_test.go @@ -18,6 +18,10 @@ import ( "testing" "time" + "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" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" @@ -29,11 +33,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" @@ -104,7 +103,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. @@ -176,18 +175,10 @@ func TestRun_TokensPrimaryDC(t *testing.T) { SecretNames: []string{resourcePrefix + "-acl-replication-acl-token"}, LocalToken: false, }, - { - TestName: "Datadog Agent Token", - TokenFlags: []string{"-create-dd-agent-token"}, - PolicyNames: []string{"datadog-agent-metrics-token"}, - PolicyDCs: []string{"dc1"}, - SecretNames: []string{resourcePrefix + "-datadog-agent-metrics-acl-token"}, - LocalToken: true, - }, } 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. @@ -252,7 +243,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" @@ -333,14 +324,6 @@ func TestRun_TokensReplicatedDC(t *testing.T) { SecretNames: []string{resourcePrefix + "-enterprise-license-acl-token"}, LocalToken: true, }, - { - TestName: "Datadog Agent Token", - TokenFlags: []string{"-create-dd-agent-token"}, - PolicyNames: []string{"datadog-agent-metrics-token-dc2"}, - PolicyDCs: []string{"dc2"}, - SecretNames: []string{resourcePrefix + "-datadog-agent-metrics-acl-token"}, - LocalToken: true, - }, } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { @@ -417,12 +400,6 @@ func TestRun_TokensWithProvidedBootstrapToken(t *testing.T) { PolicyNames: []string{"acl-replication-token"}, SecretNames: []string{resourcePrefix + "-acl-replication-acl-token"}, }, - { - TestName: "Datadog Agent Token", - TokenFlags: []string{"-create-dd-agent-token"}, - PolicyNames: []string{"datadog-agent-metrics-token"}, - SecretNames: []string{resourcePrefix + "-datadog-agent-metrics-acl-token"}, - }, } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { @@ -528,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, false) + k8s, testClient = completeSetup(t) consulHTTPAddr = testClient.TestServer.HTTPAddr consulGRPCAddr = testClient.TestServer.GRPCAddr } @@ -603,9 +580,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"}, @@ -618,16 +594,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. @@ -670,15 +641,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. @@ -701,7 +665,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() @@ -782,86 +746,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{ @@ -876,7 +761,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, @@ -902,14 +786,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. @@ -929,21 +812,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() @@ -1038,12 +920,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{ @@ -1089,7 +965,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{ @@ -1130,11 +1006,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) @@ -1245,7 +1120,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) @@ -1501,7 +1376,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", @@ -1650,7 +1525,7 @@ func TestRun_AlreadyBootstrapped(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(cmdArgs) @@ -1835,7 +1710,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } responseCode := cmd.Run([]string{ @@ -1867,7 +1742,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{ @@ -1879,7 +1754,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{ @@ -2019,7 +1894,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) @@ -2070,6 +1945,12 @@ func TestRun_PoliciesAndBindingRulesForACLLogin_PrimaryDatacenter(t *testing.T) PolicyNames: []string{"sync-catalog-policy"}, Roles: []string{resourcePrefix + "-sync-catalog-acl-role"}, }, + { + TestName: "API Gateway Controller", + TokenFlags: []string{"-api-gateway-controller"}, + PolicyNames: []string{"api-gateway-controller-policy"}, + Roles: []string{resourcePrefix + "-api-gateway-controller-acl-role"}, + }, { TestName: "Snapshot Agent", TokenFlags: []string{"-snapshot-agent"}, @@ -2115,7 +1996,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. @@ -2217,6 +2098,13 @@ func TestRun_PoliciesAndBindingRulesACLLogin_SecondaryDatacenter(t *testing.T) { Roles: []string{resourcePrefix + "-sync-catalog-acl-role-" + secondaryDatacenter}, GlobalAuthMethod: false, }, + { + TestName: "API Gateway Controller", + TokenFlags: []string{"-api-gateway-controller"}, + PolicyNames: []string{"api-gateway-controller-policy-" + secondaryDatacenter}, + Roles: []string{resourcePrefix + "-api-gateway-controller-acl-role-" + secondaryDatacenter}, + GlobalAuthMethod: true, + }, { TestName: "Snapshot Agent", TokenFlags: []string{"-snapshot-agent"}, @@ -2368,6 +2256,12 @@ func TestRun_ValidateLoginToken_PrimaryDatacenter(t *testing.T) { Roles: []string{resourcePrefix + "-sync-catalog-acl-role"}, GlobalToken: false, }, + { + ComponentName: "api-gateway-controller", + TokenFlags: []string{"-api-gateway-controller"}, + Roles: []string{resourcePrefix + "-api-gateway-controller-acl-role"}, + GlobalToken: false, + }, { ComponentName: "snapshot-agent", TokenFlags: []string{"-snapshot-agent"}, @@ -2416,7 +2310,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) { @@ -2499,6 +2393,13 @@ func TestRun_ValidateLoginToken_SecondaryDatacenter(t *testing.T) { GlobalAuthMethod: false, GlobalToken: false, }, + { + ComponentName: "api-gateway-controller", + TokenFlags: []string{"-api-gateway-controller"}, + Roles: []string{resourcePrefix + "-api-gateway-controller-acl-role-dc2"}, + GlobalAuthMethod: true, + GlobalToken: true, + }, { ComponentName: "snapshot-agent", TokenFlags: []string{"-snapshot-agent"}, @@ -2625,7 +2526,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. @@ -2701,20 +2602,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..dc3cd870f0 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject.go +++ b/control-plane/subcommand/server-acl-init/connect_inject.go @@ -6,12 +6,11 @@ package serveraclinit import ( "fmt" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "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 @@ -78,27 +77,14 @@ func (c *Command) configureConnectInjectAuthMethod(client *consul.DynamicClient, 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) 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..03e47c8ba6 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject_test.go +++ b/control-plane/subcommand/server-acl-init/connect_inject_test.go @@ -7,13 +7,12 @@ 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/k8s_secrets_backend.go b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go index e4c5d78e38..93d9d0d2d8 100644 --- a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go +++ b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go @@ -7,12 +7,11 @@ 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.go b/control-plane/subcommand/server-acl-init/rules.go index 1f00e1019c..d86dd38a0a 100644 --- a/control-plane/subcommand/server-acl-init/rules.go +++ b/control-plane/subcommand/server-acl-init/rules.go @@ -145,6 +145,38 @@ partition_prefix "" { return c.renderRules(anonTokenRulesTpl) } +func (c *Command) apiGatewayControllerRules() (string, error) { + apiGatewayRulesTpl := `{{- if .EnablePartitions }} +partition "{{ .PartitionName }}" { + mesh = "write" + acl = "write" +{{- else }} +operator = "write" +acl = "write" +{{- end }} + +{{- if .EnableNamespaces }} +namespace_prefix "" { + policy = "write" +{{- end }} + service_prefix "" { + policy = "write" + intentions = "write" + } + node_prefix "" { + policy = "read" + } +{{- if .EnableNamespaces }} +} +{{- end }} +{{- if .EnablePartitions }} +} +{{- end }} +` + + return c.renderRules(apiGatewayRulesTpl) +} + // This assumes users are using the default name for the service, i.e. // "mesh-gateway". func (c *Command) meshGatewayRules() (string, error) { @@ -289,16 +321,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 }} @@ -319,10 +350,6 @@ partition "{{ .PartitionName }}" { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } {{- if .EnableNamespaces }} } {{- end }} @@ -369,32 +396,6 @@ partition "default" { return c.renderRules(aclReplicationRulesTpl) } -func (c *Command) datadogAgentRules() (string, error) { - ddAgentRulesTpl := `{{- if .EnablePartitions }} -partition "{{ .PartitionName }}" { -{{- end }} - agent_prefix "" { - policy = "read" - } - node_prefix "" { - policy = "read" - } -{{- if .EnableNamespaces }} - namespace_prefix "" { -{{- end }} - service_prefix "" { - policy = "read" - } -{{- if .EnableNamespaces }} - } -{{- end }} -{{- if .EnablePartitions }} -} -{{- end }} -` - return c.renderRules(ddAgentRulesTpl) -} - func (c *Command) rulesData() rulesData { return rulesData{ EnablePartitions: c.consulFlags.Partition != "", diff --git a/control-plane/subcommand/server-acl-init/rules_test.go b/control-plane/subcommand/server-acl-init/rules_test.go index bb727968f3..1e629d68f7 100644 --- a/control-plane/subcommand/server-acl-init/rules_test.go +++ b/control-plane/subcommand/server-acl-init/rules_test.go @@ -5,11 +5,11 @@ package serveraclinit import ( "fmt" + "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) { @@ -142,6 +142,82 @@ partition_prefix "" { } } +func TestAPIGatewayControllerRules(t *testing.T) { + cases := []struct { + Name string + EnableNamespaces bool + Partition string + Expected string + }{ + { + Name: "Namespaces are disabled", + Expected: ` +operator = "write" +acl = "write" + service_prefix "" { + policy = "write" + intentions = "write" + } + node_prefix "" { + policy = "read" + }`, + }, + { + Name: "Namespaces are enabled", + EnableNamespaces: true, + Expected: ` +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" + } + node_prefix "" { + policy = "read" + } +} +}`, + }, + } + + for _, tt := range cases { + t.Run(tt.Name, func(t *testing.T) { + cmd := Command{ + flagEnableNamespaces: tt.EnableNamespaces, + consulFlags: &flags.ConsulFlags{ + Partition: tt.Partition, + }, + } + + meshGatewayRules, err := cmd.apiGatewayControllerRules() + + require.NoError(t, err) + require.Equal(t, tt.Expected, strings.Trim(meshGatewayRules, " ")) + }) + } +} + func TestMeshGatewayRules(t *testing.T) { cases := []struct { Name string @@ -877,7 +953,6 @@ func TestInjectRules(t *testing.T) { EnablePartitions: false, EnablePeering: false, Expected: ` - mesh = "write" operator = "write" acl = "write" node_prefix "" { @@ -887,10 +962,6 @@ func TestInjectRules(t *testing.T) { service_prefix "" { policy = "write" intentions = "write" - } - identity_prefix "" { - policy = "write" - intentions = "write" }`, }, { @@ -898,7 +969,6 @@ func TestInjectRules(t *testing.T) { EnablePartitions: false, EnablePeering: false, Expected: ` - mesh = "write" operator = "write" acl = "write" node_prefix "" { @@ -910,10 +980,6 @@ func TestInjectRules(t *testing.T) { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } }`, }, { @@ -921,7 +987,6 @@ func TestInjectRules(t *testing.T) { EnablePartitions: false, EnablePeering: true, Expected: ` - mesh = "write" operator = "write" acl = "write" peering = "write" @@ -934,10 +999,6 @@ func TestInjectRules(t *testing.T) { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } }`, }, { @@ -959,10 +1020,6 @@ partition "part-1" { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } } }`, }, @@ -986,10 +1043,6 @@ partition "part-1" { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } } }`, }, diff --git a/control-plane/subcommand/sync-catalog/command.go b/control-plane/subcommand/sync-catalog/command.go index e461121f3d..2dadf6e039 100644 --- a/control-plane/subcommand/sync-catalog/command.go +++ b/control-plane/subcommand/sync-catalog/command.go @@ -16,13 +16,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 +23,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..fb6c6c4347 100644 --- a/control-plane/subcommand/sync-catalog/command_ent_test.go +++ b/control-plane/subcommand/sync-catalog/command_ent_test.go @@ -13,6 +13,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 +23,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..0223931cc1 100644 --- a/control-plane/subcommand/sync-catalog/command_test.go +++ b/control-plane/subcommand/sync-catalog/command_test.go @@ -11,6 +11,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 +19,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 +556,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..c2498a3125 100644 --- a/control-plane/subcommand/tls-init/command.go +++ b/control-plane/subcommand/tls-init/command.go @@ -13,17 +13,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/webhook-cert-manager/command.go b/control-plane/subcommand/webhook-cert-manager/command.go index ae9d75d29e..4d85565b62 100644 --- a/control-plane/subcommand/webhook-cert-manager/command.go +++ b/control-plane/subcommand/webhook-cert-manager/command.go @@ -17,6 +17,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 +29,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 +266,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 +309,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 +320,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{}) + webhookCfg, 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{}) - 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 +344,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 +387,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..31c98b0ebe 100644 --- a/control-plane/subcommand/webhook-cert-manager/command_test.go +++ b/control-plane/subcommand/webhook-cert-manager/command_test.go @@ -10,6 +10,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 +22,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 +701,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/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 deleted file mode 100644 index f68d1632a6..0000000000 --- a/control-plane/version/version.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package version - -import ( - "fmt" - "strings" -) - -var ( - // The git commit that was compiled. These will be filled in by the compiler. - GitCommit string - GitDescribe string - - // The main version number that is being run at the moment. - // - // Version must conform to the format expected by - // github.com/hashicorp/go-version for tests to work. - 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 - // such as "dev" (in development), "beta", "rc1", etc. - VersionPrerelease = "dev" -) - -// GetHumanVersion composes the parts of the version in a way that's suitable -// for displaying to humans. -func GetHumanVersion() string { - version := Version - if GitDescribe != "" { - version = GitDescribe - } - version = fmt.Sprintf("v%s", version) - - release := VersionPrerelease - if GitDescribe == "" && release == "" { - release = "dev" - } - - if IsFIPS() { - version += "+fips1402" - } - - if 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) - } - if GitCommit != "" { - version += fmt.Sprintf(" (%s)", GitCommit) - } - } - - // Strip off any single quotes added by the git information. - return strings.Replace(version, "'", "", -1) -} 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 a93c8b6abf..4eb706c666 100644 --- a/hack/aws-acceptance-test-cleanup/go.mod +++ b/hack/aws-acceptance-test-cleanup/go.mod @@ -8,8 +8,7 @@ require ( ) require ( - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/hack/aws-acceptance-test-cleanup/go.sum b/hack/aws-acceptance-test-cleanup/go.sum index 30e9f893b4..e5dd422f19 100644 --- a/hack/aws-acceptance-test-cleanup/go.sum +++ b/hack/aws-acceptance-test-cleanup/go.sum @@ -3,16 +3,15 @@ github.com/aws/aws-sdk-go v1.38.63/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/davecgh/go-spew v1.1.0/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/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/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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/hack/aws-acceptance-test-cleanup/main.go b/hack/aws-acceptance-test-cleanup/main.go index e8e7b258e7..d62e5e4405 100644 --- a/hack/aws-acceptance-test-cleanup/main.go +++ b/hack/aws-acceptance-test-cleanup/main.go @@ -25,7 +25,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 +38,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 +68,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. @@ -196,7 +90,7 @@ func realMain(ctx context.Context) error { if err != nil { return err } - toDeleteVPCs = append(toDeleteVPCs, vpcsOutput.Vpcs...) + toDeleteVPCs = append(vpcsOutput.Vpcs) nextToken = vpcsOutput.NextToken if nextToken == nil { break @@ -366,11 +260,6 @@ func realMain(ctx context.Context) error { }, }, }) - - if err != nil { - return err - } - vpcPeeringConnectionsToDelete := append(vpcPeeringConnectionsWithAcceptor.VpcPeeringConnections, vpcPeeringConnectionsWithRequester.VpcPeeringConnections...) // Delete NAT gateways. @@ -382,11 +271,9 @@ func realMain(ctx context.Context) error { }, }, }) - if err != nil { return err } - for _, gateway := range natGateways.NatGateways { fmt.Printf("NAT gateway: Destroying... [id=%s]\n", *gateway.NatGatewayId) _, err = ec2Client.DeleteNatGatewayWithContext(ctx, &ec2.DeleteNatGatewayInput{ @@ -491,11 +378,6 @@ func realMain(ctx context.Context) error { }, }, }) - - if err != nil { - return err - } - for _, igw := range igws.InternetGateways { fmt.Printf("Internet gateway: Detaching from VPC... [id=%s]\n", *igw.InternetGatewayId) if err := destroyBackoff(ctx, "Internet Gateway", *igw.InternetGatewayId, func() error { @@ -523,37 +405,6 @@ func realMain(ctx context.Context) error { fmt.Printf("Internet gateway: Destroyed [id=%s]\n", *igw.InternetGatewayId) } - // Delete network interfaces - networkInterfaces, err := ec2Client.DescribeNetworkInterfacesWithContext(ctx, &ec2.DescribeNetworkInterfacesInput{ - Filters: []*ec2.Filter{ - { - Name: aws.String("vpc-id"), - Values: []*string{vpcID}, - }, - }, - }) - - if err != nil { - return err - } - - for _, networkInterface := range networkInterfaces.NetworkInterfaces { - fmt.Printf("Network Interface: Destroying... [id=%s]\n", *networkInterface.NetworkInterfaceId) - if err := destroyBackoff(ctx, "Network Interface", *networkInterface.NetworkInterfaceId, func() error { - _, err := ec2Client.DeleteNetworkInterfaceWithContext(ctx, &ec2.DeleteNetworkInterfaceInput{ - NetworkInterfaceId: networkInterface.NetworkInterfaceId, - }) - if err != nil { - return err - } - return nil - }); err != nil { - return err - } - - fmt.Printf("Network interface: Destroyed [id=%s]\n", *networkInterface.NetworkInterfaceId) - } - // Delete subnets. subnets, err := ec2Client.DescribeSubnetsWithContext(ctx, &ec2.DescribeSubnetsInput{ Filters: []*ec2.Filter{ @@ -563,11 +414,6 @@ func realMain(ctx context.Context) error { }, }, }) - - if err != nil { - return err - } - for _, subnet := range subnets.Subnets { fmt.Printf("Subnet: Destroying... [id=%s]\n", *subnet.SubnetId) if err := destroyBackoff(ctx, "Subnet", *subnet.SubnetId, func() error { @@ -595,11 +441,6 @@ func realMain(ctx context.Context) error { }, }, }) - - if err != nil { - return err - } - for _, routeTable := range routeTables.RouteTables { // Find out if this is the main route table. var mainRouteTable bool @@ -633,11 +474,6 @@ func realMain(ctx context.Context) error { }, }, }) - - if err != nil { - return err - } - for _, sg := range sgs.SecurityGroups { if len(sg.IpPermissions) > 0 { revokeSGInput := &ec2.RevokeSecurityGroupIngressInput{GroupId: sg.GroupId} @@ -701,25 +537,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 9c883c920f..0000000000 --- a/hack/camel-crds/go.mod +++ /dev/null @@ -1,16 +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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - 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 74383f1209..0000000000 --- a/hack/camel-crds/go.sum +++ /dev/null @@ -1,26 +0,0 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -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/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/main.go b/hack/copy-crds-to-chart/main.go index 7835a6244a..8f7953a446 100644 --- a/hack/copy-crds-to-chart/main.go +++ b/hack/copy-crds-to-chart/main.go @@ -18,13 +18,6 @@ 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() { @@ -42,7 +35,7 @@ func main() { func realMain(helmPath string) error { root := "../../control-plane/config/crd/" - dirs := []string{"bases", "external"} + dirs := []string{"bases"} for _, dir := range dirs { err := filepath.Walk(root+dir, func(path string, info os.FileInfo, err error) error { @@ -88,27 +81,21 @@ func realMain(helmPath string) error { } var split int if dir == "bases" { - split = 6 + split = 8 } else { split = 9 } 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(), "_") - filenameSplit = strings.Split(filenameSplit[1], ".") - crdName = filenameSplit[0] + suffix + ".yaml" + crdName = filenameSplit[1] } else if dir == "external" { filenameSplit := strings.Split(info.Name(), ".") - crdName = filenameSplit[0] + suffix + "-external.yaml" + crdName = filenameSplit[0] + "-external.yaml" } destinationPath := filepath.Join(helmPath, "templates", fmt.Sprintf("crd-%s", crdName)) diff --git a/hack/helm-reference-gen/go.mod b/hack/helm-reference-gen/go.mod index 5ea709a3fb..f4f090fe2f 100644 --- a/hack/helm-reference-gen/go.mod +++ b/hack/helm-reference-gen/go.mod @@ -8,9 +8,9 @@ require ( ) require ( - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/kr/pretty v0.3.0 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/hack/helm-reference-gen/go.sum b/hack/helm-reference-gen/go.sum index 55d1f80848..bcc7d84843 100644 --- a/hack/helm-reference-gen/go.sum +++ b/hack/helm-reference-gen/go.sum @@ -1,25 +1,18 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -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/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/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/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/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/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= +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/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/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hack/helm-reference-gen/main.go b/hack/helm-reference-gen/main.go index 0bc623cff0..fca66d3c06 100644 --- a/hack/helm-reference-gen/main.go +++ b/hack/helm-reference-gen/main.go @@ -162,14 +162,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/version/go.mod b/version/go.mod new file mode 100644 index 0000000000..b3772ec6bd --- /dev/null +++ b/version/go.mod @@ -0,0 +1,3 @@ +module github.com/hashicorp/consul-k8s/version + +go 1.20 diff --git a/cli/version/version.go b/version/version.go similarity index 92% rename from cli/version/version.go rename to version/version.go index f68d1632a6..9d640f22e3 100644 --- a/cli/version/version.go +++ b/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.5.0" + Version = "1.1.15" // 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 +39,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) }