@@ -4,12 +4,12 @@ import (
4
4
"context"
5
5
"encoding/json"
6
6
"fmt"
7
- "net/http"
8
-
9
7
"github.com/go-logr/logr"
10
- corev1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1"
8
+ configv1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1"
11
9
corev1 "k8s.io/api/core/v1"
10
+ "k8s.io/apimachinery/pkg/api/errors"
12
11
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12
+ "net/http"
13
13
"sigs.k8s.io/controller-runtime/pkg/client"
14
14
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
15
15
)
@@ -42,66 +42,107 @@ func (m *PodMutator) Handle(ctx context.Context, req admission.Request) admissio
42
42
return admission .Allowed ("openfeature is disabled" )
43
43
}
44
44
}
45
- // Check if the pod is static or orphaned
46
- name := pod .Name
47
- if len (pod .GetOwnerReferences ()) != 0 {
48
- name = pod .GetOwnerReferences ()[0 ].Name
49
- } else {
50
- return admission .Denied ("static or orphaned pods cannot be mutated" )
51
- }
52
45
53
- var featureFlagCustomResource corev1alpha1.FeatureFlagConfiguration
54
- // Check CustomResource
46
+ // Check configuration
55
47
val , ok = pod .GetAnnotations ()["openfeature.dev/featureflagconfiguration" ]
56
48
if ! ok {
57
49
return admission .Allowed ("FeatureFlagConfiguration not found" )
58
- } else {
59
- // Current limitation is to use the same namespace, this is easy to fix though
60
- // e.g. namespace/name check
61
- err = m .Client .Get (context .TODO (), client.ObjectKey {Name : val ,
62
- Namespace : req .Namespace },
63
- & featureFlagCustomResource )
50
+ }
51
+
52
+ // Check if the pod is static or orphaned
53
+ if len (pod .GetOwnerReferences ()) == 0 {
54
+ return admission .Denied ("static or orphaned pods cannot be mutated" )
55
+ }
56
+
57
+ // Check for ConfigMap and create it if it doesn't exist
58
+ cm := corev1.ConfigMap {}
59
+ if err := m .Client .Get (ctx , client.ObjectKey {Name : val , Namespace : req .Namespace }, & cm ); errors .IsNotFound (err ) {
60
+ err := m .CreateConfigMap (ctx , val , req .Namespace , pod )
64
61
if err != nil {
65
- return admission .Denied ("FeatureFlagConfiguration not found" )
62
+ m .Log .V (1 ).Info (fmt .Sprintf ("failed to create config map %s error: %s" , val , err .Error ()))
63
+ return admission .Errored (http .StatusInternalServerError , err )
66
64
}
67
65
}
68
- // TODO: this should be a short sha to avoid collisions
69
- configName := name
70
- // Create the agent configmap
71
- m .Client .Delete (context .TODO (), & corev1.ConfigMap {
72
- ObjectMeta : metav1.ObjectMeta {
73
- Name : configName ,
74
- Namespace : req .Namespace ,
75
- },
76
- }) // Delete the configmap if it exists
77
66
78
- m .Log .V (1 ).Info (fmt .Sprintf ("Creating configmap %s" , configName ))
79
- if err := m .Client .Create (ctx , & corev1.ConfigMap {
67
+ if ! CheckOwnerReference (pod , cm ) {
68
+ reference := pod .OwnerReferences [0 ]
69
+ reference .Controller = m .falseVal ()
70
+ cm .OwnerReferences = append (cm .OwnerReferences , reference )
71
+ err := m .Client .Update (ctx , & cm )
72
+ if err != nil {
73
+ m .Log .V (1 ).Info (fmt .Sprintf ("failed to update owner reference for %s error: %s" , val , err .Error ()))
74
+ }
75
+ }
76
+
77
+ marshaledPod , err := m .InjectSidecar (pod , val )
78
+ if err != nil {
79
+ return admission .Errored (http .StatusInternalServerError , err )
80
+ }
81
+
82
+ return admission .PatchResponseFromRaw (req .Object .Raw , marshaledPod )
83
+ }
84
+
85
+ // PodMutator implements admission.DecoderInjector.
86
+ // A decoder will be automatically injected.
87
+
88
+ // InjectDecoder injects the decoder.
89
+ func (m * PodMutator ) InjectDecoder (d * admission.Decoder ) error {
90
+ m .decoder = d
91
+ return nil
92
+ }
93
+
94
+ func CheckOwnerReference (pod * corev1.Pod , cm corev1.ConfigMap ) bool {
95
+ for _ , cmOwner := range cm .OwnerReferences {
96
+ for _ , podOwner := range pod .OwnerReferences {
97
+ if cmOwner == podOwner {
98
+ return true
99
+ }
100
+ }
101
+ }
102
+ return false
103
+ }
104
+
105
+ func (m * PodMutator ) CreateConfigMap (ctx context.Context , name string , namespace string , pod * corev1.Pod ) error {
106
+ m .Log .V (1 ).Info (fmt .Sprintf ("Creating configmap %s" , name ))
107
+ reference := pod .OwnerReferences [0 ]
108
+ reference .Controller = m .falseVal ()
109
+
110
+ spec := m .GetFeatureFlagSpec (ctx , name , namespace )
111
+ cm := corev1.ConfigMap {
80
112
ObjectMeta : metav1.ObjectMeta {
81
- Name : configName ,
82
- Namespace : req . Namespace ,
113
+ Name : name ,
114
+ Namespace : namespace ,
83
115
Annotations : map [string ]string {
84
- "openfeature.dev/featureflagconfiguration" : featureFlagCustomResource .Name ,
116
+ "openfeature.dev/featureflagconfiguration" : name ,
117
+ },
118
+ OwnerReferences : []metav1.OwnerReference {
119
+ reference ,
85
120
},
86
121
},
87
- //TODO
88
122
Data : map [string ]string {
89
- "config.yaml" : featureFlagCustomResource . Spec .FeatureFlagSpec ,
123
+ "config.yaml" : spec .FeatureFlagSpec ,
90
124
},
91
- }); err != nil {
125
+ }
126
+ return m .Client .Create (ctx , & cm )
127
+ }
92
128
93
- m .Log .V (1 ).Info (fmt .Sprintf ("failed to create config map %s error: %s" , configName , err .Error ()))
94
- return admission .Errored (http .StatusInternalServerError , err )
129
+ func (m * PodMutator ) GetFeatureFlagSpec (ctx context.Context , name string , namespace string ) configv1alpha1.FeatureFlagConfigurationSpec {
130
+ ffConfig := configv1alpha1.FeatureFlagConfiguration {}
131
+ if err := m .Client .Get (ctx , client.ObjectKey {Name : name , Namespace : namespace }, & ffConfig ); errors .IsNotFound (err ) {
132
+ return configv1alpha1.FeatureFlagConfigurationSpec {}
95
133
}
134
+ return ffConfig .Spec
135
+ }
96
136
137
+ func (m * PodMutator ) InjectSidecar (pod * corev1.Pod , configMap string ) ([]byte , error ) {
97
138
m .Log .V (1 ).Info (fmt .Sprintf ("Creating sidecar for pod %s/%s" , pod .Namespace , pod .Name ))
98
139
// Inject the agent
99
140
pod .Spec .Volumes = append (pod .Spec .Volumes , corev1.Volume {
100
141
Name : "flagd-config" ,
101
142
VolumeSource : corev1.VolumeSource {
102
143
ConfigMap : & corev1.ConfigMapVolumeSource {
103
144
LocalObjectReference : corev1.LocalObjectReference {
104
- Name : configName ,
145
+ Name : configMap ,
105
146
},
106
147
},
107
148
},
@@ -119,20 +160,10 @@ func (m *PodMutator) Handle(ctx context.Context, req admission.Request) admissio
119
160
},
120
161
},
121
162
})
122
-
123
- marshaledPod , err := json .Marshal (pod )
124
- if err != nil {
125
- return admission .Errored (http .StatusInternalServerError , err )
126
- }
127
-
128
- return admission .PatchResponseFromRaw (req .Object .Raw , marshaledPod )
163
+ return json .Marshal (pod )
129
164
}
130
165
131
- // PodMutator implements admission.DecoderInjector.
132
- // A decoder will be automatically injected.
133
-
134
- // InjectDecoder injects the decoder.
135
- func (m * PodMutator ) InjectDecoder (d * admission.Decoder ) error {
136
- m .decoder = d
137
- return nil
166
+ func (m * PodMutator ) falseVal () * bool {
167
+ b := false
168
+ return & b
138
169
}
0 commit comments