Skip to content

Commit 464b219

Browse files
authored
Allow to specify full certificate template for cert-manager integration (#18)
1 parent fa228be commit 464b219

12 files changed

+89
-36
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
77
github.com/borchero/zeus v1.0.0
88
github.com/google/uuid v1.3.0
9+
github.com/imdario/mergo v0.3.13
910
github.com/jetstack/cert-manager v1.7.2
1011
github.com/stretchr/testify v1.7.0
1112
github.com/traefik/traefik/v2 v2.6.6
@@ -47,7 +48,6 @@ require (
4748
github.com/google/gofuzz v1.2.0 // indirect
4849
github.com/gorilla/context v1.1.1 // indirect
4950
github.com/gorilla/mux v1.8.0 // indirect
50-
github.com/imdario/mergo v0.3.12 // indirect
5151
github.com/josharian/intern v1.0.0 // indirect
5252
github.com/json-iterator/go v1.1.12 // indirect
5353
github.com/mailru/easyjson v0.7.7 // indirect

go.sum

+2-1
Original file line numberDiff line numberDiff line change
@@ -923,8 +923,9 @@ github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
923923
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
924924
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
925925
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
926-
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
927926
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
927+
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
928+
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
928929
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
929930
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
930931
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=

internal/config/v1/config_types.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package v1
22

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

4142
// CertManagerIntegrationConfig describes the configuration for the cert-manager integration.
4243
type CertManagerIntegrationConfig struct {
43-
Issuer IssuerRef `json:"issuer"`
44+
Template v1.Certificate `json:"certificateTemplate"`
4445
}
4546

4647
// ServiceRef uniquely describes a Kubernetes service.

internal/config/v1/zz_generated.deepcopy.go

+7-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/controllers/ingressroute_test.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
configv1 "github.com/borchero/switchboard/internal/config/v1"
99
"github.com/borchero/switchboard/internal/k8tests"
1010
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
11+
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
1112
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/require"
1314
traefik "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
@@ -199,11 +200,11 @@ func runTest(t *testing.T, test testCase) {
199200
assert.Nil(t, err)
200201
assert.ElementsMatch(t, test.DNSNames, certificate.Spec.DNSNames)
201202
assert.Equal(t,
202-
config.Integrations.CertManager.Issuer.Kind,
203+
config.Integrations.CertManager.Template.Spec.IssuerRef.Kind,
203204
certificate.Spec.IssuerRef.Kind,
204205
)
205206
assert.Equal(t,
206-
config.Integrations.CertManager.Issuer.Name,
207+
config.Integrations.CertManager.Template.Spec.IssuerRef.Name,
207208
certificate.Spec.IssuerRef.Name,
208209
)
209210
assert.Equal(t, test.Ingress.Spec.TLS.SecretName, certificate.Spec.SecretName)
@@ -254,9 +255,13 @@ func createConfig(service *v1.Service) configv1.Config {
254255
},
255256
},
256257
CertManager: &configv1.CertManagerIntegrationConfig{
257-
Issuer: configv1.IssuerRef{
258-
Kind: "ClusterIssuer",
259-
Name: "my-issuer",
258+
Template: certmanager.Certificate{
259+
Spec: certmanager.CertificateSpec{
260+
IssuerRef: cmmeta.ObjectReference{
261+
Kind: "ClusterIssuer",
262+
Name: "my-issuer",
263+
},
264+
},
260265
},
261266
},
262267
},

internal/controllers/utils.go

+1-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/borchero/switchboard/internal/integrations"
99
"github.com/borchero/switchboard/internal/k8s"
1010
"github.com/borchero/switchboard/internal/switchboard"
11-
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
1211
traefik "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
1312
"go.uber.org/zap"
1413
"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -44,12 +43,7 @@ func integrationsFromConfig(
4443

4544
certManager := config.Integrations.CertManager
4645
if certManager != nil {
47-
result = append(result, integrations.NewCertManager(
48-
client, cmmeta.ObjectReference{
49-
Kind: certManager.Issuer.Kind,
50-
Name: certManager.Issuer.Name,
51-
},
52-
))
46+
result = append(result, integrations.NewCertManager(client, certManager.Template))
5347
}
5448
return result, nil
5549
}

internal/controllers/utils_test.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55

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

3133
config.Integrations.ExternalDNS = nil
3234
config.Integrations.CertManager = &configv1.CertManagerIntegrationConfig{
33-
Issuer: configv1.IssuerRef{Kind: "ClusterIssuer", Name: "my-issuer"},
35+
Template: certmanager.Certificate{
36+
Spec: certmanager.CertificateSpec{
37+
IssuerRef: cmmeta.ObjectReference{
38+
Kind: "ClusterIssuer",
39+
Name: "my-issuer",
40+
},
41+
},
42+
},
3443
}
3544
integrations, err = integrationsFromConfig(config, client)
3645
require.Nil(t, err)

internal/integrations/certmanager.go

+17-11
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,23 @@ import (
55
"fmt"
66

77
"github.com/borchero/switchboard/internal/k8s"
8+
"github.com/imdario/mergo"
89
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
9-
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
10+
v1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
1011
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1112
"sigs.k8s.io/controller-runtime/pkg/client"
1213
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1314
)
1415

1516
type certManager struct {
16-
client client.Client
17-
issuer cmmeta.ObjectReference
17+
client client.Client
18+
template v1.Certificate
1819
}
1920

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

2627
func (*certManager) Name() string {
@@ -52,14 +53,19 @@ func (c *certManager) UpdateResource(
5253
resource := certmanager.Certificate{ObjectMeta: c.objectMeta(owner)}
5354
if _, err := controllerutil.CreateOrPatch(ctx, c.client, &resource, func() error {
5455
// Meta
55-
if err := reconcileMetadata(owner, &resource, c.client.Scheme()); err != nil {
56-
return err
56+
if err := reconcileMetadata(
57+
owner, &resource, c.client.Scheme(), &c.template.ObjectMeta,
58+
); err != nil {
59+
return fmt.Errorf("failed to reconcile metadata: %s", err)
5760
}
61+
5862
// Spec
59-
resource.Spec.SecretName = *info.TLSSecretName
60-
resource.Spec.DNSNames = info.Hosts
61-
resource.Spec.IssuerRef.Kind = c.issuer.Kind
62-
resource.Spec.IssuerRef.Name = c.issuer.Name
63+
template := c.template.Spec.DeepCopy()
64+
template.SecretName = *info.TLSSecretName
65+
template.DNSNames = info.Hosts
66+
if err := mergo.Merge(&resource.Spec, template, mergo.WithOverride); err != nil {
67+
return fmt.Errorf("failed to reconcile specification: %s", err)
68+
}
6369
return nil
6470
}); err != nil {
6571
return fmt.Errorf("failed to upsert TLS certificate: %w", err)

internal/integrations/certmanager_test.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ func TestCertManagerUpdateResource(t *testing.T) {
2525
owner := k8tests.DummyService("my-service", namespace, 80)
2626
err := client.Create(ctx, &owner)
2727
require.Nil(t, err)
28-
integration := NewCertManager(client, cmmeta.ObjectReference{
29-
Kind: "ClusterIssuer",
30-
Name: "my-issuer",
28+
integration := NewCertManager(client, certmanager.Certificate{
29+
Spec: certmanager.CertificateSpec{
30+
IssuerRef: cmmeta.ObjectReference{
31+
Kind: "ClusterIssuer",
32+
Name: "my-issuer",
33+
},
34+
},
3135
})
3236

3337
// Nothing should be created if no hosts or no tls is set

internal/integrations/externaldns.go

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func (e *externalDNS) UpdateResource(
7373
if err := reconcileMetadata(owner, &resource, e.client.Scheme()); err != nil {
7474
return nil
7575
}
76+
7677
// Spec
7778
resource.Spec.Endpoints = e.endpoints(info.Hosts, targets)
7879
return nil

internal/integrations/utils.go

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,43 @@
11
package integrations
22

33
import (
4+
"fmt"
5+
6+
"github.com/imdario/mergo"
47
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
58
"k8s.io/apimachinery/pkg/runtime"
69
ctrl "sigs.k8s.io/controller-runtime"
710
)
811

9-
func reconcileMetadata(source metav1.Object, target metav1.Object, scheme *runtime.Scheme) error {
12+
func reconcileMetadata(
13+
owner metav1.Object, target metav1.Object, scheme *runtime.Scheme, sources ...metav1.Object,
14+
) error {
1015
// Reconcile labels
1116
labels := defaultEmpty(target.GetLabels())
1217
labels[managedByLabelKey] = "switchboard"
18+
for _, source := range sources {
19+
if err := mergo.MergeWithOverwrite(&labels, source.GetLabels()); err != nil {
20+
return fmt.Errorf("failed to update labels: %s", err)
21+
}
22+
}
1323
target.SetLabels(labels)
1424

1525
// Reconcile annotations
1626
annotations := defaultEmpty(target.GetAnnotations())
17-
if ingressClass, ok := source.GetAnnotations()[ingressAnnotationKey]; ok {
27+
if ingressClass, ok := owner.GetAnnotations()[ingressAnnotationKey]; ok {
1828
annotations[ingressAnnotationKey] = ingressClass
1929
} else {
2030
delete(annotations, ingressAnnotationKey)
2131
}
32+
for _, source := range sources {
33+
if err := mergo.MergeWithOverwrite(&annotations, source.GetAnnotations()); err != nil {
34+
return fmt.Errorf("failed to update annotations: %s", err)
35+
}
36+
}
2237
target.SetAnnotations(annotations)
2338

2439
// Set controller reference
25-
if err := ctrl.SetControllerReference(source, target, scheme); err != nil {
40+
if err := ctrl.SetControllerReference(owner, target, scheme); err != nil {
2641
return err
2742
}
2843
return nil

internal/integrations/utils_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/borchero/switchboard/internal/k8tests"
77
"github.com/stretchr/testify/assert"
88
"github.com/stretchr/testify/require"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
910
"k8s.io/apimachinery/pkg/runtime"
1011
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
1112
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
@@ -35,6 +36,18 @@ func TestReconcileMetadata(t *testing.T) {
3536
assert.Len(t, target.OwnerReferences, 1)
3637
assert.Len(t, target.Annotations, 1)
3738
assert.Len(t, target.Labels, 1)
39+
40+
// Check whether additional annotations and labels are copied
41+
meta := metav1.ObjectMeta{
42+
Labels: map[string]string{"my-label": "my-value"},
43+
Annotations: map[string]string{"my-annotation-1": "1", "my-annotation-2": "2"},
44+
}
45+
target = k8tests.DummyService("your-name", "my-namespace", 8080)
46+
err = reconcileMetadata(&parent, &target, scheme, &meta)
47+
require.Nil(t, err)
48+
assert.Len(t, target.OwnerReferences, 1)
49+
assert.Len(t, target.Annotations, 3)
50+
assert.Len(t, target.Labels, 2)
3851
}
3952

4053
func TestDefaultEmpty(t *testing.T) {

0 commit comments

Comments
 (0)