diff --git a/src/Etcd.Microsoft.Extensions.Configuration/Auth/Credentials.cs b/src/Etcd.Microsoft.Extensions.Configuration/Auth/Credentials.cs index 297e3db..5085c68 100644 --- a/src/Etcd.Microsoft.Extensions.Configuration/Auth/Credentials.cs +++ b/src/Etcd.Microsoft.Extensions.Configuration/Auth/Credentials.cs @@ -3,30 +3,52 @@ namespace Etcd.Microsoft.Extensions.Configuration.Auth; /// -/// Provides credentials +/// Provides credentials. /// /// public class Credentials : ICredentials { + private const string DefaultUserNameEnvironmentVariableName = "ETCD_CLIENT_USER_NAME"; + private const string DefaultPasswordEnvironmentVariableName = "ETCD_CLIENT_PASSWORD"; + /// /// Initializes a new instance of the class. /// /// Name of the user. /// The password. + /// The source of the username. + /// The source of the password. + /// The information about the credentials. /// /// Value cannot be null or empty. - userName /// or /// Value cannot be null or empty. - password /// - public Credentials(string userName, string password) + public Credentials(string userName, string password, + CredentialsSource userNameSource = CredentialsSource.Code, + CredentialsSource passwordSource = CredentialsSource.Code, + string? information = null) { if (string.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", nameof(userName)); if (string.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", nameof(password)); UserName = userName; Password = password; + UserNameSource = userNameSource; + PasswordSource = passwordSource; + Information = information; } + /// + /// Gets the source of the user name. + /// + public CredentialsSource UserNameSource { get; } + + /// + /// Gets the source of the password. + /// + public CredentialsSource PasswordSource { get; } + /// /// Gets the name of the user. /// @@ -42,4 +64,158 @@ public Credentials(string userName, string password) /// The password. /// public string Password { get; } + + /// + /// Gets the information about the credentials. + /// + public string? Information { get; private set; } + + /// + /// Gets the string representation of the credentials. + /// + override public string ToString() => Information ?? "etcd code based credentials"; + + /// + /// Creates a new credentials instance overriding values from environment variables if they are exists. + /// + /// The user name. + /// The password. + /// The name of the user name environment variable. + /// The name of the password environment variable. + /// The etcd user name or password are not provided via code and not found in the environment variable `{userNameEnvironmentVariableName}`. + public static ICredentials WithOverrideFromEnvironmentVariables( + string userName, + string password, + string userNameEnvironmentVariableName = DefaultUserNameEnvironmentVariableName, + string passwordEnvironmentVariableName = DefaultPasswordEnvironmentVariableName) + { + var userNameSource = CredentialsSource.Code; + var passwordSource = CredentialsSource.Code; + + var environmentUserName = Environment.GetEnvironmentVariable(userNameEnvironmentVariableName); + var environmentPassword = Environment.GetEnvironmentVariable(passwordEnvironmentVariableName); + + if (!string.IsNullOrEmpty(environmentUserName)) + { + userName = environmentUserName; + + userNameSource = CredentialsSource.EnvironmentVariables; + } + + if (string.IsNullOrEmpty(userName)) + throw new EtcdException($"Etcd user name is not provided via code and not found in the environment variable `{userNameEnvironmentVariableName}`."); + + if (!string.IsNullOrEmpty(environmentPassword)) + { + password = environmentPassword; + + passwordSource = CredentialsSource.EnvironmentVariables; + } + + if (string.IsNullOrEmpty(password)) + throw new EtcdException($"Etcd password is not provided via code and not found in the environment variable `{passwordEnvironmentVariableName}`."); + + return new Credentials(userName, password, userNameSource, passwordSource, + FormatInformation( + userNameSource, + passwordSource, + userNameEnvironmentVariableName, + passwordEnvironmentVariableName)); + } + + /// + /// Creates a new credentials instance overriding values from environment variables if they are exists. + /// + /// The user name. + /// The password. + /// The name of the password environment variable. + /// The etcd user name or password are not provided via code and not found in the environment variable `{userNameEnvironmentVariableName}`. + public static ICredentials WithOverrideFromEnvironmentVariables( + string userName, + string password, + string passwordEnvironmentVariableName = DefaultPasswordEnvironmentVariableName) + { + var userNameSource = CredentialsSource.Code; + var passwordSource = CredentialsSource.Code; + + var environmentPassword = Environment.GetEnvironmentVariable(passwordEnvironmentVariableName); + + if (string.IsNullOrEmpty(userName)) + throw new EtcdException($"Etcd user name is not provided."); + + if (!string.IsNullOrEmpty(environmentPassword)) + { + password = environmentPassword; + + passwordSource = CredentialsSource.EnvironmentVariables; + } + + if (string.IsNullOrEmpty(password)) + throw new EtcdException($"Etcd password is not provided via code and not found in the environment variable `{passwordEnvironmentVariableName}`."); + + return new Credentials(userName, password, userNameSource, passwordSource, + FormatInformation( + userNameSource, + passwordSource, + null, + passwordEnvironmentVariableName)); + } + + + /// + /// Creates a new credentials instance from environment variables. + /// + /// The name of the user name environment variable. + /// The name of the password environment variable. + /// The etcd user name or password are not provided via code and not found in the environment variable `{userNameEnvironmentVariableName}`. + public static ICredentials FromEnvironmentVariables( + string userNameEnvironmentVariableName = DefaultUserNameEnvironmentVariableName, + string passwordEnvironmentVariableName = DefaultPasswordEnvironmentVariableName) + { + var userNameSource = CredentialsSource.EnvironmentVariables; + var passwordSource = CredentialsSource.EnvironmentVariables; + + var userName = Environment.GetEnvironmentVariable(userNameEnvironmentVariableName); + var password = Environment.GetEnvironmentVariable(passwordEnvironmentVariableName); + + if (string.IsNullOrEmpty(userName)) + throw new EtcdException($"Etcd user name is not found in the environment variable `{userNameEnvironmentVariableName}`."); + + if (string.IsNullOrEmpty(password)) + throw new EtcdException($"Etcd password is not found in the environment variable `{passwordEnvironmentVariableName}`."); + + return new Credentials(userName, password, userNameSource, passwordSource, + FormatInformation( + userNameSource, + passwordSource, + userNameEnvironmentVariableName, + passwordEnvironmentVariableName)); + } + + private static string FormatInformation( + CredentialsSource userNameSource, + CredentialsSource passwordSource, + string? userNameEnvironmentVariableName = null, + string? passwordEnvironmentVariableName = null) => + FormatUserNameInformation(userNameSource, userNameEnvironmentVariableName) + ", " + FormatPassword(passwordSource, passwordEnvironmentVariableName); + + private static string FormatUserNameInformation(CredentialsSource userNameSource, string? userNameEnvironmentVariableName = null) + { + var result = $"etcd user name source: {userNameSource}"; + + if (userNameSource == CredentialsSource.EnvironmentVariables) + result += $" ({userNameEnvironmentVariableName})"; + + return result; + } + + private static string FormatPassword(CredentialsSource passwordSource, string? passwordEnvironmentVariableName = null) + { + var result = $"etcd password source: {passwordSource}"; + + if (passwordSource == CredentialsSource.EnvironmentVariables) + result += $" ({passwordEnvironmentVariableName})"; + + return result; + } } \ No newline at end of file diff --git a/src/Etcd.Microsoft.Extensions.Configuration/Auth/CredentialsSource.cs b/src/Etcd.Microsoft.Extensions.Configuration/Auth/CredentialsSource.cs new file mode 100644 index 0000000..4bf9aff --- /dev/null +++ b/src/Etcd.Microsoft.Extensions.Configuration/Auth/CredentialsSource.cs @@ -0,0 +1,17 @@ +namespace Etcd.Microsoft.Extensions.Configuration.Auth +{ + /// + /// Possible sources for credentials. + /// + public enum CredentialsSource + { + /// + /// Credentials provided via code. + /// + Code = 0, + /// + /// Credentials provided via environment variables. + /// + EnvironmentVariables = 1 + } +} \ No newline at end of file diff --git a/src/Etcd.Microsoft.Extensions.Configuration/CHANGELOG.md b/src/Etcd.Microsoft.Extensions.Configuration/CHANGELOG.md index eb340b0..8464e1c 100644 --- a/src/Etcd.Microsoft.Extensions.Configuration/CHANGELOG.md +++ b/src/Etcd.Microsoft.Extensions.Configuration/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [3.1.0] - 2025-10-22 + +### Added + +- Possibility to load credentials from environment variables + ## [3.0.0] - 2025-06-26 ### Removed diff --git a/src/Etcd.Microsoft.Extensions.Configuration/Etcd.Microsoft.Extensions.Configuration.csproj b/src/Etcd.Microsoft.Extensions.Configuration/Etcd.Microsoft.Extensions.Configuration.csproj index 3805fb9..499aff8 100644 --- a/src/Etcd.Microsoft.Extensions.Configuration/Etcd.Microsoft.Extensions.Configuration.csproj +++ b/src/Etcd.Microsoft.Extensions.Configuration/Etcd.Microsoft.Extensions.Configuration.csproj @@ -8,7 +8,7 @@ snupkg true - 3.0 + 3.1 Etcd based configuration provider for Microsoft.Extensions.Configuration Simplify community diff --git a/src/Etcd.Microsoft.Extensions.Configuration/EtcdApplicationEnvironment.cs b/src/Etcd.Microsoft.Extensions.Configuration/EtcdApplicationEnvironment.cs index e3ef278..950798f 100644 --- a/src/Etcd.Microsoft.Extensions.Configuration/EtcdApplicationEnvironment.cs +++ b/src/Etcd.Microsoft.Extensions.Configuration/EtcdApplicationEnvironment.cs @@ -20,7 +20,7 @@ public static class EtcdApplicationEnvironment /// /// The connection string. /// - /// value + /// value public static string? ConnectionString { get diff --git a/tests/Integration/Etcd.Microsoft.Extensions.Configuration.IntegrationTests/ConfigurationBuilderTests.cs b/tests/Integration/Etcd.Microsoft.Extensions.Configuration.IntegrationTests/ConfigurationBuilderTests.cs index 4c1041b..46ad9b5 100644 --- a/tests/Integration/Etcd.Microsoft.Extensions.Configuration.IntegrationTests/ConfigurationBuilderTests.cs +++ b/tests/Integration/Etcd.Microsoft.Extensions.Configuration.IntegrationTests/ConfigurationBuilderTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Etcd.Microsoft.Extensions.Configuration.Auth; using Etcd.Microsoft.Extensions.Configuration.Settings; @@ -26,7 +27,38 @@ public void Build_WithSettingsFromEtcd_ValuesLoaded() .Build(); // Act + PerformTest(config); + } + + [Test] + public void Build_WithSettingsFromEtcdAndCredentialsFromEnvironment_ValuesLoaded() + { + // Arrange + + Environment.SetEnvironmentVariable("ETCD_TEST_USERNAME", "MyUserName"); + Environment.SetEnvironmentVariable("ETCD_TEST_PASSWORD", "passw"); + + var credentials = new Credentials("MyUserName", "passw"); + var envCredentials = Credentials.WithOverrideFromEnvironmentVariables("foo", "bar", "ETCD_TEST_USERNAME", "ETCD_TEST_PASSWORD"); + var envCredentials2 = Credentials.WithOverrideFromEnvironmentVariables("MyUserName", "bar", "ETCD_TEST_PASSWORD"); + + var etcdSettings = new EtcdSettings("http://localhost:2379"); + var config = new ConfigurationBuilder() + .AddEtcd(credentials, etcdSettings) + .AddEtcd(envCredentials, etcdSettings, "MyPrefix") + .AddEtcd(envCredentials2, etcdSettings, "MYCOMPLEX/prefix", "/") + .Build(); + + // Act + PerformTest(config); + + // Assert + Assert.Pass("Credentials info: " + envCredentials.ToString()); + } + + private static void PerformTest(IConfigurationRoot config) + { var testSection = config.GetSection("TestSection"); var testSubSection = testSection.GetSection("SubSection"); var list = testSection.GetSection("ArraySection").Get>();