Skip to content

Commit 90e4b94

Browse files
committed
feat: inject bundle data into configmap
Signed-off-by: Erik Godding Boye <egboye@gmail.com>
1 parent 834957c commit 90e4b94

File tree

3 files changed

+340
-0
lines changed

3 files changed

+340
-0
lines changed

pkg/bundle/inject/controller.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
Copyright 2021 The cert-manager Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package inject
18+
19+
import (
20+
"context"
21+
"crypto/sha256"
22+
"encoding/json"
23+
"fmt"
24+
25+
corev1 "k8s.io/api/core/v1"
26+
apierrors "k8s.io/apimachinery/pkg/api/errors"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/types"
29+
v1 "k8s.io/client-go/applyconfigurations/core/v1"
30+
ctrl "sigs.k8s.io/controller-runtime"
31+
"sigs.k8s.io/controller-runtime/pkg/builder"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/predicate"
34+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
35+
36+
"github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1"
37+
"github.com/cert-manager/trust-manager/pkg/bundle/internal/ssa_client"
38+
)
39+
40+
const (
41+
BundleInjectLabelKey = "trust-manager.io/inject-bundle"
42+
)
43+
44+
var configMap = &metav1.PartialObjectMetadata{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "ConfigMap"}}
45+
46+
type Injector struct {
47+
client.Client
48+
}
49+
50+
func (i *Injector) SetupWithManager(mgr ctrl.Manager) error {
51+
return ctrl.NewControllerManagedBy(mgr).
52+
Named("configmap-injector").
53+
For(configMap,
54+
builder.WithPredicates(
55+
hasLabel(BundleInjectLabelKey),
56+
)).
57+
Complete(i)
58+
}
59+
60+
func (i *Injector) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
61+
target := configMap.DeepCopy()
62+
63+
if err := i.Get(ctx, request.NamespacedName, target); err != nil {
64+
if apierrors.IsNotFound(err) {
65+
return reconcile.Result{}, nil
66+
}
67+
return reconcile.Result{}, err
68+
}
69+
70+
bundleName := target.GetLabels()[BundleInjectLabelKey]
71+
if bundleName == "" {
72+
return reconcile.Result{}, nil
73+
}
74+
75+
bundle := &v1alpha1.Bundle{}
76+
if err := i.Get(ctx, types.NamespacedName{Name: bundleName}, bundle); err != nil {
77+
return reconcile.Result{}, fmt.Errorf("failed to look up bundle %q: %w", bundleName, err)
78+
}
79+
80+
data := map[string]string{"ca.crt": "bundle data"}
81+
dataHash := fmt.Sprintf("%x", sha256.Sum256([]byte("bundle data hash")))
82+
83+
applyConfig := v1.ConfigMap(request.Name, request.Namespace).
84+
WithAnnotations(map[string]string{v1alpha1.BundleHashAnnotationKey: dataHash}).
85+
WithData(data)
86+
87+
return reconcile.Result{}, patchConfigMap(ctx, i.Client, applyConfig)
88+
}
89+
90+
type Cleaner struct {
91+
client.Client
92+
}
93+
94+
func (c *Cleaner) SetupWithManager(mgr ctrl.Manager) error {
95+
return ctrl.NewControllerManagedBy(mgr).
96+
Named("configmap-injector-cleaner").
97+
For(configMap,
98+
builder.WithPredicates(
99+
hasAnnotation(v1alpha1.BundleHashAnnotationKey),
100+
predicate.Not(hasLabel(BundleInjectLabelKey)),
101+
)).
102+
Complete(c)
103+
}
104+
105+
func (c *Cleaner) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
106+
applyConfig := v1.ConfigMap(request.Name, request.Namespace)
107+
108+
return reconcile.Result{}, patchConfigMap(ctx, c.Client, applyConfig)
109+
}
110+
111+
func patchConfigMap(ctx context.Context, c client.Client, applyConfig *v1.ConfigMapApplyConfiguration) error {
112+
obj := &corev1.ConfigMap{
113+
ObjectMeta: metav1.ObjectMeta{
114+
Name: *applyConfig.Name,
115+
Namespace: *applyConfig.Namespace,
116+
},
117+
}
118+
119+
encodedPatch, err := json.Marshal(applyConfig)
120+
if err != nil {
121+
return err
122+
}
123+
124+
return c.Patch(ctx, obj, ssa_client.ApplyPatch{Patch: encodedPatch}, ssa_client.FieldManager, client.ForceOwnership)
125+
}
126+
127+
func hasLabel(key string) predicate.Predicate {
128+
return predicate.NewPredicateFuncs(func(obj client.Object) bool {
129+
_, ok := obj.GetLabels()[key]
130+
return ok
131+
})
132+
}
133+
134+
func hasAnnotation(key string) predicate.Predicate {
135+
return predicate.NewPredicateFuncs(func(obj client.Object) bool {
136+
_, ok := obj.GetAnnotations()[key]
137+
return ok
138+
})
139+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Copyright 2021 The cert-manager Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package inject
18+
19+
import (
20+
corev1 "k8s.io/api/core/v1"
21+
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"
22+
23+
"github.com/cert-manager/trust-manager/pkg/bundle/inject"
24+
25+
. "github.com/onsi/ginkgo/v2"
26+
. "github.com/onsi/gomega"
27+
)
28+
29+
var _ = Describe("Injector", func() {
30+
var namespace string
31+
32+
BeforeEach(func() {
33+
ns := &corev1.Namespace{}
34+
ns.GenerateName = "inject-"
35+
Expect(k8sClient.Create(ctx, ns)).To(Succeed())
36+
namespace = ns.Name
37+
})
38+
39+
It("should inject bundle data when ConfigMap labeled", func() {
40+
cm := &corev1.ConfigMap{}
41+
cm.GenerateName = "cm-"
42+
cm.Namespace = namespace
43+
cm.Labels = map[string]string{
44+
inject.BundleInjectLabelKey: bundleName,
45+
"app": "my-app",
46+
}
47+
cm.Data = map[string]string{
48+
"tls.crt": "bar",
49+
"tls.key": "baz",
50+
}
51+
Expect(k8sClient.Create(ctx, cm)).To(Succeed())
52+
53+
// Wait for ConfigMap to be processed by controller
54+
Eventually(komega.Object(cm)).Should(HaveField("Data", HaveKeyWithValue("ca.crt", "bundle data")))
55+
Expect(cm.Labels).To(HaveKeyWithValue("app", "my-app"))
56+
57+
By("removing label from ConfigMap, it should remove bundle data", func() {
58+
Expect(komega.Update(cm, func() {
59+
delete(cm.Labels, inject.BundleInjectLabelKey)
60+
})()).To(Succeed())
61+
62+
// Wait for ConfigMap to be processed by controller
63+
Eventually(komega.Object(cm)).Should(HaveField("Data", Not(HaveKey("ca.crt"))))
64+
Expect(cm.Labels).To(HaveKeyWithValue("app", "my-app"))
65+
Expect(cm.Data).To(Equal(map[string]string{
66+
"tls.crt": "bar",
67+
"tls.key": "baz",
68+
}))
69+
})
70+
})
71+
})
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
Copyright 2021 The cert-manager Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package inject
18+
19+
import (
20+
"context"
21+
"path"
22+
"testing"
23+
24+
"k8s.io/client-go/rest"
25+
"k8s.io/utils/ptr"
26+
ctrl "sigs.k8s.io/controller-runtime"
27+
"sigs.k8s.io/controller-runtime/pkg/client"
28+
"sigs.k8s.io/controller-runtime/pkg/envtest"
29+
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"
30+
logf "sigs.k8s.io/controller-runtime/pkg/log"
31+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
32+
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
33+
34+
trustapi "github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1"
35+
"github.com/cert-manager/trust-manager/pkg/bundle/inject"
36+
"github.com/cert-manager/trust-manager/test/dummy"
37+
38+
. "github.com/onsi/ginkgo/v2"
39+
. "github.com/onsi/gomega"
40+
)
41+
42+
const bundleName = "my-bundle"
43+
44+
var (
45+
cfg *rest.Config
46+
k8sClient client.Client
47+
testEnv *envtest.Environment
48+
ctx context.Context
49+
cancel context.CancelFunc
50+
)
51+
52+
func TestAPIs(t *testing.T) {
53+
ctx = t.Context()
54+
RegisterFailHandler(Fail)
55+
RunSpecs(t, "Controller Suite")
56+
}
57+
58+
var _ = BeforeSuite(func() {
59+
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
60+
61+
_, cancel = context.WithCancel(ctx)
62+
63+
By("bootstrapping test environment")
64+
testEnv = &envtest.Environment{
65+
UseExistingCluster: ptr.To(false),
66+
CRDDirectoryPaths: []string{
67+
path.Join("..", "..", "..", "..", "deploy", "crds"),
68+
},
69+
ErrorIfCRDPathMissing: true,
70+
Scheme: trustapi.GlobalScheme,
71+
}
72+
73+
var err error
74+
// cfg is defined in this file globally.
75+
cfg, err = testEnv.Start()
76+
Expect(err).NotTo(HaveOccurred())
77+
Expect(cfg).NotTo(BeNil())
78+
79+
k8sClient, err = client.New(cfg, client.Options{Scheme: trustapi.GlobalScheme})
80+
Expect(err).NotTo(HaveOccurred())
81+
Expect(k8sClient).NotTo(BeNil())
82+
komega.SetClient(k8sClient)
83+
84+
setupBundle()
85+
86+
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
87+
Client: client.Options{Cache: &client.CacheOptions{Unstructured: true}},
88+
Scheme: trustapi.GlobalScheme,
89+
Metrics: server.Options{
90+
// Disable metrics server to avoid port conflict
91+
BindAddress: "0",
92+
},
93+
})
94+
Expect(err).NotTo(HaveOccurred())
95+
96+
injector := &inject.Injector{
97+
Client: k8sManager.GetClient(),
98+
}
99+
Expect(injector.SetupWithManager(k8sManager)).To(Succeed())
100+
cleaner := &inject.Cleaner{
101+
Client: k8sManager.GetClient(),
102+
}
103+
Expect(cleaner.SetupWithManager(k8sManager)).To(Succeed())
104+
105+
go func() {
106+
defer GinkgoRecover()
107+
var ctrlCtx context.Context
108+
ctrlCtx, cancel = context.WithCancel(ctrl.SetupSignalHandler())
109+
Expect(k8sManager.Start(ctrlCtx)).To(Succeed())
110+
}()
111+
})
112+
113+
var _ = AfterSuite(func() {
114+
cancel()
115+
116+
By("tearing down the test environment")
117+
err := testEnv.Stop()
118+
Expect(err).NotTo(HaveOccurred())
119+
})
120+
121+
func setupBundle() {
122+
bundle := &trustapi.Bundle{}
123+
bundle.Name = bundleName
124+
bundle.Spec.Sources = []trustapi.BundleSource{{
125+
InLine: ptr.To(dummy.TestCertificate1),
126+
}}
127+
128+
err := k8sClient.Create(ctx, bundle)
129+
Expect(err).NotTo(HaveOccurred())
130+
}

0 commit comments

Comments
 (0)