Skip to content

Commit b7fac95

Browse files
nammncghislai
andauthored
CLOUDP-186866 - Allow override of labels and annotations (#1337)
* Merge labels and annotations on created StatefulSets, Pods, and PersistentVolumeClaims --------- Co-authored-by: cghislai <charlyghislain@gmail.com>
1 parent 7cdfdc0 commit b7fac95

File tree

11 files changed

+294
-2
lines changed

11 files changed

+294
-2
lines changed

api/v1/mongodbcommunity_types.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ type OverrideProcess struct {
343343
type StatefulSetConfiguration struct {
344344
// +kubebuilder:pruning:PreserveUnknownFields
345345
SpecWrapper StatefulSetSpecWrapper `json:"spec"`
346+
// +optional
347+
MetadataWrapper StatefulSetMetadataWrapper `json:"metadata"`
346348
}
347349

348350
type LogLevel string
@@ -385,6 +387,21 @@ func (m *StatefulSetSpecWrapper) DeepCopy() *StatefulSetSpecWrapper {
385387
}
386388
}
387389

390+
// StatefulSetMetadataWrapper is a wrapper around Labels and Annotations
391+
type StatefulSetMetadataWrapper struct {
392+
// +optional
393+
Labels map[string]string `json:"labels,omitempty"`
394+
// +optional
395+
Annotations map[string]string `json:"annotations,omitempty"`
396+
}
397+
398+
func (m *StatefulSetMetadataWrapper) DeepCopy() *StatefulSetMetadataWrapper {
399+
return &StatefulSetMetadataWrapper{
400+
Labels: m.Labels,
401+
Annotations: m.Annotations,
402+
}
403+
}
404+
388405
// MongodConfiguration holds the optional mongod configuration
389406
// that should be merged with the operator created one.
390407
type MongodConfiguration struct {

config/crd/bases/mongodbcommunity.mongodb.com_mongodbcommunity.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,19 @@ spec:
337337
description: StatefulSetConfiguration holds the optional custom StatefulSet
338338
that should be merged into the operator created one.
339339
properties:
340+
metadata:
341+
description: StatefulSetMetadataWrapper is a wrapper around Labels
342+
and Annotations
343+
properties:
344+
annotations:
345+
additionalProperties:
346+
type: string
347+
type: object
348+
labels:
349+
additionalProperties:
350+
type: string
351+
type: object
352+
type: object
340353
spec:
341354
type: object
342355
x-kubernetes-preserve-unknown-fields: true
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
apiVersion: mongodbcommunity.mongodb.com/v1
2+
kind: MongoDBCommunity
3+
metadata:
4+
name: mdb0
5+
spec:
6+
members: 3
7+
type: ReplicaSet
8+
version: "4.2.6"
9+
security:
10+
authentication:
11+
modes: [ "SCRAM" ]
12+
users:
13+
- name: my-user
14+
db: admin
15+
passwordSecretRef: # a reference to the secret that will be used to generate the user's password
16+
name: my-user-password
17+
roles:
18+
- name: clusterAdmin
19+
db: admin
20+
- name: userAdminAnyDatabase
21+
db: admin
22+
scramCredentialsSecretName: my-scram
23+
additionalMongodConfig:
24+
storage.wiredTiger.engineConfig.journalCompressor: zlib
25+
26+
statefulSet:
27+
metadata:
28+
annotations:
29+
statefulSetAnnotationTest: testValue
30+
labels:
31+
statefulSetLabelTest: testValue
32+
spec:
33+
selector:
34+
matchLabels:
35+
podTemplateLabelTest: testValue
36+
37+
template:
38+
metadata:
39+
annotations:
40+
podTemplateAnnotationTest: testValue
41+
labels:
42+
podTemplateLabelTest: testValue
43+
44+
volumeClaimTemplates:
45+
- metadata:
46+
name: data-volume
47+
annotations:
48+
pvcTemplateAnnotationTest: testValue
49+
labels:
50+
pvcTemplateLabelTest: testValue
51+
52+
---
53+
apiVersion: v1
54+
kind: Secret
55+
metadata:
56+
name: my-user-password
57+
type: Opaque
58+
stringData:
59+
password: <your-password-here>

controllers/replica_set_controller.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,10 @@ func buildStatefulSetModificationFunction(mdb mdbv1.MongoDBCommunity) statefulse
756756
),
757757

758758
statefulset.WithCustomSpecs(mdb.Spec.StatefulSetConfiguration.SpecWrapper.Spec),
759+
statefulset.WithObjectMetadata(
760+
mdb.Spec.StatefulSetConfiguration.MetadataWrapper.Labels,
761+
mdb.Spec.StatefulSetConfiguration.MetadataWrapper.Annotations,
762+
),
759763
)
760764
}
761765

