Skip to content

Commit f4fe92f

Browse files
authored
Support for application configuration service in 3.2 line (#1269)
* Initial support for application configuration service, fixes for CA2017 * prevent infinite loop with postprocessors
1 parent 027ea66 commit f4fe92f

17 files changed

Lines changed: 244 additions & 31 deletions

src/Configuration/src/Kubernetes.ServiceBinding/ConfigurationBuilderExtensions.cs

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using Microsoft.Extensions.Configuration;
66
using System;
7+
using System.Linq;
78

89
namespace Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding;
910

@@ -12,6 +13,10 @@ namespace Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding;
1213
/// </summary>
1314
public static class ConfigurationBuilderExtensions
1415
{
16+
private const bool DefaultOptional = true;
17+
private const bool DefaultReloadOnChange = false;
18+
private static readonly Predicate<string> DefaultIgnoreKeyPredicate = _ => false;
19+
1520
/// <summary>
1621
/// Adds configuration using files from the directory path specified by the environment variable "SERVICE_BINDING_ROOT". File name and directory paths
1722
/// are used as the key, and the file contents are used as the values.
@@ -24,8 +29,7 @@ public static class ConfigurationBuilderExtensions
2429
/// </returns>
2530
public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigurationBuilder builder)
2631
{
27-
var source = new ServiceBindingConfigurationSource();
28-
return RegisterPostProcessors(builder, source);
32+
return builder.AddKubernetesServiceBindings(DefaultOptional);
2933
}
3034

3135
/// <summary>
@@ -44,12 +48,7 @@ public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigura
4448
/// </returns>
4549
public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigurationBuilder builder, bool optional)
4650
{
47-
var source = new ServiceBindingConfigurationSource
48-
{
49-
Optional = optional
50-
};
51-
52-
return RegisterPostProcessors(builder, source);
51+
return builder.AddKubernetesServiceBindings(optional, DefaultReloadOnChange);
5352
}
5453

5554
/// <summary>
@@ -71,13 +70,7 @@ public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigura
7170
/// </returns>
7271
public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigurationBuilder builder, bool optional, bool reloadOnChange)
7372
{
74-
var source = new ServiceBindingConfigurationSource
75-
{
76-
Optional = optional,
77-
ReloadOnChange = reloadOnChange
78-
};
79-
80-
return RegisterPostProcessors(builder, source);
73+
return builder.AddKubernetesServiceBindings(optional, reloadOnChange, DefaultIgnoreKeyPredicate);
8174
}
8275

8376
/// <summary>
@@ -102,18 +95,25 @@ public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigura
10295
/// </returns>
10396
public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigurationBuilder builder, bool optional, bool reloadOnChange, Predicate<string> ignoreKeyPredicate)
10497
{
105-
var source = new ServiceBindingConfigurationSource
98+
if (!builder.Sources.OfType<ServiceBindingConfigurationSource>().Any())
10699
{
107-
Optional = optional,
108-
ReloadOnChange = reloadOnChange,
109-
IgnoreKeyPredicate = ignoreKeyPredicate
110-
};
100+
var source = new ServiceBindingConfigurationSource
101+
{
102+
Optional = optional,
103+
ReloadOnChange = reloadOnChange,
104+
IgnoreKeyPredicate = ignoreKeyPredicate
105+
};
106+
107+
RegisterPostProcessors(source);
108+
builder.Add(source);
109+
}
111110

112-
return RegisterPostProcessors(builder, source);
111+
return builder;
113112
}
114113

