Skip to content

Commit

Permalink
Support for application configuration service in 3.2 line (#1269)
Browse files Browse the repository at this point in the history
* Initial support for application configuration service, fixes for CA2017
* prevent infinite loop with postprocessors
  • Loading branch information
TimHess authored Mar 28, 2024
1 parent 027ea66 commit f4fe92f
Show file tree
Hide file tree
Showing 17 changed files with 244 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using Microsoft.Extensions.Configuration;
using System;
using System.Linq;

namespace Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding;

Expand All @@ -12,6 +13,10 @@ namespace Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding;
/// </summary>
public static class ConfigurationBuilderExtensions
{
private const bool DefaultOptional = true;
private const bool DefaultReloadOnChange = false;
private static readonly Predicate<string> DefaultIgnoreKeyPredicate = _ => false;

/// <summary>
/// Adds configuration using files from the directory path specified by the environment variable "SERVICE_BINDING_ROOT". File name and directory paths
/// are used as the key, and the file contents are used as the values.
Expand All @@ -24,8 +29,7 @@ public static class ConfigurationBuilderExtensions
/// </returns>
public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigurationBuilder builder)
{
var source = new ServiceBindingConfigurationSource();
return RegisterPostProcessors(builder, source);
return builder.AddKubernetesServiceBindings(DefaultOptional);
}

/// <summary>
Expand All @@ -44,12 +48,7 @@ public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigura
/// </returns>
public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigurationBuilder builder, bool optional)
{
var source = new ServiceBindingConfigurationSource
{
Optional = optional
};

return RegisterPostProcessors(builder, source);
return builder.AddKubernetesServiceBindings(optional, DefaultReloadOnChange);
}

/// <summary>
Expand All @@ -71,13 +70,7 @@ public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigura
/// </returns>
public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigurationBuilder builder, bool optional, bool reloadOnChange)
{
var source = new ServiceBindingConfigurationSource
{
Optional = optional,
ReloadOnChange = reloadOnChange
};

return RegisterPostProcessors(builder, source);
return builder.AddKubernetesServiceBindings(optional, reloadOnChange, DefaultIgnoreKeyPredicate);
}

/// <summary>
Expand All @@ -102,18 +95,25 @@ public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigura
/// </returns>
public static IConfigurationBuilder AddKubernetesServiceBindings(this IConfigurationBuilder builder, bool optional, bool reloadOnChange, Predicate<string> ignoreKeyPredicate)
{
var source = new ServiceBindingConfigurationSource
if (!builder.Sources.OfType<ServiceBindingConfigurationSource>().Any())
{
Optional = optional,
ReloadOnChange = reloadOnChange,
IgnoreKeyPredicate = ignoreKeyPredicate
};
var source = new ServiceBindingConfigurationSource
{
Optional = optional,
ReloadOnChange = reloadOnChange,
IgnoreKeyPredicate = ignoreKeyPredicate
};

RegisterPostProcessors(source);
builder.Add(source);
}

return RegisterPostProcessors(builder, source);
return builder;
}

