diff --git a/api/v1alpha1/proxydefaults_webhook.go b/api/v1alpha1/proxydefaults_webhook.go index 58ab53b67e..18f0a17b01 100644 --- a/api/v1alpha1/proxydefaults_webhook.go +++ b/api/v1alpha1/proxydefaults_webhook.go @@ -15,7 +15,7 @@ import ( // +kubebuilder:object:generate=false -type ProxyDefaultsValidator struct { +type ProxyDefaultsWebhook struct { client.Client ConsulClient *capi.Client Logger logr.Logger @@ -33,7 +33,7 @@ type ProxyDefaultsValidator struct { // // +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-proxydefaults,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=proxydefaults,versions=v1alpha1,name=mutate-proxydefaults.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None -func (v *ProxyDefaultsValidator) Handle(ctx context.Context, req admission.Request) admission.Response { +func (v *ProxyDefaultsWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { var proxyDefaults ProxyDefaults var proxyDefaultsList ProxyDefaultsList err := v.decoder.Decode(req, &proxyDefaults) @@ -67,7 +67,7 @@ func (v *ProxyDefaultsValidator) Handle(ctx context.Context, req admission.Reque return admission.Allowed(fmt.Sprintf("valid %s request", proxyDefaults.KubeKind())) } -func (v *ProxyDefaultsValidator) InjectDecoder(d *admission.Decoder) error { +func (v *ProxyDefaultsWebhook) InjectDecoder(d *admission.Decoder) error { v.decoder = d return nil } diff --git a/api/v1alpha1/proxydefaults_webhook_test.go b/api/v1alpha1/proxydefaults_webhook_test.go index 24b19d1861..538f208b00 100644 --- a/api/v1alpha1/proxydefaults_webhook_test.go +++ b/api/v1alpha1/proxydefaults_webhook_test.go @@ -89,7 +89,7 @@ func TestValidateConfigEntry(t *testing.T) { decoder, err := admission.NewDecoder(s) require.NoError(t, err) - validator := &ProxyDefaultsValidator{ + validator := &ProxyDefaultsWebhook{ Client: client, ConsulClient: nil, Logger: logrtest.TestLogger{T: t}, diff --git a/api/v1alpha1/servicedefaults_webhook.go b/api/v1alpha1/servicedefaults_webhook.go index ab760fcaf0..e45e8e5ad2 100644 --- a/api/v1alpha1/servicedefaults_webhook.go +++ b/api/v1alpha1/servicedefaults_webhook.go @@ -13,7 +13,7 @@ import ( // +kubebuilder:object:generate=false -type ServiceDefaultsValidator struct { +type ServiceDefaultsWebhook struct { ConsulClient *capi.Client Logger logr.Logger @@ -38,7 +38,7 @@ type ServiceDefaultsValidator struct { // // +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-servicedefaults,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=servicedefaults,versions=v1alpha1,name=mutate-servicedefaults.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None -func (v *ServiceDefaultsValidator) Handle(ctx context.Context, req admission.Request) admission.Response { +func (v *ServiceDefaultsWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { var svcDefaults ServiceDefaults err := v.decoder.Decode(req, &svcDefaults) if err != nil { @@ -54,7 +54,7 @@ func (v *ServiceDefaultsValidator) Handle(ctx context.Context, req admission.Req v.EnableNSMirroring) } -func (v *ServiceDefaultsValidator) List(ctx context.Context) ([]common.ConfigEntryResource, error) { +func (v *ServiceDefaultsWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, error) { var svcDefaultsList ServiceDefaultsList if err := v.Client.List(ctx, &svcDefaultsList); err != nil { return nil, err @@ -66,7 +66,7 @@ func (v *ServiceDefaultsValidator) List(ctx context.Context) ([]common.ConfigEnt return entries, nil } -func (v *ServiceDefaultsValidator) InjectDecoder(d *admission.Decoder) error { +func (v *ServiceDefaultsWebhook) InjectDecoder(d *admission.Decoder) error { v.decoder = d return nil } diff --git a/api/v1alpha1/serviceresolver_webhook.go b/api/v1alpha1/serviceresolver_webhook.go index 2431a0fd05..7b80e6af09 100644 --- a/api/v1alpha1/serviceresolver_webhook.go +++ b/api/v1alpha1/serviceresolver_webhook.go @@ -13,7 +13,7 @@ import ( // +kubebuilder:object:generate=false -type ServiceResolverValidator struct { +type ServiceResolverWebhook struct { ConsulClient *capi.Client Logger logr.Logger @@ -38,7 +38,7 @@ type ServiceResolverValidator struct { // // +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-serviceresolver,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=serviceresolvers,versions=v1alpha1,name=mutate-serviceresolver.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None -func (v *ServiceResolverValidator) Handle(ctx context.Context, req admission.Request) admission.Response { +func (v *ServiceResolverWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { var svcResolver ServiceResolver err := v.decoder.Decode(req, &svcResolver) if err != nil { @@ -54,7 +54,7 @@ func (v *ServiceResolverValidator) Handle(ctx context.Context, req admission.Req v.EnableNSMirroring) } -func (v *ServiceResolverValidator) List(ctx context.Context) ([]common.ConfigEntryResource, error) { +func (v *ServiceResolverWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, error) { var svcResolverList ServiceResolverList if err := v.Client.List(ctx, &svcResolverList); err != nil { return nil, err @@ -66,7 +66,7 @@ func (v *ServiceResolverValidator) List(ctx context.Context) ([]common.ConfigEnt return entries, nil } -func (v *ServiceResolverValidator) InjectDecoder(d *admission.Decoder) error { +func (v *ServiceResolverWebhook) InjectDecoder(d *admission.Decoder) error { v.decoder = d return nil } diff --git a/api/v1alpha1/servicerouter_webhook.go b/api/v1alpha1/servicerouter_webhook.go index 5954e31a63..4719b216f9 100644 --- a/api/v1alpha1/servicerouter_webhook.go +++ b/api/v1alpha1/servicerouter_webhook.go @@ -13,7 +13,7 @@ import ( // +kubebuilder:object:generate=false -type ServiceRouterValidator struct { +type ServiceRouterWebhook struct { ConsulClient *capi.Client Logger logr.Logger @@ -38,7 +38,7 @@ type ServiceRouterValidator struct { // // +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-servicerouter,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=servicerouters,versions=v1alpha1,name=mutate-servicerouter.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None -func (v *ServiceRouterValidator) Handle(ctx context.Context, req admission.Request) admission.Response { +func (v *ServiceRouterWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { var svcRouter ServiceRouter err := v.decoder.Decode(req, &svcRouter) if err != nil { @@ -54,7 +54,7 @@ func (v *ServiceRouterValidator) Handle(ctx context.Context, req admission.Reque v.EnableNSMirroring) } -func (v *ServiceRouterValidator) List(ctx context.Context) ([]common.ConfigEntryResource, error) { +func (v *ServiceRouterWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, error) { var svcRouterList ServiceRouterList if err := v.Client.List(ctx, &svcRouterList); err != nil { return nil, err @@ -66,7 +66,7 @@ func (v *ServiceRouterValidator) List(ctx context.Context) ([]common.ConfigEntry return entries, nil } -func (v *ServiceRouterValidator) InjectDecoder(d *admission.Decoder) error { +func (v *ServiceRouterWebhook) InjectDecoder(d *admission.Decoder) error { v.decoder = d return nil } diff --git a/api/v1alpha1/servicesplitter_webhook.go b/api/v1alpha1/servicesplitter_webhook.go index 3d055c98c2..7d31b0510e 100644 --- a/api/v1alpha1/servicesplitter_webhook.go +++ b/api/v1alpha1/servicesplitter_webhook.go @@ -13,7 +13,7 @@ import ( // +kubebuilder:object:generate=false -type ServiceSplitterValidator struct { +type ServiceSplitterWebhook struct { ConsulClient *capi.Client Logger logr.Logger @@ -39,7 +39,7 @@ type ServiceSplitterValidator struct { // // +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-servicesplitter,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=servicesplitters,versions=v1alpha1,name=mutate-servicesplitter.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None -func (v *ServiceSplitterValidator) Handle(ctx context.Context, req admission.Request) admission.Response { +func (v *ServiceSplitterWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { var serviceSplitter ServiceSplitter err := v.decoder.Decode(req, &serviceSplitter) if err != nil { @@ -55,7 +55,7 @@ func (v *ServiceSplitterValidator) Handle(ctx context.Context, req admission.Req v.EnableNSMirroring) } -func (v *ServiceSplitterValidator) List(ctx context.Context) ([]common.ConfigEntryResource, error) { +func (v *ServiceSplitterWebhook) List(ctx context.Context) ([]common.ConfigEntryResource, error) { var serviceSplitterList ServiceSplitterList if err := v.Client.List(ctx, &serviceSplitterList); err != nil { return nil, err @@ -67,7 +67,7 @@ func (v *ServiceSplitterValidator) List(ctx context.Context) ([]common.ConfigEnt return entries, nil } -func (v *ServiceSplitterValidator) InjectDecoder(d *admission.Decoder) error { +func (v *ServiceSplitterWebhook) InjectDecoder(d *admission.Decoder) error { v.decoder = d return nil } diff --git a/controllers/configentry_controller.go b/controller/configentry_controller.go similarity index 99% rename from controllers/configentry_controller.go rename to controller/configentry_controller.go index c458b98e04..6ce55cd85f 100644 --- a/controllers/configentry_controller.go +++ b/controller/configentry_controller.go @@ -1,4 +1,4 @@ -package controllers +package controller import ( "context" diff --git a/controller/configentry_controller_ent_test.go b/controller/configentry_controller_ent_test.go new file mode 100644 index 0000000000..c6a7cfe7e5 --- /dev/null +++ b/controller/configentry_controller_ent_test.go @@ -0,0 +1,623 @@ +// +build enterprise + +package controller_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/go-logr/logr" + logrtest "github.com/go-logr/logr/testing" + "github.com/hashicorp/consul-k8s/api/common" + "github.com/hashicorp/consul-k8s/api/v1alpha1" + "github.com/hashicorp/consul-k8s/controller" + 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" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// NOTE: We're not testing each controller type here because that's done in +// the OSS tests and it would result in too many permutations. Instead +// we're only testing with the ServiceDefaults and ProxyDefaults controller which will exercise +// all the namespaces code for config entries that are namespaced and those that +// exist in the global namespace. + +func TestConfigEntryController_createsConfigEntry_consulNamespaces(tt *testing.T) { + tt.Parallel() + + cases := map[string]struct { + Mirror bool + MirrorPrefix string + SourceKubeNS string + DestConsulNS string + ExpConsulNS string + }{ + "SourceKubeNS=default, DestConsulNS=default": { + SourceKubeNS: "default", + DestConsulNS: "default", + ExpConsulNS: "default", + }, + "SourceKubeNS=kube, DestConsulNS=default": { + SourceKubeNS: "kube", + DestConsulNS: "default", + ExpConsulNS: "default", + }, + "SourceKubeNS=default, DestConsulNS=other": { + SourceKubeNS: "default", + DestConsulNS: "other", + ExpConsulNS: "other", + }, + "SourceKubeNS=kube, DestConsulNS=other": { + SourceKubeNS: "kube", + DestConsulNS: "other", + ExpConsulNS: "other", + }, + "SourceKubeNS=default, Mirror=true": { + SourceKubeNS: "default", + Mirror: true, + ExpConsulNS: "default", + }, + "SourceKubeNS=kube, Mirror=true": { + SourceKubeNS: "kube", + Mirror: true, + ExpConsulNS: "kube", + }, + "SourceKubeNS=default, Mirror=true, Prefix=prefix": { + SourceKubeNS: "default", + Mirror: true, + MirrorPrefix: "prefix-", + ExpConsulNS: "prefix-default", + }, + } + + for name, c := range cases { + configEntryKinds := map[string]struct { + ConsulKind string + ConsulNamespace string + KubeResource common.ConfigEntryResource + GetController func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler + AssertValidConfig func(entry capi.ConfigEntry) bool + }{ + "namespaced": { + ConsulKind: capi.ServiceDefaults, + KubeResource: &v1alpha1.ServiceDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: c.SourceKubeNS, + }, + Spec: v1alpha1.ServiceDefaultsSpec{ + Protocol: "http", + }, + }, + GetController: func(client client.Client, logger logr.Logger, scheme *runtime.Scheme, cont *controller.ConfigEntryController) reconcile.Reconciler { + return &controller.ServiceDefaultsController{ + Client: client, + Log: logger, + Scheme: scheme, + ConfigEntryController: cont, + } + }, + AssertValidConfig: func(cfg capi.ConfigEntry) bool { + configEntry, ok := cfg.(*capi.ServiceConfigEntry) + if !ok { + return false + } + return configEntry.Protocol == "http" + }, + ConsulNamespace: c.ExpConsulNS, + }, + "global": { + ConsulKind: capi.ProxyDefaults, + KubeResource: &v1alpha1.ProxyDefaults{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global", + Namespace: c.SourceKubeNS, + }, + Spec: v1alpha1.ProxyDefaultsSpec{ + MeshGateway: v1alpha1.MeshGatewayConfig{ + 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, + }, + } + + 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() + + consul, err := testutil.NewTestServerConfigT(t, nil) + req.NoError(err) + defer consul.Stop() + consulClient, err := capi.NewClient(&capi.Config{ + Address: consul.HTTPAddr, + }) + req.NoError(err) + + fakeClient := fake.NewFakeClientWithScheme(s, in.KubeResource) + + r := in.GetController( + fakeClient, + logrtest.TestLogger{T: t}, + s, + &controller.ConfigEntryController{ + ConsulClient: consulClient, + EnableConsulNamespaces: true, + EnableNSMirroring: c.Mirror, + NSMirroringPrefix: c.MirrorPrefix, + ConsulDestinationNamespace: c.DestConsulNS, + }, + ) + + resp, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: c.SourceKubeNS, + Name: in.KubeResource.Name(), + }, + }) + req.NoError(err) + req.False(resp.Requeue) + + cfg, _, err := consulClient.ConfigEntries().Get(in.ConsulKind, in.KubeResource.Name(), &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.Name(), + }, 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.MeshGatewayConfig{ + 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 + }, + }, + } + 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() + + consul, err := testutil.NewTestServerConfigT(t, nil) + req.NoError(err) + defer consul.Stop() + consulClient, err := capi.NewClient(&capi.Config{ + Address: consul.HTTPAddr, + }) + req.NoError(err) + + fakeClient := fake.NewFakeClientWithScheme(s, in.KubeResource) + + r := in.GetControllerFunc( + fakeClient, + logrtest.TestLogger{T: t}, + s, + &controller.ConfigEntryController{ + ConsulClient: consulClient, + 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.Name(), + }, in.KubeResource) + req.NoError(err) + + // Update the resource. + err := in.UpdateResourceFunc(fakeClient, ctx, in.KubeResource) + req.NoError(err) + + resp, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: c.SourceKubeNS, + Name: in.KubeResource.Name(), + }, + }) + req.NoError(err) + req.False(resp.Requeue) + + cfg, _, err := consulClient.ConfigEntries().Get(in.ConsulKind, in.KubeResource.Name(), &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.MeshGatewayConfig{ + 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.ServiceConfigEntry{ + Kind: capi.ServiceDefaults, + Name: common.Global, + MeshGateway: capi.MeshGatewayConfig{ + Mode: capi.MeshGatewayModeRemote, + }, + }, &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) + + consul, err := testutil.NewTestServerConfigT(t, nil) + req.NoError(err) + defer consul.Stop() + consulClient, err := capi.NewClient(&capi.Config{ + Address: consul.HTTPAddr, + }) + req.NoError(err) + + fakeClient := fake.NewFakeClientWithScheme(s, in.KubeResource) + + r := in.GetControllerFunc( + fakeClient, + logrtest.TestLogger{T: t}, + s, + &controller.ConfigEntryController{ + ConsulClient: consulClient, + 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(ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: c.SourceKubeNS, + Name: in.KubeResource.Name(), + }, + }) + req.NoError(err) + req.False(resp.Requeue) + + _, _, err = consulClient.ConfigEntries().Get(in.ConsulKind, in.KubeResource.Name(), &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.Name())) + } + }) + } + } +} diff --git a/controllers/configentry_controller_test.go b/controller/configentry_controller_test.go similarity index 99% rename from controllers/configentry_controller_test.go rename to controller/configentry_controller_test.go index cecb371f6e..664f6a0961 100644 --- a/controllers/configentry_controller_test.go +++ b/controller/configentry_controller_test.go @@ -1,4 +1,4 @@ -package controllers +package controller import ( "context" diff --git a/controllers/proxydefaults_controller.go b/controller/proxydefaults_controller.go similarity index 98% rename from controllers/proxydefaults_controller.go rename to controller/proxydefaults_controller.go index e5b8481bde..5e0ed58bc1 100644 --- a/controllers/proxydefaults_controller.go +++ b/controller/proxydefaults_controller.go @@ -1,4 +1,4 @@ -package controllers +package controller import ( "context" diff --git a/controllers/servicedefaults_controller.go b/controller/servicedefaults_controller.go similarity index 98% rename from controllers/servicedefaults_controller.go rename to controller/servicedefaults_controller.go index ace31b16b3..b2dd835f8b 100644 --- a/controllers/servicedefaults_controller.go +++ b/controller/servicedefaults_controller.go @@ -1,4 +1,4 @@ -package controllers +package controller import ( "context" diff --git a/controllers/serviceresolver_controller.go b/controller/serviceresolver_controller.go similarity index 98% rename from controllers/serviceresolver_controller.go rename to controller/serviceresolver_controller.go index 4d50309f65..8f95e0f5f4 100644 --- a/controllers/serviceresolver_controller.go +++ b/controller/serviceresolver_controller.go @@ -1,4 +1,4 @@ -package controllers +package controller import ( "context" diff --git a/controllers/servicerouter_controller.go b/controller/servicerouter_controller.go similarity index 98% rename from controllers/servicerouter_controller.go rename to controller/servicerouter_controller.go index 4d3c891b18..fb6eb2addb 100644 --- a/controllers/servicerouter_controller.go +++ b/controller/servicerouter_controller.go @@ -1,4 +1,4 @@ -package controllers +package controller import ( "context" diff --git a/controllers/servicesplitter_controller.go b/controller/servicesplitter_controller.go similarity index 98% rename from controllers/servicesplitter_controller.go rename to controller/servicesplitter_controller.go index ef5cf4eef3..16a14524d2 100644 --- a/controllers/servicesplitter_controller.go +++ b/controller/servicesplitter_controller.go @@ -1,4 +1,4 @@ -package controllers +package controller import ( "context" diff --git a/controllers/configentry_controller_ent_test.go b/controllers/configentry_controller_ent_test.go deleted file mode 100644 index 7d0c4da80e..0000000000 --- a/controllers/configentry_controller_ent_test.go +++ /dev/null @@ -1,799 +0,0 @@ -// +build enterprise - -package controllers_test - -import ( - "context" - "testing" - "time" - - logrtest "github.com/go-logr/logr/testing" - "github.com/hashicorp/consul-k8s/api/common" - "github.com/hashicorp/consul-k8s/api/v1alpha1" - "github.com/hashicorp/consul-k8s/controllers" - capi "github.com/hashicorp/consul/api" - "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/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -// 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 controller which will exercise -// all the namespaces code. - -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 { - tt.Run(name, func(t *testing.T) { - req := require.New(t) - svcDefaults := &v1alpha1.ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: c.SourceKubeNS, - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - } - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, svcDefaults) - ctx := context.Background() - - consul, err := testutil.NewTestServerConfigT(t, nil) - req.NoError(err) - defer consul.Stop() - consulClient, err := capi.NewClient(&capi.Config{ - Address: consul.HTTPAddr, - }) - req.NoError(err) - - client := fake.NewFakeClientWithScheme(s, svcDefaults) - - r := controllers.ServiceDefaultsController{ - Client: client, - Log: logrtest.TestLogger{T: t}, - Scheme: s, - ConfigEntryController: &controllers.ConfigEntryController{ - ConsulClient: consulClient, - EnableConsulNamespaces: true, - EnableNSMirroring: c.Mirror, - NSMirroringPrefix: c.MirrorPrefix, - ConsulDestinationNamespace: c.DestConsulNS, - }, - } - - resp, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: svcDefaults.ObjectMeta.Namespace, - Name: svcDefaults.ObjectMeta.Name, - }, - }) - req.NoError(err) - req.False(resp.Requeue) - - cfg, _, err := consulClient.ConfigEntries().Get(capi.ServiceDefaults, "foo", &capi.QueryOptions{ - Namespace: c.ExpConsulNS, - }) - req.NoError(err) - svcDefault, ok := cfg.(*capi.ServiceConfigEntry) - req.True(ok) - req.Equal("http", svcDefault.Protocol) - - // Check that the status is "synced". - err = client.Get(ctx, types.NamespacedName{ - Namespace: svcDefaults.Namespace, - Name: svcDefaults.Name(), - }, svcDefaults) - req.NoError(err) - conditionSynced := svcDefaults.Status.GetCondition(v1alpha1.ConditionSynced) - req.True(conditionSynced.IsTrue()) - - }) - } -} - -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 { - tt.Run(name, func(t *testing.T) { - req := require.New(t) - svcDefaults := &v1alpha1.ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: c.SourceKubeNS, - Finalizers: []string{controllers.FinalizerName}, - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - } - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, svcDefaults) - ctx := context.Background() - - consul, err := testutil.NewTestServerConfigT(t, nil) - req.NoError(err) - defer consul.Stop() - consulClient, err := capi.NewClient(&capi.Config{ - Address: consul.HTTPAddr, - }) - req.NoError(err) - - client := fake.NewFakeClientWithScheme(s, svcDefaults) - - r := controllers.ServiceDefaultsController{ - Client: client, - Log: logrtest.TestLogger{T: t}, - Scheme: s, - ConfigEntryController: &controllers.ConfigEntryController{ - ConsulClient: consulClient, - 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 c.ExpConsulNS != "default" { - _, _, err := consulClient.Namespaces().Create(&capi.Namespace{ - Name: c.ExpConsulNS, - }, nil) - req.NoError(err) - } - written, _, err := consulClient.ConfigEntries().Set(&capi.ServiceConfigEntry{ - Kind: capi.ServiceDefaults, - Name: "foo", - Protocol: "http", - }, &capi.WriteOptions{Namespace: c.ExpConsulNS}) - req.NoError(err) - req.True(written) - } - - // Now update it. - { - // First get it so we have the latest revision number. - err = client.Get(ctx, types.NamespacedName{ - Namespace: svcDefaults.Namespace, - Name: svcDefaults.Name(), - }, svcDefaults) - req.NoError(err) - - // Update the protocol. - svcDefaults.Spec.Protocol = "tcp" - err := client.Update(ctx, svcDefaults) - req.NoError(err) - - resp, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: svcDefaults.ObjectMeta.Namespace, - Name: svcDefaults.ObjectMeta.Name, - }, - }) - req.NoError(err) - req.False(resp.Requeue) - - cfg, _, err := consulClient.ConfigEntries().Get(capi.ServiceDefaults, "foo", &capi.QueryOptions{Namespace: c.ExpConsulNS}) - req.NoError(err) - svcDefault, ok := cfg.(*capi.ServiceConfigEntry) - req.True(ok) - req.Equal("tcp", svcDefault.Protocol) - } - }) - } -} - -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 { - tt.Run(name, func(t *testing.T) { - req := require.New(t) - // Create it with the deletion timestamp set to mimic that it's already - // been marked for deletion. - svcDefaults := &v1alpha1.ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: c.SourceKubeNS, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - Finalizers: []string{controllers.FinalizerName}, - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - } - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, svcDefaults) - - consul, err := testutil.NewTestServerConfigT(t, nil) - req.NoError(err) - defer consul.Stop() - consulClient, err := capi.NewClient(&capi.Config{ - Address: consul.HTTPAddr, - }) - req.NoError(err) - - client := fake.NewFakeClientWithScheme(s, svcDefaults) - - r := controllers.ServiceDefaultsController{ - Client: client, - Log: logrtest.TestLogger{T: t}, - Scheme: s, - ConfigEntryController: &controllers.ConfigEntryController{ - ConsulClient: consulClient, - 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 c.ExpConsulNS != "default" { - _, _, err := consulClient.Namespaces().Create(&capi.Namespace{ - Name: c.ExpConsulNS, - }, nil) - req.NoError(err) - } - - written, _, err := consulClient.ConfigEntries().Set(&capi.ServiceConfigEntry{ - Kind: capi.ServiceDefaults, - Name: "foo", - Protocol: "http", - }, &capi.WriteOptions{Namespace: c.ExpConsulNS}) - req.NoError(err) - req.True(written) - } - - // Now run reconcile. It's marked for deletion so this should delete it. - { - resp, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: svcDefaults.ObjectMeta.Namespace, - Name: svcDefaults.ObjectMeta.Name, - }, - }) - req.NoError(err) - req.False(resp.Requeue) - - _, _, err = consulClient.ConfigEntries().Get(capi.ServiceDefaults, "foo", &capi.QueryOptions{Namespace: c.ExpConsulNS}) - req.EqualError(err, "Unexpected response code: 404 (Config entry not found for \"service-defaults\" / \"foo\")") - } - }) - } -} - -func TestConfigEntryController_createsGlobalConfigEntry_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 { - tt.Run(name, func(t *testing.T) { - req := require.New(t) - proxyDefaults := &v1alpha1.ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.Global, - Namespace: c.SourceKubeNS, - }, - Spec: v1alpha1.ProxyDefaultsSpec{ - MeshGateway: v1alpha1.MeshGatewayConfig{ - Mode: "remote", - }, - }, - } - - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, proxyDefaults) - ctx := context.Background() - - consul, err := testutil.NewTestServerConfigT(t, nil) - req.NoError(err) - defer consul.Stop() - consulClient, err := capi.NewClient(&capi.Config{ - Address: consul.HTTPAddr, - }) - req.NoError(err) - - client := fake.NewFakeClientWithScheme(s, proxyDefaults) - - proxyDefaultsController := controllers.ProxyDefaultsController{ - Client: client, - Log: logrtest.TestLogger{T: t}, - Scheme: s, - ConfigEntryController: &controllers.ConfigEntryController{ - ConsulClient: consulClient, - EnableConsulNamespaces: true, - EnableNSMirroring: c.Mirror, - NSMirroringPrefix: c.MirrorPrefix, - ConsulDestinationNamespace: c.DestConsulNS, - }, - } - - resp, err := proxyDefaultsController.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: proxyDefaults.ObjectMeta.Namespace, - Name: proxyDefaults.ObjectMeta.Name, - }, - }) - req.NoError(err) - req.False(resp.Requeue) - - cfg, _, err := consulClient.ConfigEntries().Get(capi.ProxyDefaults, common.Global, &capi.QueryOptions{ - Namespace: common.DefaultConsulNamespace, - }) - req.NoError(err) - proxyDefault, ok := cfg.(*capi.ProxyConfigEntry) - req.True(ok) - req.Equal(capi.MeshGatewayModeRemote, proxyDefault.MeshGateway.Mode) - - // Check that the status is "synced". - err = client.Get(ctx, types.NamespacedName{ - Namespace: proxyDefaults.Namespace, - Name: proxyDefaults.Name(), - }, proxyDefaults) - req.NoError(err) - conditionSynced := proxyDefaults.Status.GetCondition(v1alpha1.ConditionSynced) - req.True(conditionSynced.IsTrue()) - }) - } -} - -func TestConfigEntryController_updatesGlobalConfigEntry_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 { - tt.Run(name, func(t *testing.T) { - req := require.New(t) - proxyDefaults := &v1alpha1.ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.Global, - Namespace: c.SourceKubeNS, - Finalizers: []string{controllers.FinalizerName}, - }, - Spec: v1alpha1.ProxyDefaultsSpec{ - MeshGateway: v1alpha1.MeshGatewayConfig{ - Mode: "remote", - }, - }, - } - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, proxyDefaults) - ctx := context.Background() - - consul, err := testutil.NewTestServerConfigT(t, nil) - req.NoError(err) - defer consul.Stop() - consulClient, err := capi.NewClient(&capi.Config{ - Address: consul.HTTPAddr, - }) - req.NoError(err) - - client := fake.NewFakeClientWithScheme(s, proxyDefaults) - - r := controllers.ProxyDefaultsController{ - Client: client, - Log: logrtest.TestLogger{T: t}, - Scheme: s, - ConfigEntryController: &controllers.ConfigEntryController{ - ConsulClient: consulClient, - 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 c.ExpConsulNS != "default" { - _, _, err := consulClient.Namespaces().Create(&capi.Namespace{ - Name: c.ExpConsulNS, - }, nil) - req.NoError(err) - } - written, _, err := consulClient.ConfigEntries().Set(&capi.ProxyConfigEntry{ - Kind: capi.ProxyDefaults, - Name: common.Global, - MeshGateway: capi.MeshGatewayConfig{ - Mode: capi.MeshGatewayModeLocal, - }, - }, &capi.WriteOptions{Namespace: common.DefaultConsulNamespace}) - req.NoError(err) - req.True(written) - } - - // Now update it. - { - // First get it so we have the latest revision number. - err = client.Get(ctx, types.NamespacedName{ - Namespace: proxyDefaults.Namespace, - Name: proxyDefaults.Name(), - }, proxyDefaults) - req.NoError(err) - - // Update the protocol. - proxyDefaults.Spec.MeshGateway.Mode = "remote" - err := client.Update(ctx, proxyDefaults) - req.NoError(err) - - resp, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: proxyDefaults.ObjectMeta.Namespace, - Name: proxyDefaults.ObjectMeta.Name, - }, - }) - req.NoError(err) - req.False(resp.Requeue) - - cfg, _, err := consulClient.ConfigEntries().Get(capi.ProxyDefaults, common.Global, &capi.QueryOptions{Namespace: common.DefaultConsulNamespace}) - req.NoError(err) - svcDefault, ok := cfg.(*capi.ProxyConfigEntry) - req.True(ok) - req.Equal(capi.MeshGatewayModeRemote, svcDefault.MeshGateway.Mode) - } - }) - } -} - -func TestConfigEntryController_deletesGlobalConfigEntry_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 { - tt.Run(name, func(t *testing.T) { - req := require.New(t) - // Create it with the deletion timestamp set to mimic that it's already - // been marked for deletion. - proxyDefaults := &v1alpha1.ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.Global, - Namespace: c.SourceKubeNS, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - Finalizers: []string{controllers.FinalizerName}, - }, - Spec: v1alpha1.ProxyDefaultsSpec{ - MeshGateway: v1alpha1.MeshGatewayConfig{ - Mode: "remote", - }, - }, - } - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, proxyDefaults) - - consul, err := testutil.NewTestServerConfigT(t, nil) - req.NoError(err) - defer consul.Stop() - consulClient, err := capi.NewClient(&capi.Config{ - Address: consul.HTTPAddr, - }) - req.NoError(err) - - client := fake.NewFakeClientWithScheme(s, proxyDefaults) - - r := controllers.ProxyDefaultsController{ - Client: client, - Log: logrtest.TestLogger{T: t}, - Scheme: s, - ConfigEntryController: &controllers.ConfigEntryController{ - ConsulClient: consulClient, - 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 c.ExpConsulNS != "default" { - _, _, err := consulClient.Namespaces().Create(&capi.Namespace{ - Name: c.ExpConsulNS, - }, nil) - req.NoError(err) - } - - written, _, err := consulClient.ConfigEntries().Set(&capi.ProxyConfigEntry{ - Kind: capi.ProxyDefaults, - Name: common.Global, - MeshGateway: capi.MeshGatewayConfig{ - Mode: "local", - }, - }, &capi.WriteOptions{Namespace: common.DefaultConsulNamespace}) - req.NoError(err) - req.True(written) - } - - // Now run reconcile. It's marked for deletion so this should delete it. - { - resp, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: proxyDefaults.ObjectMeta.Namespace, - Name: proxyDefaults.ObjectMeta.Name, - }, - }) - req.NoError(err) - req.False(resp.Requeue) - - _, _, err = consulClient.ConfigEntries().Get(capi.ProxyDefaults, common.Global, &capi.QueryOptions{Namespace: common.DefaultConsulNamespace}) - req.EqualError(err, "Unexpected response code: 404 (Config entry not found for \"proxy-defaults\" / \"global\")") - } - }) - } -} diff --git a/subcommand/controller/command.go b/subcommand/controller/command.go index 12de48005d..8a4bb0aee7 100644 --- a/subcommand/controller/command.go +++ b/subcommand/controller/command.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/consul-k8s/api/common" "github.com/hashicorp/consul-k8s/api/v1alpha1" - "github.com/hashicorp/consul-k8s/controllers" + "github.com/hashicorp/consul-k8s/controller" "github.com/hashicorp/consul-k8s/subcommand/flags" "github.com/mitchellh/cli" "k8s.io/apimachinery/pkg/runtime" @@ -111,7 +111,7 @@ func (c *Command) Run(args []string) int { return 1 } - configEntryReconciler := &controllers.ConfigEntryController{ + configEntryReconciler := &controller.ConfigEntryController{ ConsulClient: consulClient, EnableConsulNamespaces: c.flagEnableNamespaces, ConsulDestinationNamespace: c.flagConsulDestinationNamespace, @@ -119,7 +119,7 @@ func (c *Command) Run(args []string) int { NSMirroringPrefix: c.flagNSMirroringPrefix, CrossNSACLPolicy: c.flagCrossNSACLPolicy, } - if err = (&controllers.ServiceDefaultsController{ + if err = (&controller.ServiceDefaultsController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.ServiceDefaults), @@ -128,7 +128,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", common.ServiceDefaults) return 1 } - if err = (&controllers.ServiceResolverController{ + if err = (&controller.ServiceResolverController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.ServiceResolver), @@ -137,7 +137,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", common.ServiceResolver) return 1 } - if err = (&controllers.ProxyDefaultsController{ + if err = (&controller.ProxyDefaultsController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.ProxyDefaults), @@ -146,7 +146,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", common.ProxyDefaults) return 1 } - if err = (&controllers.ServiceRouterController{ + if err = (&controller.ServiceRouterController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.ServiceRouter), @@ -155,7 +155,7 @@ func (c *Command) Run(args []string) int { setupLog.Error(err, "unable to create controller", "controller", common.ServiceRouter) return 1 } - if err = (&controllers.ServiceSplitterController{ + if err = (&controller.ServiceSplitterController{ ConfigEntryController: configEntryReconciler, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controller").WithName(common.ServiceSplitter), @@ -173,7 +173,7 @@ func (c *Command) Run(args []string) int { // Note: The path here should be identical to the one on the kubebuilder // annotation in each webhook file. mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicedefaults", - &webhook.Admission{Handler: &v1alpha1.ServiceDefaultsValidator{ + &webhook.Admission{Handler: &v1alpha1.ServiceDefaultsWebhook{ Client: mgr.GetClient(), ConsulClient: consulClient, Logger: ctrl.Log.WithName("webhooks").WithName(common.ServiceDefaults), @@ -181,7 +181,7 @@ func (c *Command) Run(args []string) int { EnableNSMirroring: c.flagEnableNSMirroring, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceresolver", - &webhook.Admission{Handler: &v1alpha1.ServiceResolverValidator{ + &webhook.Admission{Handler: &v1alpha1.ServiceResolverWebhook{ Client: mgr.GetClient(), ConsulClient: consulClient, Logger: ctrl.Log.WithName("webhooks").WithName(common.ServiceResolver), @@ -189,7 +189,7 @@ func (c *Command) Run(args []string) int { EnableNSMirroring: c.flagEnableNSMirroring, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-proxydefaults", - &webhook.Admission{Handler: &v1alpha1.ProxyDefaultsValidator{ + &webhook.Admission{Handler: &v1alpha1.ProxyDefaultsWebhook{ Client: mgr.GetClient(), ConsulClient: consulClient, Logger: ctrl.Log.WithName("webhooks").WithName(common.ProxyDefaults), @@ -197,7 +197,7 @@ func (c *Command) Run(args []string) int { EnableNSMirroring: c.flagEnableNSMirroring, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicerouter", - &webhook.Admission{Handler: &v1alpha1.ServiceRouterValidator{ + &webhook.Admission{Handler: &v1alpha1.ServiceRouterWebhook{ Client: mgr.GetClient(), ConsulClient: consulClient, Logger: ctrl.Log.WithName("webhooks").WithName(common.ServiceRouter), @@ -205,7 +205,7 @@ func (c *Command) Run(args []string) int { EnableNSMirroring: c.flagEnableNSMirroring, }}) mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicesplitter", - &webhook.Admission{Handler: &v1alpha1.ServiceSplitterValidator{ + &webhook.Admission{Handler: &v1alpha1.ServiceSplitterWebhook{ Client: mgr.GetClient(), ConsulClient: consulClient, Logger: ctrl.Log.WithName("webhooks").WithName(common.ServiceSplitter),