115-
private static IConfigurationBuilder RegisterPostProcessors(IConfigurationBuilder builder, ServiceBindingConfigurationSource source)
114+
private static void RegisterPostProcessors(ServiceBindingConfigurationSource source)
116115
{
116+
source.RegisterPostProcessor(new ApplicationConfigurationServicePostProcessor());
117117
source.RegisterPostProcessor(new ArtemisPostProcessor());
118118
source.RegisterPostProcessor(new CassandraPostProcessor());
119119
source.RegisterPostProcessor(new ConfigServerPostProcessor());
@@ -142,7 +142,5 @@ private static IConfigurationBuilder RegisterPostProcessors(IConfigurationBuilde
142142
source.RegisterPostProcessor(new PostgreSqlLegacyConnectorPostProcessor());
143143
source.RegisterPostProcessor(new RabbitMQLegacyConnectorPostProcessor());
144144
source.RegisterPostProcessor(new RedisLegacyConnectorPostProcessor());
145-
builder.Add(source);
146-
return builder;
147145
}
148146
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.Extensions.Configuration;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Text.RegularExpressions;
11+
12+
namespace Steeltoe.Common.Configuration;
13+
14+
internal static class ConfigurationKeyConverter
15+
{
16+
private const string DotDelimiterString = ".";
17+
private const char DotDelimiterChar = '.';
18+
private const char UnderscoreDelimiterChar = '_';
19+
private const char EscapeChar = '\\';
20+
private const string EscapeString = "\\";
21+
22+
private static readonly Regex ArrayRegex = new (@"\[(?<digits>\d+)\]", RegexOptions.Compiled | RegexOptions.Singleline);
23+
24+
public static string AsDotNetConfigurationKey(string key)
25+
{
26+
if (string.IsNullOrEmpty(key))
27+
{
28+
return key;
29+
}
30+
31+
IEnumerable<string> split = UniversalHierarchySplit(key);
32+
var sb = new StringBuilder();
33+
34+
foreach (string keyPart in split.Select(ConvertArrayKey))
35+
{
36+
sb.Append(keyPart);
37+
sb.Append(ConfigurationPath.KeyDelimiter);
38+
}
39+
40+
return sb.ToString(0, sb.Length - 1);
41+
}
42+
43+
private static IEnumerable<string> UniversalHierarchySplit(string source)
44+
{
45+
if (source is null)
46+
{
47+
throw new ArgumentNullException(nameof(source));
48+
}
49+
50+
var result = new List<string>();
51+
52+
int segmentStart = 0;
53+
54+
for (int i = 0; i < source.Length; i++)
55+
{
56+
bool readEscapeChar = false;
57+
58+
if (source[i] == EscapeChar)
59+
{
60+
readEscapeChar = true;
61+
i++;
62+
}
63+
64+
if (!readEscapeChar && source[i] == DotDelimiterChar)
65+
{
66+
result.Add(UnEscapeString(source[segmentStart..i]));
67+
segmentStart = i + 1;
68+
}
69+
70+
if (!readEscapeChar && source[i] == UnderscoreDelimiterChar && i < source.Length - 1 && source[i + 1] == UnderscoreDelimiterChar)
71+
{
72+
result.Add(UnEscapeString(source[segmentStart..i]));
73+
segmentStart = i + 2;
74+
}
75+
76+
if (i == source.Length - 1)
77+
{
78+
result.Add(UnEscapeString(source[segmentStart..]));
79+
}
80+
}
81+
82+
return result;
83+
84+
static string UnEscapeString(string src)
85+
{
86+
return src.Replace(EscapeString + DotDelimiterString, DotDelimiterString, StringComparison.Ordinal)
87+
.Replace(EscapeString + EscapeString, EscapeString, StringComparison.Ordinal);
88+
}
89+
}
90+
91+
private static string ConvertArrayKey(string key)
92+
{
93+
return ArrayRegex.Replace(key, ":${digits}");
94+
}
95+
}

src/Configuration/src/Kubernetes.ServiceBinding/PostProcessors.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,44 @@
33
// See the LICENSE file in the project root for more information.
44

55
using Microsoft.Extensions.Configuration;
6+
using Steeltoe.Common.Configuration;
67
using System.Collections.Generic;
8+
using System.Linq;
79

810
namespace Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding;
911

12+
internal sealed class ApplicationConfigurationServicePostProcessor : IConfigurationPostProcessor
13+
{
14+
internal const string BindingType = "config";
15+
16+
public void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary<string, string> configurationData)
17+
{
18+
if (!provider.IsBindingTypeEnabled(BindingType))
19+
{
20+
return;
21+
}
22+
23+
foreach (string bindingKey in configurationData.Filter(
24+
ServiceBindingConfigurationProvider.KubernetesBindingsPrefix,
25+
ServiceBindingConfigurationProvider.TypeKey,
26+
BindingType))
27+
{
28+
var mapper = new ServiceBindingMapper(configurationData, bindingKey);
29+
30+
IEnumerable<string> keysToMap = configurationData.Keys.Select(s => s.Split($"{bindingKey}:")[^1]).Except(new List<string>
31+
{
32+
ServiceBindingConfigurationProvider.ProviderKey,
33+
ServiceBindingConfigurationProvider.TypeKey
34+
}).ToList();
35+
36+
foreach (string key in keysToMap)
37+
{
38+
mapper.MapFromTo(key, ConfigurationKeyConverter.AsDotNetConfigurationKey(key));
39+
}
40+
}
41+
}
42+
}
43+
1044
internal sealed class ArtemisPostProcessor : IConfigurationPostProcessor
1145
{
1246
internal const string BindingTypeKey = "artemis";

src/Configuration/test/Kubernetes.ServiceBinding.Test/ConfigurationBuilderExtensionsTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ public void AddKubernetesServiceBindings_AddsSourceAndRegistersProcessors()
1717
Assert.Single(builder.Sources);
1818
Assert.IsType<ServiceBindingConfigurationSource>(builder.Sources[0]);
1919
var source = (ServiceBindingConfigurationSource)builder.Sources[0];
20-
Assert.Equal(26, source.RegisteredProcessors.Count);
20+
Assert.Equal(27, source.RegisteredProcessors.Count);
2121
}
2222
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using FluentAssertions;
6+
using Steeltoe.Common.Configuration;
7+
using Xunit;
8+
9+
namespace Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.Test;
10+
11+
public sealed class ConfigurationKeyConverterTest
12+
{
13+
[Theory]
14+
[InlineData("unchanged", "unchanged")]
15+
[InlineData("unchanged[index]", "unchanged[index]")]
16+
[InlineData("one.four.seven", "one:four:seven")]
17+
[InlineData("one__four__seven", "one:four:seven")]
18+
[InlineData("one__four__seven__", "one:four:seven:")]
19+
[InlineData("_one__four__and_seven_", "_one:four:and_seven_")]
20+
[InlineData("one[1]", "one:1")]
21+
[InlineData("one[12][3456]", "one:12:3456")]
22+
[InlineData("one.four.seven[0][1].twelve.thirteen[12]", "one:four:seven:0:1:twelve:thirteen:12")]
23+
[InlineData(@"one\.four\\.seven", @"one.four\:seven")]
24+
public void AsDotNetConfigurationKey_ProducesExpected(string input, string expectedOutput)
25+
{
26+
_ = ConfigurationKeyConverter.AsDotNetConfigurationKey(input).Should().BeEquivalentTo(expectedOutput);
27+
}
28+
}