private static IConfigurationBuilder RegisterPostProcessors(IConfigurationBuilder builder, ServiceBindingConfigurationSource source)
private static void RegisterPostProcessors(ServiceBindingConfigurationSource source)
{
source.RegisterPostProcessor(new ApplicationConfigurationServicePostProcessor());
source.RegisterPostProcessor(new ArtemisPostProcessor());
source.RegisterPostProcessor(new CassandraPostProcessor());
source.RegisterPostProcessor(new ConfigServerPostProcessor());
Expand Down Expand Up @@ -142,7 +142,5 @@ private static IConfigurationBuilder RegisterPostProcessors(IConfigurationBuilde
source.RegisterPostProcessor(new PostgreSqlLegacyConnectorPostProcessor());
source.RegisterPostProcessor(new RabbitMQLegacyConnectorPostProcessor());
source.RegisterPostProcessor(new RedisLegacyConnectorPostProcessor());
builder.Add(source);
return builder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Steeltoe.Common.Configuration;

internal static class ConfigurationKeyConverter
{
private const string DotDelimiterString = ".";
private const char DotDelimiterChar = '.';
private const char UnderscoreDelimiterChar = '_';
private const char EscapeChar = '\\';
private const string EscapeString = "\\";

private static readonly Regex ArrayRegex = new (@"\[(?<digits>\d+)\]", RegexOptions.Compiled | RegexOptions.Singleline);

public static string AsDotNetConfigurationKey(string key)
{
if (string.IsNullOrEmpty(key))
{
return key;
}

IEnumerable<string> split = UniversalHierarchySplit(key);
var sb = new StringBuilder();

foreach (string keyPart in split.Select(ConvertArrayKey))
{
sb.Append(keyPart);
sb.Append(ConfigurationPath.KeyDelimiter);
}

return sb.ToString(0, sb.Length - 1);
}

private static IEnumerable<string> UniversalHierarchySplit(string source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}

var result = new List<string>();

int segmentStart = 0;

for (int i = 0; i < source.Length; i++)
{
bool readEscapeChar = false;

if (source[i] == EscapeChar)
{
readEscapeChar = true;
i++;
}

if (!readEscapeChar && source[i] == DotDelimiterChar)
{
result.Add(UnEscapeString(source[segmentStart..i]));
segmentStart = i + 1;
}

if (!readEscapeChar && source[i] == UnderscoreDelimiterChar && i < source.Length - 1 && source[i + 1] == UnderscoreDelimiterChar)
{
result.Add(UnEscapeString(source[segmentStart..i]));
segmentStart = i + 2;
}

if (i == source.Length - 1)
{
result.Add(UnEscapeString(source[segmentStart..]));
}
}

return result;

static string UnEscapeString(string src)
{
return src.Replace(EscapeString + DotDelimiterString, DotDelimiterString, StringComparison.Ordinal)
.Replace(EscapeString + EscapeString, EscapeString, StringComparison.Ordinal);
}
}

private static string ConvertArrayKey(string key)
{
return ArrayRegex.Replace(key, ":${digits}");
}
}
34 changes: 34 additions & 0 deletions src/Configuration/src/Kubernetes.ServiceBinding/PostProcessors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,44 @@
// See the LICENSE file in the project root for more information.

using Microsoft.Extensions.Configuration;
using Steeltoe.Common.Configuration;
using System.Collections.Generic;
using System.Linq;

namespace Steeltoe.Extensions.Configuration.Kubernetes.ServiceBinding;

internal sealed class ApplicationConfigurationServicePostProcessor : IConfigurationPostProcessor
{
internal const string BindingType = "config";

public void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary<string, string> configurationData)
{
if (!provider.IsBindingTypeEnabled(BindingType))
{
return;
}

foreach (string bindingKey in configurationData.Filter(
ServiceBindingConfigurationProvider.KubernetesBindingsPrefix,
ServiceBindingConfigurationProvider.TypeKey,
BindingType))
{
var mapper = new ServiceBindingMapper(configurationData, bindingKey);

IEnumerable<string> keysToMap = configurationData.Keys.Select(s => s.Split($"{bindingKey}:")[^1]).Except(new List<string>
{
ServiceBindingConfigurationProvider.ProviderKey,
ServiceBindingConfigurationProvider.TypeKey
}).ToList();

foreach (string key in keysToMap)
{
mapper.MapFromTo(key, ConfigurationKeyConverter.AsDotNetConfigurationKey(key));
}
}
}
}