pkg/kube/statefulset/statefulset.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,13 @@ func WithCustomSpecs(spec appsv1.StatefulSetSpec) Modification {
306306
}
307307
}
308308

309+
func WithObjectMetadata(labels map[string]string, annotations map[string]string) Modification {
310+
return func(set *appsv1.StatefulSet) {
311+
WithLabels(labels)(set)
312+
WithAnnotations(annotations)(set)
313+
}
314+
}
315+
309316
func findVolumeClaimIndexByName(name string, pvcs []corev1.PersistentVolumeClaim) int {
310317
for idx, pvc := range pvcs {
311318
if pvc.Name == name {

pkg/kube/statefulset/statefulset_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,35 @@ func TestWithAnnotations(t *testing.T) {
235235
WithAnnotations(nil)(&sts)
236236
assert.Len(t, sts.Annotations, 2)
237237
}
238+
239+
func TestWithObjectMetadata(t *testing.T) {
240+
sts, err := defaultStatefulSetBuilder().Build()
241+
assert.NoError(t, err)
242+
assert.Len(t, sts.Labels, 0)
243+
assert.Len(t, sts.Annotations, 0)
244+
245+
// handles nil values gracefully
246+
{
247+
WithObjectMetadata(nil, nil)(&sts)
248+
}
249+
250+
// Test that it works when there are no annotations
251+
{
252+
WithObjectMetadata(map[string]string{"label": "a"}, map[string]string{"annotation": "b"})(&sts)
253+
assert.Equal(t, "b", sts.Annotations["annotation"])
254+
assert.Equal(t, "a", sts.Labels["label"])
255+
}
256+
257+
// test that WithObjectMetadata merges the maps
258+
{
259+
WithObjectMetadata(map[string]string{"label2": "a"}, map[string]string{"annotation2": "b"})(&sts)
260+
assert.Equal(t, "b", sts.Annotations["annotation"])
261+
assert.Equal(t, "b", sts.Annotations["annotation2"])
262+
}
263+
264+
// Test that we can override a key
265+
{
266+
WithObjectMetadata(map[string]string{"label": "b"}, map[string]string{"annotation": "b"})(&sts)
267+
assert.Equal(t, "b", sts.Annotations["annotation"])
268+
}
269+
}

pkg/util/merge/merge_statefulset.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ func PersistentVolumeClaim(defaultPvc corev1.PersistentVolumeClaim, overridePvc
170170
defaultPvc.Namespace = overridePvc.Namespace
171171
}
172172

173+
defaultPvc.Labels = StringToStringMap(defaultPvc.Labels, overridePvc.Labels)
174+
defaultPvc.Annotations = StringToStringMap(defaultPvc.Annotations, overridePvc.Annotations)
175+
173176
if overridePvc.Spec.VolumeMode != nil {
174177
defaultPvc.Spec.VolumeMode = overridePvc.Spec.VolumeMode
175178
}

requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ docker==4.3.1
44
kubernetes==26.1.0
55
jinja2==2.11.3
66
MarkupSafe==2.0.1
7-
PyYAML==5.4.1
7+
PyYAML==6.0.1
88
black==22.3.0
99
mypy==0.961
1010
tqdm==v4.49.0
1111
boto3==1.16.21
1212
pymongo==3.11.4
1313
dnspython==2.0.0
1414
requests==2.31.0
15-
pyyaml==5.4.1
1615
ruamel.yaml==0.17.9
1716
semver==2.13.0
1817
rsa>=4.7 # not directly required, pinned by Snyk to avoid a vulnerability

test/e2e/e2eutil.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ func TestLabels() map[string]string {
2828
}
2929
}
3030

31+
// TestAnnotations create an annotations map
32+
func TestAnnotations() map[string]string {
33+
return map[string]string{
34+
"e2e-test-annotated": "true",
35+
}
36+
}
37+
3138
func TestDataDir() string {
3239
return envvar.GetEnvOrDefault(testDataDirEnv, "/workspace/testdata")
3340
}

test/e2e/mongodbtests/mongodbtests.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"sigs.k8s.io/controller-runtime/pkg/client"
8+
"strings"
79
"testing"
810
"time"
911

@@ -217,6 +219,7 @@ func containsVolume(volumes []corev1.PersistentVolume, volumeName string) bool {
217219
}
218220
return false
219221
}
222+
220223
func HasExpectedPersistentVolumes(volumes []corev1.PersistentVolume) func(t *testing.T) {
221224
return func(t *testing.T) {
222225
volumeList, err := getPersistentVolumesList()
@@ -230,6 +233,77 @@ func HasExpectedPersistentVolumes(volumes []corev1.PersistentVolume) func(t *tes
230233
}
231234
}
232235
}
236+
func HasExpectedMetadata(mdb *mdbv1.MongoDBCommunity, expectedLabels map[string]string, expectedAnnotations map[string]string) func(t *testing.T) {
237+
return func(t *testing.T) {
238+
namespace := mdb.Namespace
239+
240+
statefulSetList := appsv1.StatefulSetList{}
241+
err := e2eutil.TestClient.Client.List(context.TODO(), &statefulSetList, client.InNamespace(namespace))
242+
assert.NoError(t, err)
243+
assert.NotEmpty(t, statefulSetList.Items)
244+
for _, s := range statefulSetList.Items {
245+
containsMetadata(t, &s.ObjectMeta, expectedLabels, expectedAnnotations, "statefulset "+s.Name)
246+
}
247+
248+
volumeList := corev1.PersistentVolumeList{}
249+
err = e2eutil.TestClient.Client.List(context.TODO(), &volumeList, client.InNamespace(namespace))
250+
assert.NoError(t, err)
251+
assert.NotEmpty(t, volumeList.Items)
252+
for _, s := range volumeList.Items {
253+
volName := s.Name
254+
if strings.HasPrefix(volName, "data-volume-") || strings.HasPrefix(volName, "logs-volume-") {
255+
containsMetadata(t, &s.ObjectMeta, expectedLabels, expectedAnnotations, "volume "+volName)
256+
}
257+
}
258+
259+
podList := corev1.PodList{}
260+
err = e2eutil.TestClient.Client.List(context.TODO(), &podList, client.InNamespace(namespace))
261+
assert.NoError(t, err)
262+
assert.NotEmpty(t, podList.Items)
263+
264+
for _, s := range podList.Items {
265+
// only consider stateful-sets (as opposite to the controller replica set)
266+
for _, owner := range s.OwnerReferences {
267+
if owner.Kind == "ReplicaSet" {
268+
continue
269+
}
270+
}
271+
// Ignore non-owned pods
272+
if len(s.OwnerReferences) == 0 {
273+
continue
274+
}
275+
276+
// Ensure we are considering pods owned by a stateful set
277+
hasStatefulSetOwner := false
278+
for _, owner := range s.OwnerReferences {
279+
if owner.Kind == "StatefulSet" {
280+
hasStatefulSetOwner = true
281+
}
282+
}
283+
if !hasStatefulSetOwner {
284+
continue
285+
}
286+
287+
containsMetadata(t, &s.ObjectMeta, expectedLabels, expectedAnnotations, "pod "+s.Name)
288+
}
289+
}
290+
}
291+
292+
func containsMetadata(t *testing.T, metadata *metav1.ObjectMeta, expectedLabels map[string]string, expectedAnnotations map[string]string, msg string) {
293+
labels := metadata.Labels
294+
for k, v := range expectedLabels {
295+
assert.Contains(t, labels, k, msg+" has label "+k)
296+
value := labels[k]
297+
assert.Equal(t, v, value, msg+" has label "+k+" with value "+v)
298+
}
299+
300+
annotations := metadata.Annotations
301+
for k, v := range expectedAnnotations {
302+
assert.Contains(t, annotations, k, msg+" has annotation "+k)
303+
value := annotations[k]
304+
assert.Equal(t, v, value, msg+" has annotation "+k+" with value "+v)
305+
}
306+
}
233307

234308
// MongoDBReachesPendingPhase ensures the MongoDB resources gets to the Pending phase
235309
func MongoDBReachesPendingPhase(mdb *mdbv1.MongoDBCommunity) func(t *testing.T) {

0 commit comments

Comments
 (0)