Skip to content

Commit

Permalink
Support Consul Ent NS's for CRDs
Browse files Browse the repository at this point in the history
  • Loading branch information
lkysow committed Sep 10, 2020
1 parent 3a61dfe commit 88b33d1
Show file tree
Hide file tree
Showing 18 changed files with 657 additions and 161 deletions.
5 changes: 2 additions & 3 deletions api/v1alpha1/servicedefaults_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ func init() {
// ToConsul converts the entry into it's Consul equivalent struct.
func (s *ServiceDefaults) ToConsul() *capi.ServiceConfigEntry {
return &capi.ServiceConfigEntry{
Kind: capi.ServiceDefaults,
Name: s.Name,
//Namespace: s.Namespace, // todo: don't set this unless enterprise
Kind: capi.ServiceDefaults,
Name: s.Name,
Protocol: s.Spec.Protocol,
MeshGateway: s.Spec.MeshGateway.toConsul(),
Expose: s.Spec.Expose.toConsul(),
Expand Down
42 changes: 2 additions & 40 deletions catalog/to-consul/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/cenkalti/backoff"
"github.com/deckarep/golang-set"
"github.com/hashicorp/consul-k8s/namespaces"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/go-hclog"
)
Expand Down Expand Up @@ -417,9 +418,7 @@ func (s *ConsulSyncer) syncFull(ctx context.Context) {
for _, services := range s.namespaces {
for _, r := range services {
if s.EnableNamespaces {
// Check and potentially create the service's namespace if
// it doesn't already exist
err := s.checkAndCreateNamespace(r.Service.Namespace)
err := namespaces.EnsureExists(s.Client, r.Service.Namespace, s.CrossNamespaceACLPolicy)
if err != nil {
s.Log.Warn("error checking and creating Consul namespace",
"node-name", r.Node,
Expand Down Expand Up @@ -475,40 +474,3 @@ func (s *ConsulSyncer) init() {
s.initialSync = make(chan bool)
}
}

func (s *ConsulSyncer) checkAndCreateNamespace(ns string) error {
// Check if the Consul namespace exists
namespaceInfo, _, err := s.Client.Namespaces().Read(ns, nil)
if err != nil {
return err
}

// If not, create it
if namespaceInfo == nil {
var aclConfig api.NamespaceACLConfig
if s.CrossNamespaceACLPolicy != "" {
// Create the ACLs config for the cross-Consul-namespace
// default policy that needs to be attached
aclConfig = api.NamespaceACLConfig{
PolicyDefaults: []api.ACLLink{
{Name: s.CrossNamespaceACLPolicy},
},
}
}

consulNamespace := api.Namespace{
Name: ns,
Description: "Auto-generated by a Catalog Sync Process",
ACLs: &aclConfig,
Meta: map[string]string{"external-source": "kubernetes"},
}

_, _, err = s.Client.Namespaces().Create(&consulNamespace, nil)
if err != nil {
return err
}
s.Log.Info("creating consul namespace", "name", consulNamespace.Name)
}

return nil
}
40 changes: 2 additions & 38 deletions connect-inject/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strconv"

"github.com/deckarep/golang-set"
"github.com/hashicorp/consul-k8s/namespaces"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/go-hclog"
"github.com/mattbaird/jsonpatch"
Expand Down Expand Up @@ -370,8 +371,7 @@ func (h *Handler) Mutate(req *v1beta1.AdmissionRequest) *v1beta1.AdmissionRespon
// all patches are created to guarantee no errors were encountered in
// that process before modifying the Consul cluster.
if h.EnableNamespaces {
// Check if the namespace exists. If not, create it.
if err := h.checkAndCreateNamespace(h.consulNamespace(req.Namespace)); err != nil {
if err := namespaces.EnsureExists(h.ConsulClient, h.consulNamespace(req.Namespace), h.CrossNamespaceACLPolicy); err != nil {
h.Log.Error("Error checking or creating namespace", "err", err,
"Namespace", h.consulNamespace(req.Namespace), "Request Name", req.Name)
return &v1beta1.AdmissionResponse{
Expand Down Expand Up @@ -503,42 +503,6 @@ func (h *Handler) consulNamespace(ns string) string {
}
}

func (h *Handler) checkAndCreateNamespace(ns string) error {
// Check if the Consul namespace exists
namespaceInfo, _, err := h.ConsulClient.Namespaces().Read(ns, nil)
if err != nil {
return err
}

// If not, create it
if namespaceInfo == nil {
var aclConfig api.NamespaceACLConfig
if h.CrossNamespaceACLPolicy != "" {
// Create the ACLs config for the cross-Consul-namespace
// default policy that needs to be attached
aclConfig = api.NamespaceACLConfig{
PolicyDefaults: []api.ACLLink{
{Name: h.CrossNamespaceACLPolicy},
},
}
}

consulNamespace := api.Namespace{
Name: ns,
Description: "Auto-generated by a Connect Injector",
ACLs: &aclConfig,
Meta: map[string]string{"external-source": "kubernetes"},
}

_, _, err = h.ConsulClient.Namespaces().Create(&consulNamespace, nil)
if err != nil {
return err
}
}

return nil
}

func portValue(pod *corev1.Pod, value string) (int32, error) {
// First search for the named port
for _, c := range pod.Spec.Containers {
Expand Down
5 changes: 2 additions & 3 deletions connect-inject/handler_ent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ func TestHandler_MutateWithNamespaces(t *testing.T) {

// Check created namespace properties
if ns != "default" {
require.Equalf("Auto-generated by a Connect Injector", actNamespace.Description,
require.Equalf("Auto-generated by consul-k8s", actNamespace.Description,
"wrong namespace description for namespace %s", ns)
require.Containsf(actNamespace.Meta, "external-source",
"namespace %s does not contain external-source metadata key", ns)
Expand Down Expand Up @@ -420,7 +420,6 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) {
a, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) {
c.ACL.Enabled = true
})
require.NoError(t, err)
defer a.Stop()

// Set up a client for bootstrapping
Expand Down Expand Up @@ -489,7 +488,7 @@ func TestHandler_MutateWithNamespaces_ACLs(t *testing.T) {

// Check created namespace properties
if ns != "default" {
require.Equalf(t, "Auto-generated by a Connect Injector", actNamespace.Description,
require.Equalf(t, "Auto-generated by consul-k8s", actNamespace.Description,
"wrong namespace description for namespace %s", ns)
require.Containsf(t, actNamespace.Meta, "external-source",
"namespace %s does not contain external-source metadata key", ns)
Expand Down
70 changes: 66 additions & 4 deletions controllers/servicedefaults_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package controllers
import (
"context"
"errors"
"fmt"
"strings"

"github.com/go-logr/logr"
"github.com/hashicorp/consul-k8s/namespaces"
capi "github.com/hashicorp/consul/api"
corev1 "k8s.io/api/core/v1"
k8serr "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -29,6 +31,30 @@ type ServiceDefaultsReconciler struct {
Log logr.Logger
Scheme *runtime.Scheme
ConsulClient *capi.Client

// EnableConsulNamespaces indicates that a user is running Consul Enterprise
// with version 1.7+ which supports namespaces.
EnableConsulNamespaces bool

// ConsulDestinationNamespace is the name of the Consul namespace to create
// all config entries in. If EnableNSMirroring is true this is ignored.
ConsulDestinationNamespace string

// EnableNSMirroring causes Consul namespaces to be created to match the
// k8s namespace of any config entry custom resource. Config entries will
// be created in the matching Consul namespace.
EnableNSMirroring bool

// NSMirroringPrefix is an optional prefix that can be added to the Consul
// namespaces created while mirroring. For example, if it is set to "k8s-",
// then the k8s `default` namespace will be mirrored in Consul's
// `k8s-default` namespace.
NSMirroringPrefix string

// CrossNSACLPolicy is the name of the ACL policy to attach to
// any created Consul namespaces to allow cross namespace service discovery.
// Only necessary if ACLs are enabled.
CrossNSACLPolicy string
}

// +kubebuilder:rbac:groups=consul.hashicorp.com,resources=servicedefaults,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -64,7 +90,9 @@ func (r *ServiceDefaultsReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er
logger.Info("deletion event")
// Our finalizer is present, so we need to delete the config entry
// from consul.
_, err = r.ConsulClient.ConfigEntries().Delete(capi.ServiceDefaults, svcDefaults.Name, nil)
_, err = r.ConsulClient.ConfigEntries().Delete(capi.ServiceDefaults, svcDefaults.Name, &capi.WriteOptions{
Namespace: r.consulNamespace(req.Namespace),
})
if err != nil {
return ctrl.Result{}, err
}
Expand All @@ -83,11 +111,27 @@ func (r *ServiceDefaultsReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er
}

// Check to see if consul has service defaults with the same name
entry, _, err := r.ConsulClient.ConfigEntries().Get(capi.ServiceDefaults, svcDefaults.Name, nil)
entry, _, err := r.ConsulClient.ConfigEntries().Get(capi.ServiceDefaults, svcDefaults.Name, &capi.QueryOptions{
Namespace: r.consulNamespace(req.Namespace),
})
// If a config entry with this name does not exist
if isNotFoundErr(err) {
// If Consul namespaces are enabled we may need to create the
// destination consul namespace first.
if r.EnableConsulNamespaces {
if err := namespaces.EnsureExists(r.ConsulClient, r.consulNamespace(req.Namespace), r.CrossNSACLPolicy); err != nil {
svcDefaults.Status.Conditions = syncFailed(ConsulAgentError, err.Error())
if err := r.Status().Update(context.Background(), &svcDefaults); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, err
}
}

// Create the config entry
_, _, err := r.ConsulClient.ConfigEntries().Set(svcDefaults.ToConsul(), nil)
_, _, err := r.ConsulClient.ConfigEntries().Set(svcDefaults.ToConsul(), &capi.WriteOptions{
Namespace: r.consulNamespace(req.Namespace),
})
if err != nil {
svcDefaults.Status.Conditions = syncFailed(ConsulAgentError, err.Error())
if err := r.Status().Update(context.Background(), &svcDefaults); err != nil {
Expand Down Expand Up @@ -121,7 +165,9 @@ func (r *ServiceDefaultsReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er
return ctrl.Result{}, err
}
if !svcDefaults.MatchesConsul(svcDefaultEntry) {
_, _, err := r.ConsulClient.ConfigEntries().Set(svcDefaults.ToConsul(), nil)
_, _, err := r.ConsulClient.ConfigEntries().Set(svcDefaults.ToConsul(), &capi.WriteOptions{
Namespace: r.consulNamespace(req.Namespace),
})
if err != nil {
svcDefaults.Status.Conditions = syncUnknownWithError(ConsulAgentError, err.Error())
if err := r.Status().Update(context.Background(), &svcDefaults); err != nil {
Expand Down Expand Up @@ -149,6 +195,22 @@ func (r *ServiceDefaultsReconciler) SetupWithManager(mgr ctrl.Manager) error {
Complete(r)
}

// consulNamespace returns the namespace that a service should be
// registered in based on the namespace options. It returns an
// empty string if namespaces aren't enabled.
func (r *ServiceDefaultsReconciler) consulNamespace(ns string) string {
if !r.EnableConsulNamespaces {
return ""
}

// Mirroring takes precedence.
if r.EnableNSMirroring {
return fmt.Sprintf("%s%s", r.NSMirroringPrefix, ns)
}

return r.ConsulDestinationNamespace
}

func syncFailed(reason, message string) consulv1alpha1.Conditions {
return consulv1alpha1.Conditions{
{
Expand Down
Loading

0 comments on commit 88b33d1

Please sign in to comment.