Skip to content

Commit 16d3212

Browse files
committed
add test
Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>
1 parent 1a8bba6 commit 16d3212

File tree

5 files changed

+199
-23
lines changed

5 files changed

+199
-23
lines changed

cmd/app/app.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,12 @@ func NewCommand() *cobra.Command {
6060
eventBroadcaster.StartLogging(func(format string, args ...interface{}) { mlog.V(3).Info(fmt.Sprintf(format, args...)) })
6161
eventBroadcaster.StartRecordingToSink(&clientv1.EventSinkImpl{Interface: cl.CoreV1().Events("")})
6262

63-
scheme := trustapi.GlobalScheme
6463
mgr, err := ctrl.NewManager(opts.RestConfig, ctrl.Options{
65-
Scheme: scheme,
64+
Scheme: trustapi.GlobalScheme,
6665
EventBroadcaster: eventBroadcaster,
6766
LeaderElection: true,
6867
LeaderElectionNamespace: opts.Bundle.Namespace,
69-
NewCache: bundle.NewCacheFunc(scheme, opts.Bundle),
68+
NewCache: bundle.NewCacheFunc(opts.Bundle),
7069
LeaderElectionID: "trust-manager-leader-election",
7170
LeaderElectionReleaseOnCancel: true,
7271
ReadinessEndpointName: opts.ReadyzPath,
Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package internal
17+
package cache
1818

1919
import (
2020
"context"
@@ -56,7 +56,7 @@ type multiScopedCache struct {
5656
// resources in the given namespace. namespacedInformers is the set of resource
5757
// types which should only be watched in the given namespace.
5858
// namespacedInformers expects Namespaced resource types.
59-
func NewMultiScopedCache(scheme *runtime.Scheme, namespace string, namespacedInformers []schema.GroupKind) cache.NewCacheFunc {
59+
func NewMultiScopedCache(namespace string, namespacedInformers []schema.GroupKind) cache.NewCacheFunc {
6060
return func(config *rest.Config, opts cache.Options) (cache.Cache, error) {
6161
namespacedOpts := opts
6262
namespacedOpts.Namespace = namespace
@@ -71,8 +71,9 @@ func NewMultiScopedCache(scheme *runtime.Scheme, namespace string, namespacedInf
7171
if err != nil {
7272
return nil, err
7373
}
74+
7475
return &multiScopedCache{
75-
scheme: scheme,
76+
scheme: opts.Scheme,
7677
namespacedCache: namespacedCache,
7778
clusterCache: clusterCache,
7879
namespacedInformers: namespacedInformers,
@@ -85,13 +86,23 @@ func (b *multiScopedCache) GetInformer(ctx context.Context, obj client.Object) (
8586
if err := setGroupVersionKind(b.scheme, obj); err != nil {
8687
return nil, err
8788
}
88-
return b.cacheFromGVK(obj.GetObjectKind().GroupVersionKind()).GetInformer(ctx, obj)
89+
90+
cache, err := b.cacheFromGVK(obj.GetObjectKind().GroupVersionKind())
91+
if err != nil {
92+
return nil, err
93+
}
94+
return cache.GetInformer(ctx, obj)
8995
}
9096

9197
// GetInformerForKind returns the underlying cache's GetInformerForKind based
9298
// on resource type.
9399
func (b *multiScopedCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind) (cache.Informer, error) {
94-
return b.cacheFromGVK(gvk).GetInformerForKind(ctx, gvk)
100+
101+
cache, err := b.cacheFromGVK(gvk)
102+
if err != nil {
103+
return nil, err
104+
}
105+
return cache.GetInformerForKind(ctx, gvk)
95106
}
96107

97108
// Start starts both the cluster and namespaced caches. Returned is an
@@ -137,46 +148,61 @@ func (b *multiScopedCache) IndexField(ctx context.Context, obj client.Object, fi
137148
if err := setGroupVersionKind(b.scheme, obj); err != nil {
138149
return err
139150
}
140-
return b.cacheFromGVK(obj.GetObjectKind().GroupVersionKind()).IndexField(ctx, obj, field, extractValue)
151+
152+
cache, err := b.cacheFromGVK(obj.GetObjectKind().GroupVersionKind())
153+
if err != nil {
154+
return err
155+
}
156+
return cache.IndexField(ctx, obj, field, extractValue)
141157
}
142158

143159
// Get returns the underlying cache's Get based on resource type.
144160
func (b *multiScopedCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error {
145161
if err := setGroupVersionKind(b.scheme, obj); err != nil {
146162
return err
147163
}
148-
return b.cacheFromGVK(obj.GetObjectKind().GroupVersionKind()).Get(ctx, key, obj)
164+
165+
cache, err := b.cacheFromGVK(obj.GetObjectKind().GroupVersionKind())
166+
if err != nil {
167+
return err
168+
}
169+
return cache.Get(ctx, key, obj)
149170
}
150171

151172
// List returns the underlying cache's List based on resource type.
152173
func (b *multiScopedCache) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
153174
if err := setGroupVersionKind(b.scheme, list); err != nil {
154175
return err
155176
}
156-
return b.cacheFromGVK(list.GetObjectKind().GroupVersionKind()).List(ctx, list, opts...)
177+
178+
cache, err := b.cacheFromGVK(list.GetObjectKind().GroupVersionKind())
179+
if err != nil {
180+
return err
181+
}
182+
return cache.List(ctx, list, opts...)
157183
}
158184

159185
// cacheFromGVK returns either the cluster or namespaced cache, based on the
160186
// resource type given.
161-
func (b *multiScopedCache) cacheFromGVK(gvk schema.GroupVersionKind) cache.Cache {
187+
func (b *multiScopedCache) cacheFromGVK(gvk schema.GroupVersionKind) (cache.Cache, error) {
162188
if gvk.Group == "" && gvk.Kind == "" {
163-
panic("The Group and/or Kind must be set")
189+
return nil, fmt.Errorf("the Group and/or Kind must be set")
164190
}
165191

166192
for _, namespacedInformer := range b.namespacedInformers {
167193
if namespacedInformer.Group == gvk.Group && namespacedInformer.Kind == gvk.Kind {
168-
return b.namespacedCache
194+
return b.namespacedCache, nil
169195
}
170196
}
171-
return b.clusterCache
197+
return b.clusterCache, nil
172198
}
173199

174200
// setGroupVersionKind populates the Group and Kind fields of obj using the
175201
// scheme type registry.
176202
// Inspired by https://github.com/kubernetes-sigs/controller-runtime/issues/1735#issuecomment-984763173
177203
func setGroupVersionKind(scheme *runtime.Scheme, obj runtime.Object) error {
178204
gvk := obj.GetObjectKind().GroupVersionKind()
179-
if gvk.Group != "" || gvk.Kind != "" {
205+
if gvk.Group != "" || gvk.Kind != "" || scheme == nil {
180206
return nil // eg. in case of PartialMetadata, we don't want to overwrite the Group/ Kind
181207
}
182208

pkg/bundle/controller.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323

2424
corev1 "k8s.io/api/core/v1"
2525
apierrors "k8s.io/apimachinery/pkg/api/errors"
26-
"k8s.io/apimachinery/pkg/runtime"
2726
"k8s.io/apimachinery/pkg/runtime/schema"
2827
"k8s.io/apimachinery/pkg/types"
2928
"k8s.io/utils/clock"
@@ -38,7 +37,7 @@ import (
3837
"sigs.k8s.io/controller-runtime/pkg/source"
3938

4039
trustapi "github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1"
41-
"github.com/cert-manager/trust-manager/pkg/bundle/internal"
40+
multiscopedcache "github.com/cert-manager/trust-manager/pkg/bundle/cache"
4241
"github.com/cert-manager/trust-manager/pkg/fspkg"
4342
)
4443

@@ -195,6 +194,6 @@ func (b *bundle) mustBundleList(ctx context.Context) *trustapi.BundleList {
195194

196195
// NewCacheFunc will return a multi-scoped controller-runtime NewCacheFunc
197196
// where Secret resources will only be watched within the trust Namespace.
198-
func NewCacheFunc(scheme *runtime.Scheme, opts Options) cache.NewCacheFunc {
199-
return internal.NewMultiScopedCache(scheme, opts.Namespace, []schema.GroupKind{{Kind: "Secret"}})
197+
func NewCacheFunc(opts Options) cache.NewCacheFunc {
198+
return multiscopedcache.NewMultiScopedCache(opts.Namespace, []schema.GroupKind{{Kind: "Secret"}})
200199
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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 test
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
. "github.com/onsi/ginkgo/v2"
25+
. "github.com/onsi/gomega"
26+
27+
corev1 "k8s.io/api/core/v1"
28+
rbacv1 "k8s.io/api/rbac/v1"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/runtime"
31+
"k8s.io/apimachinery/pkg/runtime/schema"
32+
"k8s.io/client-go/rest"
33+
"sigs.k8s.io/controller-runtime/pkg/cache"
34+
"sigs.k8s.io/controller-runtime/pkg/client"
35+
36+
multiscopedcache "github.com/cert-manager/trust-manager/pkg/bundle/cache"
37+
)
38+
39+
var _ = Describe("Integration test cache", func() {
40+
It("should be possible to Get a resource without having cluster-wide List & Watch permissions", func() {
41+
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
42+
defer cancel()
43+
44+
namespace := "test-namespace"
45+
46+
// Create a service account that can only retrieve secrets in a single namespace.
47+
var cacheRestConfig *rest.Config
48+
{
49+
godClient, err := client.New(env.Config, client.Options{})
50+
Expect(err).NotTo(HaveOccurred())
51+
52+
ns := &corev1.Namespace{
53+
ObjectMeta: metav1.ObjectMeta{
54+
Name: namespace,
55+
},
56+
}
57+
err = godClient.Create(ctx, ns)
58+
Expect(err).NotTo(HaveOccurred())
59+
60+
sa := &corev1.ServiceAccount{
61+
ObjectMeta: metav1.ObjectMeta{
62+
Name: "cache-sa",
63+
Namespace: namespace,
64+
},
65+
}
66+
err = godClient.Create(ctx, sa)
67+
Expect(err).NotTo(HaveOccurred())
68+
69+
role := &rbacv1.Role{
70+
ObjectMeta: metav1.ObjectMeta{
71+
Name: "cache-role",
72+
Namespace: namespace,
73+
},
74+
Rules: []rbacv1.PolicyRule{
75+
{
76+
Verbs: []string{"list", "watch"},
77+
APIGroups: []string{""},
78+
Resources: []string{"secrets"},
79+
},
80+
},
81+
}
82+
err = godClient.Create(ctx, role)
83+
Expect(err).NotTo(HaveOccurred())
84+
85+
rolebinding := rbacv1.RoleBinding{
86+
ObjectMeta: metav1.ObjectMeta{
87+
Name: "cache-rolebinding",
88+
Namespace: namespace,
89+
},
90+
RoleRef: rbacv1.RoleRef{
91+
APIGroup: "rbac.authorization.k8s.io",
92+
Kind: "Role",
93+
Name: "cache-role",
94+
},
95+
Subjects: []rbacv1.Subject{
96+
{
97+
Kind: "ServiceAccount",
98+
Name: "cache-sa",
99+
Namespace: namespace,
100+
},
101+
},
102+
}
103+
err = godClient.Create(ctx, &rolebinding)
104+
Expect(err).NotTo(HaveOccurred())
105+
106+
// Create a config that uses the service account.
107+
cacheRestConfig = rest.CopyConfig(env.Config)
108+
cacheRestConfig.Impersonate.UserName = fmt.Sprintf("system:serviceaccount:%s:%s", namespace, "cache-sa")
109+
cacheRestConfig.Impersonate.UID = string(sa.UID)
110+
111+
// Create a secret that the service account can access.
112+
secret := &corev1.Secret{
113+
ObjectMeta: metav1.ObjectMeta{
114+
Name: "test-secret",
115+
Namespace: namespace,
116+
},
117+
Data: map[string][]byte{
118+
"test": []byte("test"),
119+
},
120+
}
121+
err = godClient.Create(ctx, secret)
122+
Expect(err).NotTo(HaveOccurred())
123+
}
124+
125+
newCache := multiscopedcache.NewMultiScopedCache(namespace, []schema.GroupKind{{Group: "", Kind: "Secret"}})
126+
127+
scheme := runtime.NewScheme()
128+
Expect(corev1.AddToScheme(scheme)).NotTo(HaveOccurred())
129+
cache, err := newCache(cacheRestConfig, cache.Options{
130+
Scheme: scheme,
131+
})
132+
Expect(err).NotTo(HaveOccurred())
133+
134+
done := make(chan error)
135+
go func() {
136+
done <- cache.Start(ctx)
137+
}()
138+
defer func() {
139+
Expect(<-done).NotTo(HaveOccurred())
140+
}()
141+
142+
Expect(cache.WaitForCacheSync(ctx)).To(BeTrue())
143+
144+
secret := &corev1.Secret{}
145+
err = cache.Get(ctx, client.ObjectKey{
146+
Namespace: namespace,
147+
Name: "test-secret",
148+
}, secret)
149+
Expect(err).NotTo(HaveOccurred())
150+
151+
Expect(secret.Data["test"]).To(Equal([]byte("test")))
152+
})
153+
})

test/integration/bundle/suite.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,9 @@ var _ = Describe("Integration", func() {
9090
DefaultPackageLocation: tmpFileName,
9191
}
9292

93-
scheme := trustapi.GlobalScheme
9493
mgr, err = ctrl.NewManager(env.Config, ctrl.Options{
95-
Scheme: scheme,
96-
NewCache: bundle.NewCacheFunc(scheme, opts),
94+
Scheme: trustapi.GlobalScheme,
95+
NewCache: bundle.NewCacheFunc(opts),
9796
// TODO: can we disable leader election here? The mgr goroutine prints extra output we probably don't need
9897
// and it might not be valuable to enable leader election here
9998
LeaderElection: true,

0 commit comments

Comments
 (0)