Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
506b311
Remove duplicate tests
bart-vmware Mar 23, 2026
de04511
Update tests using Sandbox to use MemoryFileProvider (and without del…
bart-vmware Mar 25, 2026
3d5b1e8
Update existing test to verify precedence between options and appsett…
bart-vmware Mar 23, 2026
1dc382b
Make ConfigServerClientOptions reactive to configuration changes
bart-vmware Mar 27, 2026
661ec73
Improve test coverage
bart-vmware Mar 27, 2026
9737b70
Fix references to Config Server in comments
bart-vmware Mar 27, 2026
b0d32f8
Fix threading bugs and resource leaks in Config Server provider
bart-vmware Mar 28, 2026
e66e1d7
Fixed: use latest options snapshot during discovery in Config Server
bart-vmware Mar 28, 2026
bdd0111
Fixed: preserve original stack trace when load throws
bart-vmware Mar 28, 2026
fcf31f7
Fix Config Server health contributor to act on a consistent snapshot
bart-vmware Mar 28, 2026
2e26d63
Fix torn reads in _lastDiscoveryLookupResult
bart-vmware Mar 28, 2026
5d79adf
Fixed: act on single snapshot of _httpClientHandler
bart-vmware Mar 28, 2026
2f771f5
Refresh discovery during polled reload and fix lazy initialization race
bart-vmware Mar 28, 2026
eedb331
Fix change callback accumulation on repeated configuration reloads
bart-vmware Mar 28, 2026
9c621ad
Fix HttpClientHandler mutation race and timer disposal race
bart-vmware Mar 28, 2026
90dfa08
Fix threadpool starvation during retries
bart-vmware Mar 28, 2026
76f21e9
Fix timer callbacks potentially using stale options when settings change
bart-vmware Mar 28, 2026
7edbee5
Fix stale reads in ConfigServerDiscoveryService
bart-vmware Mar 28, 2026
4f7de6f
Add overloads to post-configure Config Server options
bart-vmware Mar 31, 2026
a3dc452
Cleanup existing tests
bart-vmware Apr 2, 2026
cdccbd2
Various fixes
bart-vmware Apr 2, 2026
0d65fa8
Increase timeout to reduce failures in CI
bart-vmware Apr 2, 2026
1a543ee
Update src/Configuration/test/ConfigServer.Test/ConfigServerConfigura…
bart-vmware Apr 7, 2026
160c374
Review feedback: replace overrule with override
bart-vmware Apr 7, 2026
603250b
Review feedback: Change initial to default options
bart-vmware Apr 7, 2026
a923987
Review feedback: remove redundant settings in test
bart-vmware Apr 7, 2026
94743ac
Review feedback: adjust test name
bart-vmware Apr 7, 2026
885a2ff
Review feedback: remove duplicate configuration
bart-vmware Apr 7, 2026
747f5f1
Review feedback: reformat JSON snippets in tests
bart-vmware Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Bootstrap/src/AutoConfiguration/BootstrapScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public void ConfigureSteeltoe()

private void WireConfigServer()
{
_wrapper.AddConfigServer(_loggerFactory);
_wrapper.AddConfigServer(null, _loggerFactory);

LogConfigServerConfigured();
}
Expand Down
16 changes: 15 additions & 1 deletion src/Common/src/Certificates/CertificateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ public sealed class CertificateOptions
internal const string ConfigurationKeyPrefix = "Certificates";

public X509Certificate2? Certificate { get; set; }

public IList<X509Certificate2> IssuerChain { get; } = [];

internal CertificateOptions Clone()
{
var clone = new CertificateOptions
{
Certificate = Certificate
};

foreach (X509Certificate2 issuer in IssuerChain)
{
clone.IssuerChain.Add(issuer);
}

return clone;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public async Task CertificateOptions_update_on_changed_contents(string certifica
string secondPrivateKeyContent = await File.ReadAllTextAsync("secondInstance.key", TestContext.Current.CancellationToken);
using var secondX509 = X509Certificate2.CreateFromPemFile("secondInstance.crt", "secondInstance.key");
string appSettings = BuildAppSettingsJson(certificateName, certificateFilePath, privateKeyFilePath);
string appSettingsPath = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings);
string appSettingsPath = sandbox.CreateFile("appsettings.json", appSettings);
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile(appSettingsPath, false, true);
IConfiguration configuration = configurationBuilder.Build();
Expand All @@ -167,7 +167,7 @@ public async Task CertificateOptions_update_on_changed_contents(string certifica
await File.WriteAllTextAsync(privateKeyFilePath, secondPrivateKeyContent, TestContext.Current.CancellationToken);

using Task pollTask = WaitUntilCertificateChangedToAsync(secondX509, optionsMonitor, certificateName, TestContext.Current.CancellationToken);
await pollTask.WaitAsync(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken);
await pollTask.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);

optionsMonitor.Get(certificateName).Certificate.Should().Be(secondX509);
}
Expand All @@ -185,7 +185,7 @@ public async Task CertificateOptions_update_on_changed_path(string certificateNa
string firstPrivateKeyFilePath = sandbox.CreateFile(Guid.NewGuid() + ".key", firstPrivateKeyContent);
using var secondX509 = X509Certificate2.CreateFromPemFile("secondInstance.crt", "secondInstance.key");
string appSettings = BuildAppSettingsJson(certificateName, firstCertificateFilePath, firstPrivateKeyFilePath);
string appSettingsPath = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, appSettings);
string appSettingsPath = sandbox.CreateFile("appsettings.json", appSettings);
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile(appSettingsPath, false, true);
IConfiguration configuration = configurationBuilder.Build();
Expand Down
13 changes: 5 additions & 8 deletions src/Common/test/Common.Test/ApplicationInstanceInfoTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Microsoft.Extensions.DependencyInjection;
using Steeltoe.Common.Extensions;
using Steeltoe.Common.TestResources;
using Steeltoe.Common.TestResources.IO;

namespace Steeltoe.Common.Test;

Expand All @@ -24,7 +23,7 @@ public void ConstructorSetsDefaults()
[Fact]
public async Task ReadsApplicationConfiguration()
{
const string configJson = """
const string appSettings = """
{
"Spring": {
"Application": {
Expand All @@ -34,13 +33,11 @@ public async Task ReadsApplicationConfiguration()
}
""";

using var sandbox = new Sandbox();
string path = sandbox.CreateFile(MemoryFileProvider.DefaultAppSettingsFileName, configJson);
string directory = Path.GetDirectoryName(path)!;
string fileName = Path.GetFileName(path);
var fileProvider = new MemoryFileProvider();
fileProvider.IncludeAppSettingsJsonFile(appSettings);

var builder = new ConfigurationBuilder();
builder.SetBasePath(directory);
builder.AddJsonFile(fileName);
builder.AddInMemoryAppSettingsJsonFile(fileProvider);
IConfiguration configuration = builder.Build();

var services = new ServiceCollection();
Expand Down
2 changes: 0 additions & 2 deletions src/Common/test/TestResources/MemoryFileProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ namespace Steeltoe.Common.TestResources;

public sealed class MemoryFileProvider : IFileProvider
{
public const string DefaultAppSettingsFileName = "appsettings.json";

private static readonly char[] DirectorySeparators =
[
Path.DirectorySeparatorChar,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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.

namespace Steeltoe.Common.TestResources;

public static class MemoryFileProviderAppSettingsExtensions
{
public static void IncludeAppSettingsJsonFile(this MemoryFileProvider fileProvider, string contents)
{
ArgumentNullException.ThrowIfNull(fileProvider);

fileProvider.IncludeFile(MemoryFileProviderConfigurationBuilderExtensions.AppSettingsJsonFileName, contents);
}

public static void IncludeAppSettingsXmlFile(this MemoryFileProvider fileProvider, string contents)
{
ArgumentNullException.ThrowIfNull(fileProvider);

fileProvider.IncludeFile(MemoryFileProviderConfigurationBuilderExtensions.AppSettingsXmlFileName, contents);
}

public static void IncludeAppSettingsIniFile(this MemoryFileProvider fileProvider, string contents)
{
ArgumentNullException.ThrowIfNull(fileProvider);

fileProvider.IncludeFile(MemoryFileProviderConfigurationBuilderExtensions.AppSettingsIniFileName, contents);
}

public static void ReplaceAppSettingsJsonFile(this MemoryFileProvider fileProvider, string contents)
{
ArgumentNullException.ThrowIfNull(fileProvider);

fileProvider.ReplaceFile(MemoryFileProviderConfigurationBuilderExtensions.AppSettingsJsonFileName, contents);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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 Microsoft.Extensions.Configuration.Ini;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.Configuration.Xml;

namespace Steeltoe.Common.TestResources;

public static class MemoryFileProviderConfigurationBuilderExtensions
{
internal const string AppSettingsJsonFileName = "appsettings.json";
internal const string AppSettingsXmlFileName = "appsettings.xml";
internal const string AppSettingsIniFileName = "appsettings.ini";

public static void AddInMemoryAppSettingsJsonFile(this IConfigurationBuilder builder, MemoryFileProvider fileProvider)
{
AddInMemoryJsonFile(builder, fileProvider, AppSettingsJsonFileName);
}

public static void AddInMemoryJsonFile(this IConfigurationBuilder builder, MemoryFileProvider fileProvider, string path)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(fileProvider);
ArgumentException.ThrowIfNullOrEmpty(path);

var source = new JsonConfigurationSource
{
FileProvider = fileProvider,
Path = path,
Optional = false,
ReloadOnChange = true,
// Turn off debounce, so the change token triggers immediately. Then we don't need to sleep in tests.
ReloadDelay = 0
};

builder.Add(source);
}

public static void AddInMemoryAppSettingsXmlFile(this IConfigurationBuilder builder, MemoryFileProvider fileProvider)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(fileProvider);

var source = new XmlConfigurationSource
{
FileProvider = fileProvider,
Path = AppSettingsXmlFileName,
Optional = false,
ReloadOnChange = true,
// Turn off debounce, so the change token triggers immediately. Then we don't need to sleep in tests.
ReloadDelay = 0
};

builder.Add(source);
}

public static void AddInMemoryAppSettingsIniFile(this IConfigurationBuilder builder, MemoryFileProvider fileProvider)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(fileProvider);

var source = new IniConfigurationSource
{
FileProvider = fileProvider,
Path = AppSettingsIniFileName,
Optional = false,
ReloadOnChange = true,
// Turn off debounce, so the change token triggers immediately. Then we don't need to sleep in tests.
ReloadDelay = 0
};

builder.Add(source);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ private void Load(bool isReload)

public IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string? parentPath)
{
ArgumentNullException.ThrowIfNull(earlierKeys);

string[] earlierKeysArray = earlierKeys as string[] ?? earlierKeys.ToArray();
#pragma warning disable S3236 // Caller information arguments should not be provided explicitly
ArgumentNullException.ThrowIfNull(earlierKeysArray, nameof(earlierKeys));
#pragma warning restore S3236 // Caller information arguments should not be provided explicitly

if (_logger.IsEnabled(LogLevel.Trace))
{
Expand Down
63 changes: 56 additions & 7 deletions src/Configuration/src/ConfigServer/ConfigServerClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ public sealed class ConfigServerClientOptions : IValidateCertificatesOptions
private const char CommaDelimiter = ',';
internal const string ConfigurationPrefix = "spring:cloud:config";

internal CertificateOptions ClientCertificate { get; } = new();
internal TimeSpan HttpTimeout => TimeSpan.FromMilliseconds(Timeout);
internal bool IsMultiServerConfiguration => Uri != null && Uri.Contains(CommaDelimiter);

/// <summary>
/// Gets or sets the client certificate used for mutual TLS authentication with the Config Server.
/// </summary>
internal CertificateOptions ClientCertificate { get; set; } = new();

/// <summary>
/// Gets or sets a value indicating whether the Config Server provider is enabled. Default value: true.
/// </summary>
Expand All @@ -35,7 +39,7 @@ public sealed class ConfigServerClientOptions : IValidateCertificatesOptions
/// Gets or sets a comma-separated list of environments used when accessing configuration data. Default value: "Production".
/// </summary>
[ConfigurationKeyName("Env")]
public string? Environment { get; set; } = "Production";
public string? Environment { get; set; }

/// <summary>
/// Gets or sets a comma-separated list of labels to request from the server.
Expand Down Expand Up @@ -96,17 +100,17 @@ public bool ValidateCertificatesAlt
/// <summary>
/// Gets retry settings.
/// </summary>
public ConfigServerRetryOptions Retry { get; } = new();
public ConfigServerRetryOptions Retry { get; private set; } = new();

/// <summary>
/// Gets service discovery settings.
/// </summary>
public ConfigServerDiscoveryOptions Discovery { get; } = new();
public ConfigServerDiscoveryOptions Discovery { get; private set; } = new();

/// <summary>
/// Gets health check settings.
/// </summary>
public ConfigServerHealthOptions Health { get; } = new();
public ConfigServerHealthOptions Health { get; private set; } = new();

/// <summary>
/// Gets or sets the address used by the provider to obtain a OAuth Access Token.
Expand All @@ -129,7 +133,7 @@ public bool ValidateCertificatesAlt
public int TokenTtl { get; set; } = 300_000;

/// <summary>
/// Gets or sets the vault token renew rate (in milliseconds). Default value: 60_000 (1 minute).
/// Gets or sets the Vault token renew rate (in milliseconds). Default value: 60_000 (1 minute).
/// </summary>
public int TokenRenewRate { get; set; } = 60_000;

Expand All @@ -141,7 +145,52 @@ public bool ValidateCertificatesAlt
/// <summary>
/// Gets headers that will be added to the Config Server request.
/// </summary>
public IDictionary<string, string> Headers { get; } = new Dictionary<string, string>();
public IDictionary<string, string> Headers { get; private set; } = new Dictionary<string, string>();

internal ConfigServerClientOptions Clone()
{
return new ConfigServerClientOptions
{
ClientCertificate = ClientCertificate.Clone(),
Enabled = Enabled,
FailFast = FailFast,
Environment = Environment,
Label = Label,
Name = Name,
Uri = Uri,
Username = Username,
Password = Password,
Token = Token,
Timeout = Timeout,
PollingInterval = PollingInterval,
ValidateCertificates = ValidateCertificates,
Retry = new ConfigServerRetryOptions
{
Enabled = Retry.Enabled,
InitialInterval = Retry.InitialInterval,
MaxInterval = Retry.MaxInterval,
Multiplier = Retry.Multiplier,
MaxAttempts = Retry.MaxAttempts
},
Discovery = new ConfigServerDiscoveryOptions
{
Enabled = Discovery.Enabled,
ServiceId = Discovery.ServiceId
},
Health = new ConfigServerHealthOptions
{
Enabled = Health.Enabled,
TimeToLive = Health.TimeToLive
},
AccessTokenUri = AccessTokenUri,
ClientSecret = ClientSecret,
ClientId = ClientId,
TokenTtl = TokenTtl,
TokenRenewRate = TokenRenewRate,
DisableTokenRenewal = DisableTokenRenewal,
Headers = new Dictionary<string, string>(Headers)
};
}

internal List<Uri> GetUris()
{
Expand Down
Loading
Loading