From e2d36c95f7da0f6468ead85e519d514589ca0d80 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Thu, 26 Nov 2020 14:33:03 -0800 Subject: [PATCH] Add support for ingress gateway CRD --- CHANGELOG.md | 20 ++- templates/controller-clusterrole.yaml | 2 + ...ntroller-mutatingwebhookconfiguration.yaml | 22 +++ templates/crd-ingressgateways.yaml | 132 ++++++++++++++++++ templates/crd-proxydefaults.yaml | 2 +- templates/crd-servicedefaults.yaml | 2 +- templates/crd-serviceintentions.yaml | 2 +- templates/crd-serviceresolvers.yaml | 57 +++----- templates/crd-servicerouters.yaml | 2 +- templates/crd-servicesplitters.yaml | 2 +- .../controller/controller_namespaces_test.go | 31 ++++ .../tests/controller/controller_test.go | 30 ++++ .../tests/fixtures/crds/ingressgateway.yaml | 10 ++ test/unit/crd-ingressgateways.bats | 24 ++++ 14 files changed, 287 insertions(+), 51 deletions(-) create mode 100644 templates/crd-ingressgateways.yaml create mode 100644 test/acceptance/tests/fixtures/crds/ingressgateway.yaml create mode 100644 test/unit/crd-ingressgateways.bats diff --git a/CHANGELOG.md b/CHANGELOG.md index cb02acfae..2d8d9bedc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,24 @@ ## Unreleased -BUG FIXES: -* Fix pod security policy when running mesh gateways in `hostNetwork` mode. [[GH-605](https://github.com/hashicorp/consul-helm/issues/605)] +BREAKING CHANGES: +* Setting `server.bootstrapExpect` to a value less than `server.replicas` will now + give an error. This was a misconfiguration as the servers wouldn't wait + until the proper number have started before electing a leader. [[GH-721](https://github.com/hashicorp/consul-helm/pull/721)] + +FEATURES: +* CRDs: add new CRD `IngressGateway` for configuring Consul's [ingress-gateway](https://www.consul.io/docs/agent/config-entries/ingress-gateway) config entry. [[GH-714](https://github.com/hashicorp/consul-helm/pull/714)] IMPROVEMENTS: * Make `server.bootstrapExpect` optional. If not set, will now default to `server.replicas`. If you're currently setting `server.replicas`, there is no effect. [[GH-721](https://github.com/hashicorp/consul-helm/pull/721)] -BREAKING CHANGES: -* Setting `server.bootstrapExpect` to a value less than `server.replicas` will now - give an error. This was a misconfiguration as the servers wouldn't wait - until the proper number have started before electing a leader. [[GH-721](https://github.com/hashicorp/consul-helm/pull/721)] +BUG FIXES: +* Fix pod security policy when running mesh gateways in `hostNetwork` mode. [[GH-605](https://github.com/hashicorp/consul-helm/issues/605)] +* CRDs: **(Consul Enterprise only)** change `ServiceResolver` field `failover[].namespaces` to `failover[].namespace`. + This will not affect existing `ServiceResolver` resources and will only update the documentation for that field. + + If `failover[].namespaces` was used previously, it was ignored and after this change it will still be ignored. + If `failover[].namespace` was used previously, it worked correctly and after this change it will still work correctly. [[GH-714](https://github.com/hashicorp/consul-helm/pull/714)] ## 0.27.0 (Nov 25, 2020) diff --git a/templates/controller-clusterrole.yaml b/templates/controller-clusterrole.yaml index 2e403d5fc..6d9d70726 100644 --- a/templates/controller-clusterrole.yaml +++ b/templates/controller-clusterrole.yaml @@ -19,6 +19,7 @@ rules: - servicerouters - servicesplitters - serviceintentions + - ingressgateways verbs: - create - delete @@ -36,6 +37,7 @@ rules: - servicerouters/status - servicesplitters/status - serviceintentions/status + - ingressgateways/status verbs: - get - patch diff --git a/templates/controller-mutatingwebhookconfiguration.yaml b/templates/controller-mutatingwebhookconfiguration.yaml index 560abe413..8024067ee 100644 --- a/templates/controller-mutatingwebhookconfiguration.yaml +++ b/templates/controller-mutatingwebhookconfiguration.yaml @@ -146,4 +146,26 @@ webhooks: resources: - serviceintentions sideEffects: None +- clientConfig: + caBundle: Cg== + service: + name: {{ template "consul.fullname" . }}-controller-webhook + namespace: {{ .Release.Namespace }} + path: /mutate-v1alpha1-ingressgateway + failurePolicy: Fail + admissionReviewVersions: + - "v1beta1" + - "v1" + name: mutate-ingressgateway.consul.hashicorp.com + rules: + - apiGroups: + - consul.hashicorp.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - ingressgateways + sideEffects: None {{- end }} diff --git a/templates/crd-ingressgateways.yaml b/templates/crd-ingressgateways.yaml new file mode 100644 index 000000000..0d73ff4b5 --- /dev/null +++ b/templates/crd-ingressgateways.yaml @@ -0,0 +1,132 @@ +{{- if .Values.controller.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: ingressgateways.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd +spec: + additionalPrinterColumns: + - JSONPath: .status.conditions[?(@.type=="Synced")].status + description: The sync status of the resource with Consul + name: Synced + type: string + - JSONPath: .metadata.creationTimestamp + description: The age of the resource + name: Age + type: date + group: consul.hashicorp.com + names: + kind: IngressGateway + listKind: IngressGatewayList + plural: ingressgateways + singular: ingressgateway + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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' + 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: IngressGatewaySpec defines the desired state of IngressGateway + properties: + listeners: + 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. + properties: + port: + description: Port declares the port on which the ingress gateway 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).' + type: string + services: + description: "Services declares the set of services to which the listener forwards traffic. \n 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. + 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." + items: + type: string + type: array + 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." + type: string + namespace: + description: Namespace is the namespace where the service is located. Namespacing is a Consul Enterprise feature. + type: string + type: object + type: array + type: object + type: array + tls: + description: TLS holds the TLS configuration for this gateway. + properties: + enabled: + description: Indicates that TLS should be enabled for this gateway service. + type: boolean + required: + - enabled + 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 + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/templates/crd-proxydefaults.yaml b/templates/crd-proxydefaults.yaml index 5fbda12b7..661e1a5f7 100644 --- a/templates/crd-proxydefaults.yaml +++ b/templates/crd-proxydefaults.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.0 + controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null name: proxydefaults.consul.hashicorp.com labels: diff --git a/templates/crd-servicedefaults.yaml b/templates/crd-servicedefaults.yaml index b39419d44..770fb618d 100644 --- a/templates/crd-servicedefaults.yaml +++ b/templates/crd-servicedefaults.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.0 + controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null name: servicedefaults.consul.hashicorp.com labels: diff --git a/templates/crd-serviceintentions.yaml b/templates/crd-serviceintentions.yaml index d747f9a6f..41bc34919 100644 --- a/templates/crd-serviceintentions.yaml +++ b/templates/crd-serviceintentions.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.0 + controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null name: serviceintentions.consul.hashicorp.com labels: diff --git a/templates/crd-serviceresolvers.yaml b/templates/crd-serviceresolvers.yaml index b5715b1bb..34c0582ad 100644 --- a/templates/crd-serviceresolvers.yaml +++ b/templates/crd-serviceresolvers.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.0 + controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null name: serviceresolvers.consul.hashicorp.com labels: @@ -62,7 +62,7 @@ spec: items: type: string type: array - namespaces: + 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 service: @@ -75,83 +75,60 @@ spec: 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: - description: CookieConfig contains configuration for the "cookie" - hash policy type. + description: CookieConfig contains configuration for the "cookie" hash policy type. properties: path: description: Path is the path to set for the cookie. type: string session: - description: Session generates a session cookie with no - expiration. + description: Session determines whether to generate a session cookie with no expiration. type: boolean ttl: - description: TTL is the ttl for generated cookies. Cannot - be specified for session cookies. + description: TTL is the ttl for generated cookies. Cannot be specified for session cookies. format: int64 type: integer 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. - Cannot be specified along with field or fieldValue. + 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 leastRequestConfig: - description: LeastRequestConfig contains configuration for the "leastRequest" - policy type. + description: LeastRequestConfig contains configuration for the "leastRequest" policy type. properties: choiceCount: - description: ChoiceCount determines the number of random healthy - hosts from which to select the one with the least requests. + description: ChoiceCount determines the number of random healthy hosts from which to select the one with the least requests. format: int32 type: integer type: object policy: - description: Policy is the load balancing policy used to select - a host. + description: Policy is the load balancing policy used to select a host. type: string ringHashConfig: - description: RingHashConfig contains configuration for the "ringHash" - policy type. + description: RingHashConfig contains configuration for the "ringHash" policy type. properties: maximumRingSize: - description: MaximumRingSize determines the maximum number of - entries in the hash ring. + description: MaximumRingSize determines the maximum number of entries in the hash ring. format: int64 type: integer minimumRingSize: - description: MinimumRingSize determines the minimum number of - entries in the hash ring. + description: MinimumRingSize determines the minimum number of entries in the hash ring. format: int64 type: integer type: object diff --git a/templates/crd-servicerouters.yaml b/templates/crd-servicerouters.yaml index 8ce9038dc..6760ce6ff 100644 --- a/templates/crd-servicerouters.yaml +++ b/templates/crd-servicerouters.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.0 + controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null name: servicerouters.consul.hashicorp.com labels: diff --git a/templates/crd-servicesplitters.yaml b/templates/crd-servicesplitters.yaml index 9cd2c071b..42df6acfb 100644 --- a/templates/crd-servicesplitters.yaml +++ b/templates/crd-servicesplitters.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.0 + controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null name: servicesplitters.consul.hashicorp.com labels: diff --git a/test/acceptance/tests/controller/controller_namespaces_test.go b/test/acceptance/tests/controller/controller_namespaces_test.go index c3e713484..50bd6fc4d 100644 --- a/test/acceptance/tests/controller/controller_namespaces_test.go +++ b/test/acceptance/tests/controller/controller_namespaces_test.go @@ -173,6 +173,18 @@ func TestControllerNamespaces(t *testing.T) { svcIntentions, ok := entry.(*api.ServiceIntentionsConfigEntry) require.True(r, ok, "could not cast to ServiceSplitterConfigEntry") require.Equal(r, api.IntentionActionAllow, svcIntentions.Sources[0].Action) + + // ingress-gateway + entry, _, err = consulClient.ConfigEntries().Get(api.IngressGateway, "ingress-gateway", queryOpts) + require.NoError(r, err) + ingressGatewayEntry, ok := entry.(*api.IngressGatewayConfigEntry) + require.True(r, ok, "could not cast to IngressGatewayConfigEntry") + require.Len(r, ingressGatewayEntry.Listeners, 1) + require.Equal(r, "tcp", ingressGatewayEntry.Listeners[0].Protocol) + require.Equal(r, 8080, ingressGatewayEntry.Listeners[0].Port) + require.Len(r, ingressGatewayEntry.Listeners[0].Services, 1) + require.Equal(r, "foo", ingressGatewayEntry.Listeners[0].Services[0].Name) + }) } @@ -200,6 +212,10 @@ func TestControllerNamespaces(t *testing.T) { logger.Log(t, "patching service-intentions custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "serviceintentions", "intentions", "-p", `{"spec": {"sources": [{"name": "svc2", "action": "deny"}]}}`, "--type=merge") + logger.Log(t, "patching ingress-gateway custom resource") + patchPort := 9090 + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "ingressgateway", "ingress-gateway", "-p", fmt.Sprintf(`{"spec": {"listeners": [{"port": %d, "protocol": "tcp", "services": [{"name": "foo"}]}]}}`, patchPort), "--type=merge") + counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -245,6 +261,13 @@ func TestControllerNamespaces(t *testing.T) { svcIntentions, ok := entry.(*api.ServiceIntentionsConfigEntry) require.True(r, ok, "could not cast to ServiceIntentionsConfigEntry") require.Equal(r, api.IntentionActionDeny, svcIntentions.Sources[0].Action) + + // ingress-gateway + entry, _, err = consulClient.ConfigEntries().Get(api.IngressGateway, "ingress-gateway", queryOpts) + require.NoError(r, err) + ingressGatewayEntry, ok := entry.(*api.IngressGatewayConfigEntry) + require.True(r, ok, "could not cast to IngressGatewayConfigEntry") + require.Equal(r, patchPort, ingressGatewayEntry.Listeners[0].Port) }) } @@ -268,6 +291,9 @@ func TestControllerNamespaces(t *testing.T) { logger.Log(t, "deleting service-intentions custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "serviceintentions", "intentions") + logger.Log(t, "deleting ingress-gateway custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "ingressgateway", "ingress-gateway") + counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -299,6 +325,11 @@ func TestControllerNamespaces(t *testing.T) { _, _, err = consulClient.ConfigEntries().Get(api.ServiceIntentions, IntentionName, queryOpts) require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") + + // ingress-gateway + _, _, err = consulClient.ConfigEntries().Get(api.IngressGateway, "ingress-gateway", queryOpts) + require.Error(r, err) + require.Contains(r, err.Error(), "404 (Config entry not found") }) } }) diff --git a/test/acceptance/tests/controller/controller_test.go b/test/acceptance/tests/controller/controller_test.go index 6d510b736..e04f3f46b 100644 --- a/test/acceptance/tests/controller/controller_test.go +++ b/test/acceptance/tests/controller/controller_test.go @@ -114,6 +114,17 @@ func TestController(t *testing.T) { require.True(r, ok, "could not cast to ServiceIntentionsConfigEntry") require.Equal(r, api.IntentionActionAllow, svcIntentionsEntry.Sources[0].Action) require.Equal(r, api.IntentionActionAllow, svcIntentionsEntry.Sources[1].Permissions[0].Action) + + // ingress-gateway + entry, _, err = consulClient.ConfigEntries().Get(api.IngressGateway, "ingress-gateway", nil) + require.NoError(r, err) + ingressGatewayEntry, ok := entry.(*api.IngressGatewayConfigEntry) + require.True(r, ok, "could not cast to IngressGatewayConfigEntry") + require.Len(r, ingressGatewayEntry.Listeners, 1) + require.Equal(r, "tcp", ingressGatewayEntry.Listeners[0].Protocol) + require.Equal(r, 8080, ingressGatewayEntry.Listeners[0].Port) + require.Len(r, ingressGatewayEntry.Listeners[0].Services, 1) + require.Equal(r, "foo", ingressGatewayEntry.Listeners[0].Services[0].Name) }) } @@ -141,6 +152,10 @@ func TestController(t *testing.T) { logger.Log(t, "patching service-intentions custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "serviceintentions", "intentions", "-p", `{"spec": {"sources": [{"name": "svc2", "action": "deny"}, {"name": "svc3", "permissions": [{"action": "deny", "http": {"pathExact": "/foo", "methods": ["GET", "PUT"]}}]}]}}`, "--type=merge") + logger.Log(t, "patching ingress-gateway custom resource") + patchPort := 9090 + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "ingressgateway", "ingress-gateway", "-p", fmt.Sprintf(`{"spec": {"listeners": [{"port": %d, "protocol": "tcp", "services": [{"name": "foo"}]}]}}`, patchPort), "--type=merge") + counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -187,6 +202,13 @@ func TestController(t *testing.T) { require.True(r, ok, "could not cast to ServiceIntentionsConfigEntry") require.Equal(r, api.IntentionActionDeny, svcIntentions.Sources[0].Action) require.Equal(r, api.IntentionActionDeny, svcIntentions.Sources[1].Permissions[0].Action) + + // ingress-gateway + entry, _, err = consulClient.ConfigEntries().Get(api.IngressGateway, "ingress-gateway", nil) + require.NoError(r, err) + ingressGatewayEntry, ok := entry.(*api.IngressGatewayConfigEntry) + require.True(r, ok, "could not cast to IngressGatewayConfigEntry") + require.Equal(r, patchPort, ingressGatewayEntry.Listeners[0].Port) }) } @@ -210,6 +232,9 @@ func TestController(t *testing.T) { logger.Log(t, "deleting service-intentions custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "serviceintentions", "intentions") + logger.Log(t, "deleting ingress-gateway custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "ingressgateway", "ingress-gateway") + counter := &retry.Counter{Count: 10, Wait: 500 * time.Millisecond} retry.RunWith(counter, t, func(r *retry.R) { // service-defaults @@ -241,6 +266,11 @@ func TestController(t *testing.T) { _, _, err = consulClient.ConfigEntries().Get(api.ServiceIntentions, IntentionName, nil) require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") + + // ingress-gateway + _, _, err = consulClient.ConfigEntries().Get(api.IngressGateway, "ingress-gateway", nil) + require.Error(r, err) + require.Contains(r, err.Error(), "404 (Config entry not found") }) } }) diff --git a/test/acceptance/tests/fixtures/crds/ingressgateway.yaml b/test/acceptance/tests/fixtures/crds/ingressgateway.yaml new file mode 100644 index 000000000..d613dc217 --- /dev/null +++ b/test/acceptance/tests/fixtures/crds/ingressgateway.yaml @@ -0,0 +1,10 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: IngressGateway +metadata: + name: ingress-gateway +spec: + listeners: + - port: 8080 + protocol: "tcp" + services: + - name: "foo" diff --git a/test/unit/crd-ingressgateways.bats b/test/unit/crd-ingressgateways.bats new file mode 100644 index 000000000..315a22c8e --- /dev/null +++ b/test/unit/crd-ingressgateways.bats @@ -0,0 +1,24 @@ +#!/usr/bin/env bats + +load _helpers + +@test "ingressGateway/CustomerResourceDefinition: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-ingressgateways.yaml \ + . +} + +@test "ingressGateway/CustomerResourceDefinition: enabled with controller.enabled=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-ingressgateways.yaml \ + --set 'controller.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" ] +}