Skip to content

Commit 630db44

Browse files
merge
Signed-off-by: James Milligan <[email protected]>
2 parents a9c0d13 + 04bcf68 commit 630db44

File tree

9 files changed

+242
-54
lines changed

9 files changed

+242
-54
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ IMG ?= controller:latest
44
# customize overlay to be used in the build, DEFAULT or HELM
55
KUSTOMIZE_OVERLAY ?= DEFAULT
66
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
7-
FLAGD_VERSION=v0.3.0
7+
FLAGD_VERSION=v0.3.1
88
CHART_VERSION=v0.2.23# x-release-please-version
99
ENVTEST_K8S_VERSION = 1.25
1010

docs/getting_started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ kubectl describe pod busybox-curl-7bd5767999-spf7v
7272
```
7373
```yaml
7474
flagd:
75-
Image: ghcr.io/open-feature/flagd:vX.X.X
75+
Image: ghcr.io/open-feature/flagd:v0.3.1
7676
Port: 8014/TCP
7777
Host Port: 0/TCP
7878
Args:

docs/permissions.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ The `proxy-role` definition can be found [here](../config/rbac/auth_proxy_role.y
4444
### Flagd Kubernetes Sync
4545

4646
The `flagd-kubernetes-sync` role providers the permission to get, watch and list all `core.openfeature.dev` resources, permitting the kubernetes sync feature in injected `flagd` containers.
47-
Its definition can be found [here](../config/rbac/flagd_kubernetes_sync_clusterrole.yaml)
47+
Its definition can be found [here](../config/rbac/flagd_kubernetes_sync_clusterrole.yaml).
48+
During startup the operator will backfill permissions to the `flagd-kubernetes-sync` cluster role binding from the current state of the cluster, adding all service accounts from pods with the `core.openfeature.dev/enabled` annotation set to `"true"`, preventing unexpected behavior during upgrades.
4849

4950
| API Group | Resource | Verbs |
5051
| ----------- | ----------- | ----------- |

main.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"context"
2021
"flag"
2122
"os"
2223

@@ -131,6 +132,17 @@ func main() {
131132
os.Exit(1)
132133
}
133134

135+
// setup indexer for backfilling permissions on the flagd-kubernetes-sync role binding
136+
if err := mgr.GetFieldIndexer().IndexField(
137+
context.Background(),
138+
&corev1.Pod{},
139+
webhooks.OpenFeatureEnabledAnnotationPath,
140+
webhooks.OpenFeatureEnabledAnnotationIndex,
141+
); err != nil {
142+
setupLog.Error(err, "unable to create indexer", "webhook", webhooks.OpenFeatureEnabledAnnotationPath)
143+
os.Exit(1)
144+
}
145+
134146
if err = (&controllers.FeatureFlagConfigurationReconciler{
135147
Client: mgr.GetClient(),
136148
Scheme: mgr.GetScheme(),
@@ -181,10 +193,7 @@ func main() {
181193
Client: mgr.GetClient(),
182194
Log: ctrl.Log.WithName("mutating-pod-webhook"),
183195
}
184-
podMutator.BackfillPermissions()
185-
hookServer.Register("/mutate-v1-pod", &webhook.Admission{
186-
Handler: podMutator,
187-
})
196+
hookServer.Register("/mutate-v1-pod", &webhook.Admission{Handler: podMutator})
188197
hookServer.Register("/validate-v1alpha1-featureflagconfiguration", &webhook.Admission{Handler: &webhooks.FeatureFlagConfigurationValidator{
189198
Client: mgr.GetClient(),
190199
Log: ctrl.Log.WithName("validating-featureflagconfiguration-webhook"),
@@ -200,7 +209,19 @@ func main() {
200209
}
201210

202211
setupLog.Info("starting manager")
203-
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
212+
ctx := ctrl.SetupSignalHandler()
213+
errChan := make(chan error, 1)
214+
go func(chan error) {
215+
if err := mgr.Start(ctx); err != nil {
216+
errChan <- err
217+
}
218+
}(errChan)
219+
220+
setupLog.Info("restoring flagd-kubernetes-sync cluster role binding subjects from current cluster state")
221+
// backfill can be handled asynchronously, so we do not need to block via the channel
222+
go podMutator.BackfillPermissions(ctx, make(chan struct{}, 1))
223+
224+
if err := <-errChan; err != nil {
204225
setupLog.Error(err, "problem running manager")
205226
os.Exit(1)
206227
}

renovate.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,19 @@
22
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
33
"extends": [
44
"config:base"
5+
],
6+
"regexManagers": [
7+
{
8+
"fileMatch": ["^Makefile$"],
9+
"matchStrings": ["FLAGD_VERSION=(?<currentValue>.*?)\\n"],
10+
"depNameTemplate": "open-feature/flagd",
11+
"datasourceTemplate": "github-releases"
12+
},
13+
{
14+
"fileMatch": ["^docs/getting_started.md$"],
15+
"matchStrings": ["ghcr\\.io\\/open-feature\\/flagd:(?<currentValue>.*?)\\n"],
16+
"depNameTemplate": "open-feature/flagd",
17+
"datasourceTemplate": "github-releases"
18+
}
519
]
620
}

webhooks/pod_webhook.go

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77
"net/http"
88
"reflect"
99
"strings"
10+
"time"
11+
12+
goErr "errors"
1013

1114
"github.com/go-logr/logr"
1215
corev1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1"
@@ -15,15 +18,18 @@ import (
1518
v1 "k8s.io/api/rbac/v1"
1619
"k8s.io/apimachinery/pkg/api/errors"
1720
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
"sigs.k8s.io/controller-runtime/pkg/cache"
1822
"sigs.k8s.io/controller-runtime/pkg/client"
1923
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
2024
)
2125

2226
// we likely want these to be configurable, eventually
2327
const (
24-
FlagDImagePullPolicy corev1.PullPolicy = "Always"
25-
clusterRoleBindingName string = "open-feature-operator-flagd-kubernetes-sync"
26-
fileSyncMountPath string = "/etc/flagd/"
28+
FlagDImagePullPolicy corev1.PullPolicy = "Always"
29+
clusterRoleBindingName string = "open-feature-operator-flagd-kubernetes-sync"
30+
flagdMetricPortEnvVar string = "FLAGD_METRICS_PORT"
31+
fileSyncMountPath string = "/etc/flagd/"
32+
OpenFeatureEnabledAnnotationPath = "metadata.annotations.openfeature.dev/enabled"
2733
)
2834

2935
// NOTE: RBAC not needed here.
@@ -42,9 +48,45 @@ type PodMutator struct {
4248
Log logr.Logger
4349
}
4450

45-
func (m *PodMutator) BackfillPermissions() {
46-
podList := &corev1.PodList{}
47-
err := m.Client.List(context.Background(), podList, client.MatchingLabels{"somelabel": "someval"})
51+
// BackfillPermissions recovers the state of the flagd-kubernetes-sync role binding in the event of upgrade
52+
func (m *PodMutator) BackfillPermissions(ctx context.Context, backfillComplete chan struct{}) {
53+
defer func() {
54+
backfillComplete <- struct{}{}
55+
}()
56+
57+
for i := 0; i < 5; i++ {
58+
// fetch all pods with the "openfeature.dev/enabled" annotation set to "true"
59+
podList := &corev1.PodList{}
60+
err := m.Client.List(ctx, podList, client.MatchingFields{OpenFeatureEnabledAnnotationPath: "true"})
61+
if err != nil {
62+
if !goErr.Is(err, &cache.ErrCacheNotStarted{}) {
63+
m.Log.Error(err, "unable to list annotated pods", "webhook", OpenFeatureEnabledAnnotationPath)
64+
return
65+
}
66+
time.Sleep(1 * time.Second)
67+
continue
68+
}
69+
70+
// add each new service account to the flagd-kubernetes-sync role binding
71+
for _, pod := range podList.Items {
72+
m.Log.V(1).Info(fmt.Sprintf("backfilling permissions for pod %s/%s", pod.Namespace, pod.Name))
73+
if err := m.enableClusterRoleBinding(ctx, &pod); err != nil {
74+
m.Log.Error(
75+
err,
76+
fmt.Sprintf("unable backfill permissions for pod %s/%s", pod.Namespace, pod.Name),
77+
"webhook",
78+
OpenFeatureEnabledAnnotationPath,
79+
)
80+
}
81+
}
82+
return
83+
}
84+
err := goErr.New("unable to backfill permissions for the flagd-kubernetes-sync role binding: timeout")
85+
m.Log.Error(
86+
err,
87+
"webhook",
88+
OpenFeatureEnabledAnnotationPath,
89+
)
4890
}
4991

5092
// Handle injects the flagd sidecar (if the prerequisites are all met)
@@ -429,3 +471,27 @@ func setSecurityContext() *corev1.SecurityContext {
429471
},
430472
}
431473
}
474+
475+
func OpenFeatureEnabledAnnotationIndex(o client.Object) []string {
476+
pod := o.(*corev1.Pod)
477+
if pod.ObjectMeta.Annotations == nil {
478+
return []string{
479+
"false",
480+
}
481+
}
482+
val, ok := pod.ObjectMeta.Annotations["openfeature.dev/enabled"]
483+
if ok && val == "true" {
484+
return []string{
485+
"true",
486+
}
487+
}
488+
val, ok = pod.ObjectMeta.Annotations["openfeature.dev"]
489+
if ok && val == "enabled" {
490+
return []string{
491+
"true",
492+
}
493+
}
494+
return []string{
495+
"false",
496+
}
497+
}

0 commit comments

Comments
 (0)