Skip to content

Commit 31bfc68

Browse files
committed
Allow to remap key name when mirroring secret
Use reflector.v1.k8s.emberstack.com/reflection-key-mapping and reflector.v1.k8s.emberstack.com/reflection-auto-key-mapping to do the renaming See README.md for details
1 parent a9571d3 commit 31bfc68

File tree

7 files changed

+113
-19
lines changed

7 files changed

+113
-19
lines changed

README.md

+33
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ $ kubectl -n kube-system apply -f https://github.com/emberstack/kubernetes-refle
7979
Reflector can create mirrors with the same name in other namespaces automatically. The following annotations control if and how the mirrors are created:
8080
- Add `reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"` to the resource annotations to automatically create mirrors in other namespaces. Note: Requires `reflector.v1.k8s.emberstack.com/reflection-allowed` to be `true` since mirrors need to able to reflect the source.
8181
- Add `reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "<list>"` to the resource annotations specify in which namespaces to automatically create mirrors. Note: If this annotation is omitted or is empty, all namespaces are allowed. Namespaces in this list will also be checked by `reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces` since mirrors need to be in namespaces from where reflection is permitted.
82+
- Optionally add `reflector.v1.k8s.emberstack.com/reflection-auto-key-mapping: "<list>"` to the resource annotations specify in the key mapping to use when automatically create mirrors. Expected format is a list of comma separated `src_key:dst_key`.
8283

8384
> Important: If the `source` is deleted, automatic mirrors are deleted. Also if either reflection or automirroring is turned off or the automatic mirror's namespace is no longer a valid match for the allowed namespaces, the automatic mirror is deleted.
8485
@@ -109,10 +110,29 @@ $ kubectl -n kube-system apply -f https://github.com/emberstack/kubernetes-refle
109110
data:
110111
...
111112
```
113+
114+
Example source secret with key mapping:
115+
```yaml
116+
apiVersion: v1
117+
kind: Secret
118+
metadata:
119+
name: source-secret
120+
annotations:
121+
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
122+
reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "namespace-1,namespace-2,namespace-[0-9]*"
123+
reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
124+
reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "namespace-1"
125+
reflector.v1.k8s.emberstack.com/reflection-auto-key-mapping: "user:username,pass:password"
126+
data:
127+
user: YWRtaW4K
128+
pass: YWRtaW4K
129+
...
130+
```
112131

113132
### 2. Annotate the mirror secret or configmap
114133

115134
- Add `reflector.v1.k8s.emberstack.com/reflects: "<source namespace>/<source name>"` to the mirror object. The value of the annotation is the full name of the source object in `namespace/name` format.
135+
- Optionally add `reflector.v1.k8s.emberstack.com/reflection-key-mapping: "<list>"` to the resource annotations specify in the key mapping to use when mirroring. Expected format is a list of comma separated `src_key:dst_key`. All omitted source key will be copied as is. No warning will be issued if the source key does not exist.
116136

117137
> Note: Add `reflector.v1.k8s.emberstack.com/reflected-version: ""` to the resource annotations when doing any manual changes to the mirror (for example when deploying with `helm` or re-applying the deployment script). This will reset the reflected version of the mirror.
118138
@@ -140,6 +160,19 @@ $ kubectl -n kube-system apply -f https://github.com/emberstack/kubernetes-refle
140160
...
141161
```
142162
163+
Example mirror secret with key mapping :
164+
```yaml
165+
apiVersion: v1
166+
kind: Secret
167+
metadata:
168+
name: mirror-secret
169+
annotations:
170+
reflector.v1.k8s.emberstack.com/reflects: "default/source-secret"
171+
reflector.v1.k8s.emberstack.com/reflection-key-mapping: "user:username,pass:password"
172+
data:
173+
...
174+
```
175+
143176
### 3. Done!
144177
Reflector will monitor any changes done to the source objects and copy the following fields:
145178
- `data` for secrets