src/Configuration/test/Kubernetes.ServiceBinding.Test/PostProcessorsTest.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,65 @@
22
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information.
44

5+
using FluentAssertions;
6+
using Microsoft.Extensions.Configuration;
57
using System;
68
using System.Collections.Generic;
9+
using System.Globalization;
10+
using System.IO;
711
using Xunit;
812

913
namespace Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding.Test;
1014

1115
public class PostProcessorsTest : BasePostProcessorsTest
1216
{
17+
[Fact]
18+
public void Processes_ApplicationConfigurationService_ConfigurationData()
19+
{
20+
var postProcessor = new ApplicationConfigurationServicePostProcessor();
21+
22+
Dictionary<string, string> configurationData = GetConfigData(
23+
_testBindingName,
24+
ApplicationConfigurationServicePostProcessor.BindingType,
25+
Tuple.Create("provider", "acs"),
26+
Tuple.Create("random", "data"),
27+
Tuple.Create("from", "some-source"),
28+
Tuple.Create("secret", "password"),
29+
Tuple.Create("secret.one", "password1"),
30+
Tuple.Create("secret__two", "password2"));
31+
32+
PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor, ApplicationConfigurationServicePostProcessor.BindingType, true);
33+
34+
postProcessor.PostProcessConfiguration(provider, configurationData);
35+
36+
configurationData["random"].Should().Be("data");
37+
configurationData["from"].Should().Be("some-source");
38+
configurationData["secret"].Should().Be("password");
39+
configurationData["secret:one"].Should().Be("password1");
40+
configurationData["secret:two"].Should().Be("password2");
41+
configurationData.Should().NotContainKey("type");
42+
configurationData.Should().NotContainKey("provider");
43+
}
44+
45+
[Fact]
46+
public void PopulatesDotNetFriendlyKeysFromOtherFormats()
47+
{
48+
string rootDirectory = Path.Combine(Environment.CurrentDirectory, "..", "..", "..", "resources", "k8s");
49+
var source = new ServiceBindingConfigurationSource(rootDirectory);
50+
var postProcessor = new ApplicationConfigurationServicePostProcessor();
51+
source.RegisterPostProcessor(postProcessor);
52+
53+
IConfigurationRoot configuration = new ConfigurationBuilder()
54+
.AddInMemoryCollection(new Dictionary<string, string> { { "steeltoe:kubernetes:bindings:enable", "true" } })
55+
.Add(source).Build();
56+
57+
configuration["test-secret-key"].Should().Be("test-secret-value");
58+
configuration["key:with:periods"].Should().Be("test-secret-value.");
59+
configuration["key:with:double:underscores"].Should().Be("test-secret-value0");
60+
configuration["key:with:double:underscores_"].Should().Be("test-secret-value1");
61+
configuration["key:with:double:underscores:"].Should().Be("test-secret-value2");
62+
}
63+
1364
[Fact]
1465
public void ArtemisTest_BindingTypeDisabled()
1566
{

src/Configuration/test/Kubernetes.ServiceBinding.Test/ServiceBindingsTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public void PopulatesContent()
2323
{
2424
var path = new PhysicalFileProvider(GetK8SResourcesDirectory());
2525
var b = new ServiceBindingConfigurationProvider.ServiceBindings(path);
26-
Assert.Equal(3, b.Bindings.Count);
26+
Assert.Equal(4, b.Bindings.Count);
2727
}
2828

2929
private static string GetK8SResourcesDirectory()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test-secret-value.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test-secret-value0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test-secret-value1

0 commit comments

Comments
 (0)