internal sealed class ArtemisPostProcessor : IConfigurationPostProcessor
{
internal const string BindingTypeKey = "artemis";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public void AddKubernetesServiceBindings_AddsSourceAndRegistersProcessors()
Assert.Single(builder.Sources);
Assert.IsType<ServiceBindingConfigurationSource>(builder.Sources[0]);
var source = (ServiceBindingConfigurationSource)builder.Sources[0];
Assert.Equal(26, source.RegisteredProcessors.Count);
Assert.Equal(27, source.RegisteredProcessors.Count);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using FluentAssertions;
using Steeltoe.Common.Configuration;
using Xunit;

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

public sealed class ConfigurationKeyConverterTest
{
[Theory]
[InlineData("unchanged", "unchanged")]
[InlineData("unchanged[index]", "unchanged[index]")]
[InlineData("one.four.seven", "one:four:seven")]
[InlineData("one__four__seven", "one:four:seven")]
[InlineData("one__four__seven__", "one:four:seven:")]
[InlineData("_one__four__and_seven_", "_one:four:and_seven_")]
[InlineData("one[1]", "one:1")]
[InlineData("one[12][3456]", "one:12:3456")]
[InlineData("one.four.seven[0][1].twelve.thirteen[12]", "one:four:seven:0:1:twelve:thirteen:12")]
[InlineData(@"one\.four\\.seven", @"one.four\:seven")]
public void AsDotNetConfigurationKey_ProducesExpected(string input, string expectedOutput)
{
_ = ConfigurationKeyConverter.AsDotNetConfigurationKey(input).Should().BeEquivalentTo(expectedOutput);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,65 @@
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using FluentAssertions;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Xunit;

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

public class PostProcessorsTest : BasePostProcessorsTest
{
[Fact]
public void Processes_ApplicationConfigurationService_ConfigurationData()
{
var postProcessor = new ApplicationConfigurationServicePostProcessor();

Dictionary<string, string> configurationData = GetConfigData(
_testBindingName,
ApplicationConfigurationServicePostProcessor.BindingType,
Tuple.Create("provider", "acs"),
Tuple.Create("random", "data"),
Tuple.Create("from", "some-source"),
Tuple.Create("secret", "password"),
Tuple.Create("secret.one", "password1"),
Tuple.Create("secret__two", "password2"));

PostProcessorConfigurationProvider provider = GetConfigurationProvider(postProcessor, ApplicationConfigurationServicePostProcessor.BindingType, true);

postProcessor.PostProcessConfiguration(provider, configurationData);

configurationData["random"].Should().Be("data");
configurationData["from"].Should().Be("some-source");
configurationData["secret"].Should().Be("password");
configurationData["secret:one"].Should().Be("password1");
configurationData["secret:two"].Should().Be("password2");
configurationData.Should().NotContainKey("type");
configurationData.Should().NotContainKey("provider");
}

[Fact]
public void PopulatesDotNetFriendlyKeysFromOtherFormats()
{
string rootDirectory = Path.Combine(Environment.CurrentDirectory, "..", "..", "..", "resources", "k8s");
var source = new ServiceBindingConfigurationSource(rootDirectory);
var postProcessor = new ApplicationConfigurationServicePostProcessor();
source.RegisterPostProcessor(postProcessor);

IConfigurationRoot configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string> { { "steeltoe:kubernetes:bindings:enable", "true" } })
.Add(source).Build();

configuration["test-secret-key"].Should().Be("test-secret-value");
configuration["key:with:periods"].Should().Be("test-secret-value.");
configuration["key:with:double:underscores"].Should().Be("test-secret-value0");
configuration["key:with:double:underscores_"].Should().Be("test-secret-value1");
configuration["key:with:double:underscores:"].Should().Be("test-secret-value2");
}

[Fact]
public void ArtemisTest_BindingTypeDisabled()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void PopulatesContent()
{
var path = new PhysicalFileProvider(GetK8SResourcesDirectory());
var b = new ServiceBindingConfigurationProvider.ServiceBindings(path);
Assert.Equal(3, b.Bindings.Count);
Assert.Equal(4, b.Bindings.Count);
}

private static string GetK8SResourcesDirectory()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test-secret-value.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test-secret-value0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test-secret-value1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test-secret-value2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
acs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test-secret-value
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
config
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public bool HandleError(Exception exception)
}
catch (Exception errorDeliveryError)
{
_logger?.LogWarning("Error message was not delivered.", errorDeliveryError);
_logger?.LogWarning(errorDeliveryError, "Error message was not delivered.");
}
}

Expand All @@ -67,7 +67,7 @@ public bool HandleError(Exception exception)
}
else
{
_logger?.LogError("failure occurred in messaging task", exception);
_logger?.LogError(exception, "failure occurred in messaging task.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ protected void InvokeHandler(IMessageHandler handler, IMessage message)
throw;
}

_logger?.LogWarning("Suppressing Exception since 'ignoreFailures' is set to TRUE.", e);
_logger?.LogWarning(e, "Suppressing Exception since 'ignoreFailures' is set to TRUE.");
}
}

Expand Down
Loading

0 comments on commit f4fe92f

Please sign in to comment.