src/ES.Kubernetes.Reflector/Core/ConfigMapMirror.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ protected override Task OnResourceApplyPatch(V1Patch patch, KubeRef refId)
2323
return Client.CoreV1.PatchNamespacedConfigMapAsync(patch, refId.Name, refId.Namespace);
2424
}
2525

26-
protected override Task OnResourceConfigurePatch(V1ConfigMap source, JsonPatchDocument<V1ConfigMap> patchDoc)
26+
protected override Task OnResourceConfigurePatch(V1ConfigMap source, JsonPatchDocument<V1ConfigMap> patchDoc, Dictionary<string, string>? mapping)
2727
{
28-
patchDoc.Replace(e => e.Data, source.Data);
29-
patchDoc.Replace(e => e.BinaryData, source.BinaryData);
28+
patchDoc.Replace(e => e.Data, MappedData(source.Data, mapping));
29+
patchDoc.Replace(e => e.BinaryData, MappedData(source.BinaryData, mapping));
3030
return Task.CompletedTask;
3131
}
3232

@@ -35,14 +35,14 @@ protected override Task OnResourceCreate(V1ConfigMap item, string ns)
3535
return Client.CoreV1.CreateNamespacedConfigMapAsync(item, ns);
3636
}
3737

38-
protected override Task<V1ConfigMap> OnResourceClone(V1ConfigMap sourceResource)
38+
protected override Task<V1ConfigMap> OnResourceClone(V1ConfigMap sourceResource, Dictionary<string, string>? mapping)
3939
{
4040
return Task.FromResult(new V1ConfigMap
4141
{
4242
ApiVersion = sourceResource.ApiVersion,
4343
Kind = sourceResource.Kind,
44-
Data = sourceResource.Data,
45-
BinaryData = sourceResource.BinaryData
44+
Data = MappedData(sourceResource.Data, mapping),
45+
BinaryData = MappedData(sourceResource.BinaryData, mapping)
4646
});
4747
}
4848

src/ES.Kubernetes.Reflector/Core/Mirroring/Constants/Annotations.cs

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public static class Reflection
1010
public static string AllowedNamespaces => $"{Prefix}/reflection-allowed-namespaces";
1111
public static string AutoEnabled => $"{Prefix}/reflection-auto-enabled";
1212
public static string AutoNamespaces => $"{Prefix}/reflection-auto-namespaces";
13+
public static string KeyMapping => $"{Prefix}/reflection-key-mapping";
14+
public static string AutoKeyMapping => $"{Prefix}/reflection-auto-key-mapping";
1315
public static string Reflects => $"{Prefix}/reflects";
1416

1517

src/ES.Kubernetes.Reflector/Core/Mirroring/Extensions/ReflectorExtensions.cs

+10
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ public static ReflectorProperties GetReflectionProperties(this V1ObjectMeta meta
3535
? autoNamespaces ?? string.Empty
3636
: string.Empty,
3737

38+
KeyMapping = metadata.SafeAnnotations()
39+
.TryGet(Annotations.Reflection.KeyMapping, out string? keyMapping)
40+
? keyMapping ?? string.Empty
41+
: string.Empty,
42+
43+
AutoKeyMapping = metadata.SafeAnnotations()
44+
.TryGet(Annotations.Reflection.AutoKeyMapping, out string? autoKeyMapping)
45+
? autoKeyMapping ?? string.Empty
46+
: string.Empty,
47+
3848
Reflects = metadata.SafeAnnotations()
3949
.TryGet(Annotations.Reflection.Reflects, out string? metaReflects)
4050
? string.IsNullOrWhiteSpace(metaReflects) ? KubeRef.Empty :

src/ES.Kubernetes.Reflector/Core/Mirroring/ReflectorProperties.cs

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ public class ReflectorProperties
99
public bool AutoEnabled { get; set; }
1010
public string AutoNamespaces { get; set; } = string.Empty;
1111
public KubeRef Reflects { get; set; } = KubeRef.Empty;
12+
public string KeyMapping { get; set; } = string.Empty;
13+
public string AutoKeyMapping { get; set; } = string.Empty;
1214

1315
public string Version { get; set; } = string.Empty;
1416

src/ES.Kubernetes.Reflector/Core/Mirroring/ResourceMirror.cs

+55-9
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,19 @@ public async Task Handle(WatcherEvent notification, CancellationToken cancellati
149149
}
150150
}
151151

