Skip to content

Commit

Permalink
Allow to specify full certificate template for cert-manager integrati…
Browse files Browse the repository at this point in the history
…on (#18)
  • Loading branch information
borchero authored Jun 25, 2022
1 parent fa228be commit 464b219
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 36 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/borchero/zeus v1.0.0
github.com/google/uuid v1.3.0
github.com/imdario/mergo v0.3.13
github.com/jetstack/cert-manager v1.7.2
github.com/stretchr/testify v1.7.0
github.com/traefik/traefik/v2 v2.6.6
Expand Down Expand Up @@ -47,7 +48,6 @@ require (
github.com/google/gofuzz v1.2.0 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -923,8 +923,9 @@ github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
Expand Down
3 changes: 2 additions & 1 deletion internal/config/v1/config_types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v1

import (
v1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
cfg "sigs.k8s.io/controller-runtime/pkg/config/v1alpha1"
)
Expand Down Expand Up @@ -40,7 +41,7 @@ type ExternalDNSIntegrationConfig struct {

// CertManagerIntegrationConfig describes the configuration for the cert-manager integration.
type CertManagerIntegrationConfig struct {
Issuer IssuerRef `json:"issuer"`
Template v1.Certificate `json:"certificateTemplate"`
}

// ServiceRef uniquely describes a Kubernetes service.
Expand Down
10 changes: 7 additions & 3 deletions internal/config/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 10 additions & 5 deletions internal/controllers/ingressroute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
configv1 "github.com/borchero/switchboard/internal/config/v1"
"github.com/borchero/switchboard/internal/k8tests"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
traefik "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
Expand Down Expand Up @@ -199,11 +200,11 @@ func runTest(t *testing.T, test testCase) {
assert.Nil(t, err)
assert.ElementsMatch(t, test.DNSNames, certificate.Spec.DNSNames)
assert.Equal(t,
config.Integrations.CertManager.Issuer.Kind,
config.Integrations.CertManager.Template.Spec.IssuerRef.Kind,
certificate.Spec.IssuerRef.Kind,
)
assert.Equal(t,
config.Integrations.CertManager.Issuer.Name,
config.Integrations.CertManager.Template.Spec.IssuerRef.Name,
certificate.Spec.IssuerRef.Name,
)
assert.Equal(t, test.Ingress.Spec.TLS.SecretName, certificate.Spec.SecretName)
Expand Down Expand Up @@ -254,9 +255,13 @@ func createConfig(service *v1.Service) configv1.Config {
},
},
CertManager: &configv1.CertManagerIntegrationConfig{
Issuer: configv1.IssuerRef{
Kind: "ClusterIssuer",
Name: "my-issuer",
Template: certmanager.Certificate{
Spec: certmanager.CertificateSpec{
IssuerRef: cmmeta.ObjectReference{
Kind: "ClusterIssuer",
Name: "my-issuer",
},
},
},
},
},
Expand Down
8 changes: 1 addition & 7 deletions internal/controllers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/borchero/switchboard/internal/integrations"
"github.com/borchero/switchboard/internal/k8s"
"github.com/borchero/switchboard/internal/switchboard"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
traefik "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"go.uber.org/zap"
"sigs.k8s.io/controller-runtime/pkg/builder"
Expand Down Expand Up @@ -44,12 +43,7 @@ func integrationsFromConfig(

certManager := config.Integrations.CertManager
if certManager != nil {
result = append(result, integrations.NewCertManager(
client, cmmeta.ObjectReference{
Kind: certManager.Issuer.Kind,
Name: certManager.Issuer.Name,
},
))
result = append(result, integrations.NewCertManager(client, certManager.Template))
}
return result, nil
}
Expand Down
11 changes: 10 additions & 1 deletion internal/controllers/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (

configv1 "github.com/borchero/switchboard/internal/config/v1"
"github.com/borchero/switchboard/internal/k8tests"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand All @@ -30,7 +32,14 @@ func TestIntegrationsFromConfig(t *testing.T) {

config.Integrations.ExternalDNS = nil
config.Integrations.CertManager = &configv1.CertManagerIntegrationConfig{
Issuer: configv1.IssuerRef{Kind: "ClusterIssuer", Name: "my-issuer"},
Template: certmanager.Certificate{
Spec: certmanager.CertificateSpec{
IssuerRef: cmmeta.ObjectReference{
Kind: "ClusterIssuer",
Name: "my-issuer",
},
},
},
}
integrations, err = integrationsFromConfig(config, client)
require.Nil(t, err)
Expand Down
28 changes: 17 additions & 11 deletions internal/integrations/certmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@ import (
"fmt"

"github.com/borchero/switchboard/internal/k8s"
"github.com/imdario/mergo"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
v1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type certManager struct {
client client.Client
issuer cmmeta.ObjectReference
client client.Client
template v1.Certificate
}

// NewCertManager initializes a new cert-manager integration which creates certificates which use
// the provided issuer.
func NewCertManager(client client.Client, issuer cmmeta.ObjectReference) Integration {
return &certManager{client, issuer}
func NewCertManager(client client.Client, template v1.Certificate) Integration {
return &certManager{client, template}
}

func (*certManager) Name() string {
Expand Down Expand Up @@ -52,14 +53,19 @@ func (c *certManager) UpdateResource(
resource := certmanager.Certificate{ObjectMeta: c.objectMeta(owner)}
if _, err := controllerutil.CreateOrPatch(ctx, c.client, &resource, func() error {
// Meta
if err := reconcileMetadata(owner, &resource, c.client.Scheme()); err != nil {
return err
if err := reconcileMetadata(
owner, &resource, c.client.Scheme(), &c.template.ObjectMeta,
); err != nil {
return fmt.Errorf("failed to reconcile metadata: %s", err)
}

// Spec
resource.Spec.SecretName = *info.TLSSecretName
resource.Spec.DNSNames = info.Hosts
resource.Spec.IssuerRef.Kind = c.issuer.Kind
resource.Spec.IssuerRef.Name = c.issuer.Name
template := c.template.Spec.DeepCopy()
template.SecretName = *info.TLSSecretName
template.DNSNames = info.Hosts
if err := mergo.Merge(&resource.Spec, template, mergo.WithOverride); err != nil {
return fmt.Errorf("failed to reconcile specification: %s", err)
}
return nil
}); err != nil {
return fmt.Errorf("failed to upsert TLS certificate: %w", err)
Expand Down
10 changes: 7 additions & 3 deletions internal/integrations/certmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ func TestCertManagerUpdateResource(t *testing.T) {
owner := k8tests.DummyService("my-service", namespace, 80)
err := client.Create(ctx, &owner)
require.Nil(t, err)
integration := NewCertManager(client, cmmeta.ObjectReference{
Kind: "ClusterIssuer",
Name: "my-issuer",
integration := NewCertManager(client, certmanager.Certificate{
Spec: certmanager.CertificateSpec{
IssuerRef: cmmeta.ObjectReference{
Kind: "ClusterIssuer",
Name: "my-issuer",
},
},
})

// Nothing should be created if no hosts or no tls is set
Expand Down
1 change: 1 addition & 0 deletions internal/integrations/externaldns.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func (e *externalDNS) UpdateResource(
if err := reconcileMetadata(owner, &resource, e.client.Scheme()); err != nil {
return nil
}

// Spec
resource.Spec.Endpoints = e.endpoints(info.Hosts, targets)
return nil
Expand Down
21 changes: 18 additions & 3 deletions internal/integrations/utils.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
package integrations

import (
"fmt"

"github.com/imdario/mergo"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
)

func reconcileMetadata(source metav1.Object, target metav1.Object, scheme *runtime.Scheme) error {
func reconcileMetadata(
owner metav1.Object, target metav1.Object, scheme *runtime.Scheme, sources ...metav1.Object,
) error {
// Reconcile labels
labels := defaultEmpty(target.GetLabels())
labels[managedByLabelKey] = "switchboard"
for _, source := range sources {
if err := mergo.MergeWithOverwrite(&labels, source.GetLabels()); err != nil {
return fmt.Errorf("failed to update labels: %s", err)
}
}
target.SetLabels(labels)

// Reconcile annotations
annotations := defaultEmpty(target.GetAnnotations())
if ingressClass, ok := source.GetAnnotations()[ingressAnnotationKey]; ok {
if ingressClass, ok := owner.GetAnnotations()[ingressAnnotationKey]; ok {
annotations[ingressAnnotationKey] = ingressClass
} else {
delete(annotations, ingressAnnotationKey)
}
for _, source := range sources {
if err := mergo.MergeWithOverwrite(&annotations, source.GetAnnotations()); err != nil {
return fmt.Errorf("failed to update annotations: %s", err)
}
}
target.SetAnnotations(annotations)

// Set controller reference
if err := ctrl.SetControllerReference(source, target, scheme); err != nil {
if err := ctrl.SetControllerReference(owner, target, scheme); err != nil {
return err
}
return nil
Expand Down
13 changes: 13 additions & 0 deletions internal/integrations/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/borchero/switchboard/internal/k8tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
Expand Down Expand Up @@ -35,6 +36,18 @@ func TestReconcileMetadata(t *testing.T) {
assert.Len(t, target.OwnerReferences, 1)
assert.Len(t, target.Annotations, 1)
assert.Len(t, target.Labels, 1)

// Check whether additional annotations and labels are copied
meta := metav1.ObjectMeta{
Labels: map[string]string{"my-label": "my-value"},
Annotations: map[string]string{"my-annotation-1": "1", "my-annotation-2": "2"},
}
target = k8tests.DummyService("your-name", "my-namespace", 8080)
err = reconcileMetadata(&parent, &target, scheme, &meta)
require.Nil(t, err)
assert.Len(t, target.OwnerReferences, 1)
assert.Len(t, target.Annotations, 3)
assert.Len(t, target.Labels, 2)
}

func TestDefaultEmpty(t *testing.T) {
Expand Down

0 comments on commit 464b219

Please sign in to comment.