diff --git a/.circleci/config.yml b/.circleci/config.yml index 2871bde9..09c7c372 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -145,8 +145,8 @@ jobs: -kubecontext="kind-dc1" \ -secondary-kubecontext="kind-dc2" \ -debug-directory="$TEST_RESULTS/debug" \ - -consul-image="hashicorp/consul-enterprise:1.10.0-ent-beta1" \ - -consul-k8s-image="ashwinvenkatesh/consul-k8s@sha256:2b2a52ddebf7226b7aae7f66c41f903b0053deb0a3c1b88b5b9cdf063deb530e" + -consul-image="ashwinvenkatesh/consul@sha256:a4b48ff4f60f5ea4cf408b838e7617bb6b652ec188faa31474ded8aa60cd23b4" \ + -consul-k8s-image="ashwinvenkatesh/consul-k8s@sha256:b9b39ee28e2d769341253ee07ee3ef993f301e3d2c1634e6bc2f9ec47efc5863" then echo "Tests in ${pkg} failed, aborting early" exit_code=1 @@ -225,7 +225,7 @@ jobs: -secondary-kubeconfig="$secondary_kubeconfig" \ -debug-directory="$TEST_RESULTS/debug" \ -consul-image="hashicorp/consul-enterprise:1.10.0-ent-beta1" \ - -consul-k8s-image="docker.mirror.hashicorp.services/hashicorpdev/consul-k8s:latest" + -consul-k8s-image="ashwinvenkatesh/consul-k8s@sha256:b184e2e9d2cdd761241957fdd3a2d3580d9d232b4b852e4326b360447aed6280" - store_test_results: path: /tmp/test-results diff --git a/templates/controller-clusterrole.yaml b/templates/controller-clusterrole.yaml index 57c51ab1..b113366c 100644 --- a/templates/controller-clusterrole.yaml +++ b/templates/controller-clusterrole.yaml @@ -16,6 +16,7 @@ rules: - servicedefaults - serviceresolvers - proxydefaults + - meshes - servicerouters - servicesplitters - serviceintentions @@ -35,6 +36,7 @@ rules: - servicedefaults/status - serviceresolvers/status - proxydefaults/status + - meshes/status - servicerouters/status - servicesplitters/status - serviceintentions/status diff --git a/templates/controller-mutatingwebhookconfiguration.yaml b/templates/controller-mutatingwebhookconfiguration.yaml index 141dbe0b..be1be9c1 100644 --- a/templates/controller-mutatingwebhookconfiguration.yaml +++ b/templates/controller-mutatingwebhookconfiguration.yaml @@ -32,6 +32,28 @@ webhooks: resources: - proxydefaults sideEffects: None +- clientConfig: + caBundle: Cg== + service: + name: {{ template "consul.fullname" . }}-controller-webhook + namespace: {{ .Release.Namespace }} + path: /mutate-v1alpha1-mesh + failurePolicy: Fail + admissionReviewVersions: + - "v1beta1" + - "v1" + name: mutate-mesh.consul.hashicorp.com + rules: + - apiGroups: + - consul.hashicorp.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - meshes + sideEffects: None - clientConfig: caBundle: Cg== service: diff --git a/templates/crd-meshes.yaml b/templates/crd-meshes.yaml new file mode 100644 index 00000000..bae7d7cd --- /dev/null +++ b/templates/crd-meshes.yaml @@ -0,0 +1,104 @@ +{{- if .Values.controller.enabled }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.5.0 + creationTimestamp: null + name: meshes.consul.hashicorp.com + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: crd +spec: + group: consul.hashicorp.com + names: + kind: Mesh + listKind: MeshList + plural: meshes + singular: mesh + 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: 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' + 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: MeshSpec defines the desired state of Mesh + properties: + transparentProxy: + properties: + catalogDestinationsOnly: + type: boolean + 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: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end }} diff --git a/test/acceptance/go.mod b/test/acceptance/go.mod index 4e0d75a5..b09a5ad3 100644 --- a/test/acceptance/go.mod +++ b/test/acceptance/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( github.com/gruntwork-io/terratest v0.31.2 - github.com/hashicorp/consul/api v1.4.1-0.20210415000851-62fcf1ff17cd + github.com/hashicorp/consul/api v1.4.1-0.20210428221329-078c40425f85 github.com/hashicorp/consul/sdk v0.7.0 github.com/stretchr/testify v1.5.1 gopkg.in/yaml.v2 v2.2.8 diff --git a/test/acceptance/go.sum b/test/acceptance/go.sum index 5d3fd747..7ec5426e 100644 --- a/test/acceptance/go.sum +++ b/test/acceptance/go.sum @@ -225,8 +225,8 @@ github.com/gruntwork-io/gruntwork-cli v0.7.0 h1:YgSAmfCj9c61H+zuvHwKfYUwlMhu5arn github.com/gruntwork-io/gruntwork-cli v0.7.0/go.mod h1:jp6Z7NcLF2avpY8v71fBx6hds9eOFPELSuD/VPv7w00= github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+XbVVtytUHg= github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= -github.com/hashicorp/consul/api v1.4.1-0.20210415000851-62fcf1ff17cd h1:HKntL2binKSK5AFIzsJ9WmfPwjUTRgT5/mCifKVpPt8= -github.com/hashicorp/consul/api v1.4.1-0.20210415000851-62fcf1ff17cd/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= +github.com/hashicorp/consul/api v1.4.1-0.20210428221329-078c40425f85 h1:RwN0V6dAmVBi8FlzKLNpCD9LlpTJg18c/hM5YfSTj8I= +github.com/hashicorp/consul/api v1.4.1-0.20210428221329-078c40425f85/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= github.com/hashicorp/consul/sdk v0.7.0 h1:H6R9d008jDcHPQPAqPNuydAshJ4v5/8URdFnUvK/+sc= github.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= diff --git a/test/acceptance/tests/controller/controller_namespaces_test.go b/test/acceptance/tests/controller/controller_namespaces_test.go index 9c4c0784..a6ea02f2 100644 --- a/test/acceptance/tests/controller/controller_namespaces_test.go +++ b/test/acceptance/tests/controller/controller_namespaces_test.go @@ -153,6 +153,13 @@ func TestControllerNamespaces(t *testing.T) { require.True(r, ok, "could not cast to ProxyConfigEntry") require.Equal(r, api.MeshGatewayModeLocal, proxyDefaultEntry.MeshGateway.Mode) + // mesh + entry, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", defaultOpts) + require.NoError(r, err) + meshEntry, ok := entry.(*api.MeshConfigEntry) + require.True(r, ok, "could not cast to MeshConfigEntry") + require.True(r, meshEntry.TransparentProxy.CatalogDestinationsOnly) + // service-router entry, _, err = consulClient.ConfigEntries().Get(api.ServiceRouter, "router", queryOpts) require.NoError(r, err) @@ -213,6 +220,9 @@ 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 mesh custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "mesh", "mesh", "-p", fmt.Sprintf(`{"spec":{"transparentProxy":{"catalogDestinationsOnly": "%t"}}}`, false), "--type=merge") + logger.Log(t, "patching service-router custom resource") patchPathPrefix := "/baz" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "-n", KubeNS, "servicerouter", "router", "-p", fmt.Sprintf(`{"spec":{"routes":[{"match":{"http":{"pathPrefix":"%s"}}}]}}`, patchPathPrefix), "--type=merge") @@ -254,6 +264,13 @@ func TestControllerNamespaces(t *testing.T) { require.True(r, ok, "could not cast to ProxyConfigEntry") require.Equal(r, api.MeshGatewayModeRemote, proxyDefaultsEntry.MeshGateway.Mode) + // mesh + entry, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", defaultOpts) + require.NoError(r, err) + meshEntry, ok := entry.(*api.MeshConfigEntry) + require.True(r, ok, "could not cast to MeshConfigEntry") + require.False(r, meshEntry.TransparentProxy.CatalogDestinationsOnly) + // service-router entry, _, err = consulClient.ConfigEntries().Get(api.ServiceRouter, "router", queryOpts) require.NoError(r, err) @@ -304,6 +321,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 mesh custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "mesh", "mesh") + logger.Log(t, "deleting service-router custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "-n", KubeNS, "servicerouter", "router") @@ -336,6 +356,11 @@ func TestControllerNamespaces(t *testing.T) { 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) + require.Contains(r, err.Error(), "404 (Config entry not found") + // service-router _, _, err = consulClient.ConfigEntries().Get(api.ServiceRouter, "router", queryOpts) require.Error(r, err) diff --git a/test/acceptance/tests/controller/controller_test.go b/test/acceptance/tests/controller/controller_test.go index 7c2272fc..56191e9a 100644 --- a/test/acceptance/tests/controller/controller_test.go +++ b/test/acceptance/tests/controller/controller_test.go @@ -101,6 +101,13 @@ func TestController(t *testing.T) { require.Equal(r, 22000, proxyDefaultEntry.Expose.Paths[0].ListenerPort) require.Equal(r, 8080, proxyDefaultEntry.Expose.Paths[0].LocalPathPort) + // mesh + entry, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", nil) + require.NoError(r, err) + meshConfigEntry, ok := entry.(*api.MeshConfigEntry) + require.True(r, ok, "could not cast to MeshConfigEntry") + require.True(r, meshConfigEntry.TransparentProxy.CatalogDestinationsOnly) + // service-router entry, _, err = consulClient.ConfigEntries().Get(api.ServiceRouter, "router", nil) require.NoError(r, err) @@ -162,6 +169,9 @@ func TestController(t *testing.T) { patchMeshGatewayMode := "remote" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "proxydefaults", "global", "-p", fmt.Sprintf(`{"spec":{"meshGateway":{"mode": "%s"}}}`, patchMeshGatewayMode), "--type=merge") + logger.Log(t, "patching mesh custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "mesh", "mesh", "-p", fmt.Sprintf(`{"spec":{"transparentProxy":{"catalogDestinationsOnly": "%t"}}}`, false), "--type=merge") + logger.Log(t, "patching service-router custom resource") patchPathPrefix := "/baz" k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "servicerouter", "router", "-p", fmt.Sprintf(`{"spec":{"routes":[{"match":{"http":{"pathPrefix":"%s"}}}]}}`, patchPathPrefix), "--type=merge") @@ -203,6 +213,13 @@ func TestController(t *testing.T) { require.True(r, ok, "could not cast to ProxyConfigEntry") require.Equal(r, api.MeshGatewayModeRemote, proxyDefaultsEntry.MeshGateway.Mode) + // mesh + entry, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", nil) + require.NoError(r, err) + meshEntry, ok := entry.(*api.MeshConfigEntry) + require.True(r, ok, "could not cast to MeshConfigEntry") + require.False(r, meshEntry.TransparentProxy.CatalogDestinationsOnly) + // service-router entry, _, err = consulClient.ConfigEntries().Get(api.ServiceRouter, "router", nil) require.NoError(r, err) @@ -254,6 +271,9 @@ func TestController(t *testing.T) { logger.Log(t, "deleting proxy-defaults custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "proxydefaults", "global") + logger.Log(t, "deleting mesh custom resource") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "mesh", "mesh") + logger.Log(t, "deleting service-router custom resource") k8s.RunKubectl(t, ctx.KubectlOptions(t), "delete", "servicerouter", "router") @@ -286,6 +306,11 @@ func TestController(t *testing.T) { require.Error(r, err) require.Contains(r, err.Error(), "404 (Config entry not found") + // mesh + _, _, err = consulClient.ConfigEntries().Get(api.MeshConfig, "mesh", nil) + require.Error(r, err) + require.Contains(r, err.Error(), "404 (Config entry not found") + // service-router _, _, err = consulClient.ConfigEntries().Get(api.ServiceRouter, "router", nil) require.Error(r, err) diff --git a/test/acceptance/tests/fixtures/crds/mesh.yaml b/test/acceptance/tests/fixtures/crds/mesh.yaml new file mode 100644 index 00000000..158cfc97 --- /dev/null +++ b/test/acceptance/tests/fixtures/crds/mesh.yaml @@ -0,0 +1,7 @@ +apiVersion: consul.hashicorp.com/v1alpha1 +kind: Mesh +metadata: + name: mesh +spec: + transparentProxy: + catalogDestinationsOnly: true \ No newline at end of file diff --git a/test/unit/crd-meshes.bats b/test/unit/crd-meshes.bats new file mode 100644 index 00000000..4ad7acf3 --- /dev/null +++ b/test/unit/crd-meshes.bats @@ -0,0 +1,24 @@ +#!/usr/bin/env bats + +load _helpers + +@test "mesh/CustomerResourceDefinition: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/crd-meshes.yaml \ + . +} + +@test "mesh/CustomerResourceDefinition: enabled with controller.enabled=true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/crd-meshes.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" ] +}