Skip to content

Commit 5c59317

Browse files
cparandievtsvetko
andauthored
add additionalCharactersForConfigurationPath option (#14)
Co-authored-by: tsvetko <[email protected]>
1 parent 6535fe0 commit 5c59317

File tree

4 files changed

+80
-2
lines changed

4 files changed

+80
-2
lines changed

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,39 @@ Later in your services you can track changes in app configuration using `IOption
6666
Keep in mind that your service should be registered as scoped or transient to receive updates.
6767
Also `IOptionsSnapshot` can return empty value in some cases ([it's .net core bug](https://github.com/dotnet/runtime/issues/37860))
6868

69+
## Configuration using additional characters for a configuration path
70+
This will be helpful when you want to flatten the structure of the secrets.
71+
For example the following two secret objects will evaluate to the same configuration if for the second object the `additionalCharactersForConfigurationPath` option is used with `new []{'.'}` value:
72+
```json
73+
{
74+
"secrets":
75+
{
76+
"DB":
77+
{
78+
"ConnectionString": "secret value"
79+
}
80+
}
81+
}
82+
```
83+
```json
84+
{
85+
"secrets":
86+
{
87+
"DB.ConnectionString": "secret value"
88+
}
89+
}
90+
```
91+
92+
```csharp
93+
config.AddVaultConfiguration(
94+
() => new VaultOptions(
95+
"htpp://localhost:8200",
96+
"root",
97+
additionalCharactersForConfigurationPath: new []{'.'}),),
98+
"sampleapp",
99+
"secret");
100+
```
101+
new VaultOptions("http://localhost:8200", "root", null, null, false, 300, false, new []{'.'}),
69102
## Configuration using environmnt variables
70103

71104
Alternatively, you can configure Vault connection using next environmnt variables:

Source/VaultSharp.Extensions.Configuration/VaultConfigurationProvider.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ namespace VaultSharp.Extensions.Configuration
33
using System;
44
using System.Collections.Generic;
55
using System.Globalization;
6+
using System.Linq;
7+
using System.Text;
68
using System.Threading.Tasks;
79
using Microsoft.Extensions.Configuration;
810
using Microsoft.Extensions.Logging;
@@ -94,6 +96,7 @@ private async Task LoadVaultDataAsync(IVaultClient vaultClient)
9496
var key = secretData.Key;
9597
key = key.Replace(this._source.BasePath, string.Empty, StringComparison.InvariantCultureIgnoreCase).TrimStart('/')
9698
.Replace('/', ':');
99+
key = this.ReplaceTheAdditionalCharactersForConfigurationPath(key);
97100
var data = secretData.SecretData.Data;
98101

99102
var shouldSetValue = true;
@@ -118,6 +121,8 @@ private void SetData<TValue>(IEnumerable<KeyValuePair<string, TValue>> data, str
118121
foreach (var pair in data)
119122
{
120123
var nestedKey = string.IsNullOrEmpty(key) ? pair.Key : $"{key}:{pair.Key}";
124+
nestedKey = this.ReplaceTheAdditionalCharactersForConfigurationPath(nestedKey);
125+
121126
var nestedValue = pair.Value;
122127
switch (nestedValue)
123128
{
@@ -235,6 +240,23 @@ private async IAsyncEnumerable<KeyedSecretData> ReadKeysAsync(IVaultClient vault
235240
}
236241
}
237242

243+
private string ReplaceTheAdditionalCharactersForConfigurationPath(string inputKey)
244+
{
245+
if (!this._source.Options.AdditionalCharactersForConfigurationPath.Any())
246+
{
247+
return inputKey;
248+
}
249+
250+
var outputKey = new StringBuilder(inputKey);
251+
252+
foreach (var c in this._source.Options.AdditionalCharactersForConfigurationPath)
253+
{
254+
outputKey.Replace(c, ':');
255+
}
256+
257+
return outputKey.ToString();
258+
}
259+
238260
private class KeyedSecretData
239261
{
240262
public KeyedSecretData(string key, SecretData secretData)

Source/VaultSharp.Extensions.Configuration/VaultOptions.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
namespace VaultSharp.Extensions.Configuration
22
{
3+
using System;
4+
using System.Collections.Generic;
5+
36
/// <summary>
47
/// Vault options class.
58
/// </summary>
@@ -15,14 +18,16 @@ public class VaultOptions
1518
/// <param name="reloadOnChange">Reload secrets if changed in Vault.</param>
1619
/// <param name="reloadCheckIntervalSeconds">Interval in seconds to check Vault for any changes.</param>
1720
/// <param name="omitVaultKeyName">Omit Vault Key Name in Configuration Keys.</param>
21+
/// <param name="additionalCharactersForConfigurationPath">Additional characters for the Configuration path.</param>
1822
public VaultOptions(
1923
string vaultAddress,
2024
string? vaultToken,
2125
string? vaultSecret = null,
2226
string? vaultRoleId = null,
2327
bool reloadOnChange = false,
2428
int reloadCheckIntervalSeconds = 300,
25-
bool omitVaultKeyName = false)
29+
bool omitVaultKeyName = false,
30+
IEnumerable<char>? additionalCharactersForConfigurationPath = null)
2631
{
2732
this.VaultAddress = vaultAddress;
2833
this.VaultToken = vaultToken;
@@ -31,6 +36,7 @@ public VaultOptions(
3136
this.ReloadOnChange = reloadOnChange;
3237
this.ReloadCheckIntervalSeconds = reloadCheckIntervalSeconds;
3338
this.OmitVaultKeyName = omitVaultKeyName;
39+
this.AdditionalCharactersForConfigurationPath = additionalCharactersForConfigurationPath ?? Array.Empty<char>();
3440
}
3541

3642
/// <summary>
@@ -68,5 +74,10 @@ public VaultOptions(
6874
/// Gets a value indicating whether the Vault key should be ommited when generation Configuration key names.
6975
/// </summary>
7076
public bool OmitVaultKeyName { get; }
77+
78+
/// <summary>
79+
/// Gets an array of characters that will be used as a path to form the Configuration.
80+
/// </summary>
81+
public IEnumerable<char> AdditionalCharactersForConfigurationPath { get; }
7182
}
7283
}

Tests/VaultSharp.Extensions.Configuration.Test/IntegrationTests.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ public async Task Success_SimpleTest_TokenAuth()
104104
new KeyValuePair<string, object>("option2", "value2"),
105105
}
106106
},
107+
{
108+
"test/otherSubsection.otherSubsection2/otherSubsection3.otherSubsection4.otherSubsection5", new[]
109+
{
110+
new KeyValuePair<string, object>("option7", "value7"),
111+
}
112+
},
107113
};
108114

109115
var container = this.PrepareVaultContainer();
@@ -115,7 +121,7 @@ public async Task Success_SimpleTest_TokenAuth()
115121
// act
116122
ConfigurationBuilder builder = new ConfigurationBuilder();
117123
builder.AddVaultConfiguration(
118-
() => new VaultOptions("http://localhost:8200", "root"),
124+
() => new VaultOptions("http://localhost:8200", "root", additionalCharactersForConfigurationPath: new []{'.'}),
119125
"test",
120126
"secret",
121127
this._logger);
@@ -137,6 +143,12 @@ public async Task Success_SimpleTest_TokenAuth()
137143
t2.OptionA.Should().Be("a2");
138144
t2.OptionB.Should().Be("b2");
139145
configurationRoot.GetSection("subsection").GetValue<string>("option2").Should().Be("value2");
146+
configurationRoot.GetSection("otherSubsection")
147+
.GetSection("otherSubsection2")
148+
.GetSection("otherSubsection3")
149+
.GetSection("otherSubsection4")
150+
.GetSection("otherSubsection5")
151+
.GetValue<string>("option7").Should().Be("value7");
140152
}
141153
finally
142154
{

0 commit comments

Comments
 (0)