Skip to content

Commit e9da8b6

Browse files
authored
Merge pull request #114 from kluctl/fix-watch
Fix permissions errors when creating watches
2 parents c7999e9 + 27a0f9a commit e9da8b6

5 files changed

+63
-24
lines changed

controllers/objecttemplate_controller.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
ctrl "sigs.k8s.io/controller-runtime"
3333
"sigs.k8s.io/controller-runtime/pkg/builder"
3434
"sigs.k8s.io/controller-runtime/pkg/client"
35+
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
3536
"sigs.k8s.io/controller-runtime/pkg/controller"
3637
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3738
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -193,11 +194,11 @@ func (r *ObjectTemplateReconciler) doReconcile(ctx context.Context, rt *template
193194
newObjects := map[templatesv1alpha1.ObjectRef]struct{}{}
194195
for _, me := range rt.Spec.Matrix {
195196
if me.Object != nil {
196-
newObjects[me.Object.Ref] = struct{}{}
197-
err = wt.addWatchForObject(ctx, me.Object.Ref)
197+
refWithNs, err := wt.addWatchForObject(ctx, me.Object.Ref)
198198
if err != nil {
199199
return err
200200
}
201+
newObjects[refWithNs] = struct{}{}
201202
}
202203
}
203204
wt.removeDeletedWatches(ctx, newObjects)
@@ -234,11 +235,11 @@ func (r *ObjectTemplateReconciler) doReconcile(ctx context.Context, rt *template
234235
}
235236

236237
for _, x := range allResources {
237-
rm, err := r.Client.RESTMapper().RESTMapping(x.GroupVersionKind().GroupKind(), x.GroupVersionKind().Version)
238+
isNs, err := apiutil.IsGVKNamespaced(x.GroupVersionKind(), objClient.RESTMapper())
238239
if err != nil {
239240
return err
240241
}
241-
if rm.Scope.Name() == apimeta.RESTScopeNameNamespace && x.GetNamespace() == "" {
242+
if isNs && x.GetNamespace() == "" {
242243
x.SetNamespace(rt.Namespace)
243244
}
244245
}

controllers/objecttemplate_controller_test.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,33 @@ var _ = Describe("ObjectTemplate controller", func() {
153153
})
154154
It("Should succeed after the SA is being created", func() {
155155
createServiceAccount("non-existent", ns)
156-
createRoleWithBinding("non-existent", ns, []string{"configmaps"})
156+
createRoleWithBinding("non-existent", ns, []string{"configmaps", "secrets"})
157157
triggerReconcile(key)
158158
waitUntiReconciled(key, timeout)
159-
assertAppliedConfigMaps(key, cmKey)
159+
t2 := getObjectTemplate(key)
160+
c := getReadyCondition(t2.GetConditions())
161+
Expect(c.Status).To(Equal(metav1.ConditionTrue))
162+
})
163+
It("Should still succeed if the matrix input has no namespace", func() {
164+
Expect(k8sClient.Create(ctx, buildTestSecret("m2", ns, map[string]string{
165+
"k3": "3",
166+
}))).To(Succeed())
167+
updateObjectTemplate(key, func(t *templatesv1alpha1.ObjectTemplate) {
168+
// we test the existing matrix entry to be changed
169+
t.Spec.Matrix[0].Object.Ref.Namespace = ""
170+
// and a new one being added
171+
t.Spec.Matrix = append(t.Spec.Matrix, buildMatrixObjectEntry("m2", "m2", "", "Secret", "", false))
172+
t.Spec.Templates[0].Object = buildTestConfigMap(cmKey.Name, cmKey.Namespace, map[string]string{
173+
"k1": `{{ matrix.m1.data.k1 + matrix.m1.data.k2 + matrix.m2.data.k3 }}`,
174+
})
175+
})
176+
waitUntiReconciled(key, timeout)
177+
t2 := getObjectTemplate(key)
178+
c := getReadyCondition(t2.GetConditions())
179+
Expect(c.Status).To(Equal(metav1.ConditionTrue))
180+
assertConfigMapData(cmKey, map[string]string{
181+
"k1": "MQ==Mg==Mw==", // three base64 encoded strings got added
182+
})
160183
})
161184
It("Should cleanup", func() {
162185
Expect(k8sClient.Delete(ctx, t)).To(Succeed())

controllers/texttemplate_controller.go

+5-9
Original file line numberDiff line numberDiff line change
@@ -129,29 +129,25 @@ func (r *TextTemplateReconciler) doReconcile(ctx context.Context, tt *templatesv
129129
wt.setClient(ctx, objClient, tt.Spec.ServiceAccountName)
130130
newObjects := map[templatesv1alpha1.ObjectRef]struct{}{}
131131
if tt.Spec.TemplateRef != nil && tt.Spec.TemplateRef.ConfigMap != nil {
132-
ns := tt.Spec.TemplateRef.ConfigMap.Namespace
133-
if ns == "" {
134-
ns = tt.Namespace
135-
}
136132
objRef := templatesv1alpha1.ObjectRef{
137133
APIVersion: "v1",
138134
Kind: "ConfigMap",
139-
Namespace: ns,
135+
Namespace: tt.Spec.TemplateRef.ConfigMap.Namespace,
140136
Name: tt.Spec.TemplateRef.ConfigMap.Name,
141137
}
142-
err = wt.addWatchForObject(ctx, objRef)
138+
refWithNs, err := wt.addWatchForObject(ctx, objRef)
143139
if err != nil {
144140
return err
145141
}
146-
newObjects[objRef] = struct{}{}
142+
newObjects[refWithNs] = struct{}{}
147143
}
148144
for _, me := range tt.Spec.Inputs {
149145
if me.Object != nil {
150-
err = wt.addWatchForObject(ctx, me.Object.Ref)
146+
refWithNs, err := wt.addWatchForObject(ctx, me.Object.Ref)
151147
if err != nil {
152148
return err
153149
}
154-
newObjects[me.Object.Ref] = struct{}{}
150+
newObjects[refWithNs] = struct{}{}
155151
}
156152
}
157153
wt.removeDeletedWatches(ctx, newObjects)

controllers/texttemplate_controller_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@ var _ = Describe("TextTemplate controller", func() {
103103
Expect(c.Status).To(Equal(metav1.ConditionTrue))
104104
assertTextTemplateResult(key, "v1")
105105
})
106+
It("Should still succeed when the input object has no namespace", func() {
107+
updateTextTemplate(key, func(t *templatesv1alpha1.TextTemplate) {
108+
t.Spec.Inputs[0].Object.Ref.Namespace = ""
109+
})
110+
waitUntiTextTemplateReconciled(key, timeout)
111+
t2 := getTextTemplate(key)
112+
c := getReadyCondition(t2.GetConditions())
113+
Expect(c.Status).To(Equal(metav1.ConditionTrue))
114+
})
106115
It("Should respect the jsonPath", func() {
107116
updateTextTemplate(key, func(t *templatesv1alpha1.TextTemplate) {
108117
p := ".data"

controllers/watches_util.go

+19-9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"k8s.io/apimachinery/pkg/watch"
1313
"net/http"
1414
"sigs.k8s.io/controller-runtime/pkg/client"
15+
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
1516
"sigs.k8s.io/controller-runtime/pkg/controller"
1617
"sigs.k8s.io/controller-runtime/pkg/event"
1718
"sigs.k8s.io/controller-runtime/pkg/handler"
@@ -87,24 +88,33 @@ func (wt *watchesForTemplate) setClient(ctx context.Context, objClient client.Wi
8788
wt.client = objClient
8889
}
8990

90-
func (wt *watchesForTemplate) addWatchForObject(ctx context.Context, objectRef templatesv1alpha1.ObjectRef) error {
91+
func (wt *watchesForTemplate) addWatchForObject(ctx context.Context, objectRef templatesv1alpha1.ObjectRef) (templatesv1alpha1.ObjectRef, error) {
9192
logger := log.FromContext(ctx)
9293

94+
gvk, err := objectRef.GroupVersionKind()
95+
if err != nil {
96+
return objectRef, err
97+
}
98+
99+
// if namespace is not set, default to the template's namespace
100+
if objectRef.Namespace == "" {
101+
if isNs, err := apiutil.IsGVKNamespaced(gvk, wt.client.RESTMapper()); err != nil {
102+
return objectRef, fmt.Errorf("failed to determine if object is namespaced: %w", err)
103+
} else if isNs {
104+
objectRef.Namespace = wt.templateKey.Namespace
105+
}
106+
}
107+
93108
wt.mutex.Lock()
94109
defer wt.mutex.Unlock()
95110

96111
w := wt.watches[objectRef]
97112
if w != nil {
98-
return nil
113+
return objectRef, nil
99114
}
100115

101116
logger.V(1).Info("Starting watch for object", "templateKey", wt.templateKey, "objectRef", objectRef)
102117

103-
gvk, err := objectRef.GroupVersionKind()
104-
if err != nil {
105-
return err
106-
}
107-
108118
// this is a single-object watch that does NOT require global watch permissions!
109119
var dummy unstructured.UnstructuredList
110120
dummy.SetGroupVersionKind(gvk)
@@ -120,7 +130,7 @@ func (wt *watchesForTemplate) addWatchForObject(ctx context.Context, objectRef t
120130
err = fmt.Errorf("watch for %s \"%s\" is forbidden: %w", gvk.Kind, objectRef.Name, err)
121131
}
122132
}
123-
return err
133+
return objectRef, err
124134
}
125135
wt.watches[objectRef] = w
126136

@@ -132,7 +142,7 @@ func (wt *watchesForTemplate) addWatchForObject(ctx context.Context, objectRef t
132142
}
133143
}()
134144

135-
return nil
145+
return objectRef, nil
136146
}
137147

138148
func (wt *watchesForTemplate) removeDeletedWatches(ctx context.Context, newRefs map[templatesv1alpha1.ObjectRef]struct{}) {

0 commit comments

Comments
 (0)