152+
protected IDictionary<string, T> MappedData<T>(IDictionary<string, T>? data, Dictionary<string, string>? mapping = null)
153+
{
154+
mapping ??= new Dictionary<string, string>();
155+
IDictionary<string, T> newData = new Dictionary<string, T>();
156+
if (data != null)
157+
{
158+
foreach (var (key, value) in data)
159+
{
160+
newData.Add(mapping.GetValueOrDefault(key, key), value);
161+
}
162+
}
163+
return newData;
164+
}
152165

153166
private async Task HandleUpsert(TResource resource, WatchEventType eventType, CancellationToken cancellationToken)
154167
{
@@ -428,25 +441,38 @@ private async Task ResourceReflect(KubeRef sourceId, KubeRef targetId, TResource
428441
[Annotations.Reflection.MetaReflectedVersion] = source.Metadata.ResourceVersion,
429442
[Annotations.Reflection.MetaReflectedAt] = JsonConvert.SerializeObject(DateTimeOffset.UtcNow)
430443
};
444+
patchAnnotations[Annotations.Reflection.KeyMapping] = "";
445+
if (autoReflection)
446+
{
447+
patchAnnotations[Annotations.Reflection.KeyMapping] = autoReflection ? source.GetReflectionProperties().AutoKeyMapping : string.Empty;
448+
} else if (targetResource is not null)
449+
{
450+
patchAnnotations[Annotations.Reflection.KeyMapping] =
451+
targetResource.Metadata.Annotations.TryGetValue(Annotations.Reflection.KeyMapping, out var keyMapping) ? keyMapping : string.Empty;
452+
}
431453

454+
Dictionary<string, string> mapping = new Dictionary<string, string>();
455+
try
456+
{
457+
mapping = Mapping(patchAnnotations[Annotations.Reflection.KeyMapping]);
458+
}
459+
catch (FormatException e)
460+
{
461+
Logger.LogError(e, e.Message);
462+
}
432463

433464
try
434465
{
435466
if (targetResource is null)
436467
{
437-
var newResource = await OnResourceClone(source);
468+
var newResource = await OnResourceClone(source, mapping);
438469
newResource.Metadata ??= new V1ObjectMeta();
439470
newResource.Metadata.Name = targetId.Name;
440471
newResource.Metadata.NamespaceProperty = targetId.Namespace;
441472
newResource.Metadata.Annotations ??= new Dictionary<string, string>();
442473
var newResourceAnnotations = newResource.Metadata.Annotations;
443474
foreach (var patchAnnotation in patchAnnotations)
444475
newResourceAnnotations[patchAnnotation.Key] = patchAnnotation.Value;
445-
newResourceAnnotations[Annotations.Reflection.MetaAutoReflects] = autoReflection.ToString();
446-
newResourceAnnotations[Annotations.Reflection.Reflects] = sourceId.ToString();
447-
newResourceAnnotations[Annotations.Reflection.MetaReflectedVersion] = source.Metadata.ResourceVersion;
448-
newResourceAnnotations[Annotations.Reflection.MetaReflectedAt] =
449-
JsonConvert.SerializeObject(DateTimeOffset.UtcNow);
450476

451477
try
452478
{
@@ -476,7 +502,7 @@ private async Task ResourceReflect(KubeRef sourceId, KubeRef targetId, TResource
476502
annotations[patchAnnotation.Key] = patchAnnotation.Value;
477503
patchDoc.Replace(e => e.Metadata.Annotations, annotations);
478504

479-
await OnResourceConfigurePatch(source, patchDoc);
505+
await OnResourceConfigurePatch(source, patchDoc, mapping);
480506

481507
var patch = JsonConvert.SerializeObject(patchDoc, Formatting.Indented);
482508
await OnResourceApplyPatch(new V1Patch(patch, V1Patch.PatchType.JsonPatch), targetId);
@@ -488,11 +514,31 @@ private async Task ResourceReflect(KubeRef sourceId, KubeRef targetId, TResource
488514
}
489515
}
490516

517+
private Dictionary<string, string> Mapping(string? keyMapping)
518+
{
519+
var mappings = new Dictionary<string, string>();
520+
if (string.IsNullOrEmpty(keyMapping)) return mappings;
521+
522+
foreach (var definition in keyMapping.Split(","))
523+
{
524+
var definitionDetail = definition.Split(":");
525+
if (definitionDetail.Length == 2)
526+
{
527+
mappings.Add(definitionDetail[0], definitionDetail[1]);
528+
}
529+
else
530+
{
531+
throw new FormatException("Invalid key mapping, format is src_key:dst_key,src_other:dst_other. Received: " + definition);
532+
}
533+
}
534+
return mappings;
535+
}
536+
491537

492538
protected abstract Task OnResourceApplyPatch(V1Patch source, KubeRef refId);
493-
protected abstract Task OnResourceConfigurePatch(TResource source, JsonPatchDocument<TResource> patchDoc);
539+
protected abstract Task OnResourceConfigurePatch(TResource source, JsonPatchDocument<TResource> patchDoc, Dictionary<string, string> mapping);
494540
protected abstract Task OnResourceCreate(TResource item, string ns);
495-
protected abstract Task<TResource> OnResourceClone(TResource sourceResource);
541+
protected abstract Task<TResource> OnResourceClone(TResource sourceResource, Dictionary<string, string> mapping);
496542
protected abstract Task OnResourceDelete(KubeRef resourceId);
497543

498544

src/ES.Kubernetes.Reflector/Core/SecretMirror.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using ES.Kubernetes.Reflector.Core.Mirroring;
2+
using ES.Kubernetes.Reflector.Core.Mirroring.Extensions;
23
using ES.Kubernetes.Reflector.Core.Resources;
34
using k8s;
45
using k8s.Models;
@@ -23,9 +24,9 @@ protected override Task OnResourceApplyPatch(V1Patch patch, KubeRef refId)
2324
return Client.CoreV1.PatchNamespacedSecretWithHttpMessagesAsync(patch, refId.Name, refId.Namespace);
2425
}
2526

26-
protected override Task OnResourceConfigurePatch(V1Secret source, JsonPatchDocument<V1Secret> patchDoc)
27+
protected override Task OnResourceConfigurePatch(V1Secret source, JsonPatchDocument<V1Secret> patchDoc, Dictionary<string, string> mapping)
2728
{
28-
patchDoc.Replace(e => e.Data, source.Data);
29+
patchDoc.Replace(e => e.Data, MappedData(source.Data, mapping));
2930
return Task.CompletedTask;
3031
}
3132

@@ -34,14 +35,14 @@ protected override Task OnResourceCreate(V1Secret item, string ns)
3435
return Client.CoreV1.CreateNamespacedSecretAsync(item, ns);
3536
}
3637

37-
protected override Task<V1Secret> OnResourceClone(V1Secret sourceResource)
38+
protected override Task<V1Secret> OnResourceClone(V1Secret sourceResource, Dictionary<string, string> mapping)
3839
{
3940
return Task.FromResult(new V1Secret
4041
{
4142
ApiVersion = sourceResource.ApiVersion,
4243
Kind = sourceResource.Kind,
4344
Type = sourceResource.Type,
44-
Data = sourceResource.Data
45+
Data = MappedData(sourceResource.Data, mapping)
4546
});
4647
}
4748

0 commit comments

Comments
 (0)