diff --git a/extensions/src/AWSSDK.Extensions.NETCore.Setup/AWSOptions.cs b/extensions/src/AWSSDK.Extensions.NETCore.Setup/AWSOptions.cs index da72c7925b1b..8613fb58a2d4 100644 --- a/extensions/src/AWSSDK.Extensions.NETCore.Setup/AWSOptions.cs +++ b/extensions/src/AWSSDK.Extensions.NETCore.Setup/AWSOptions.cs @@ -21,6 +21,7 @@ using Amazon.Runtime; using Amazon.Extensions.NETCore.Setup; +using AWSSDK.Extensions.NETCore.Setup; namespace Amazon.Extensions.NETCore.Setup { @@ -110,7 +111,7 @@ internal set /// The service client that implements the service interface. public T CreateServiceClient() where T : IAmazonService { - return (T)ClientFactory.CreateServiceClient(null, typeof(T), this); + return (T)ClientFactory.CreateServiceClient(null, typeof(T), this, new DefaultAWSCredentialsFactory(this)); } /// diff --git a/extensions/src/AWSSDK.Extensions.NETCore.Setup/ClientFactory.cs b/extensions/src/AWSSDK.Extensions.NETCore.Setup/ClientFactory.cs index bfb386059098..2c903b43e9b3 100644 --- a/extensions/src/AWSSDK.Extensions.NETCore.Setup/ClientFactory.cs +++ b/extensions/src/AWSSDK.Extensions.NETCore.Setup/ClientFactory.cs @@ -16,7 +16,7 @@ using System.Reflection; using Amazon.Runtime; using Amazon.Runtime.CredentialManagement; - +using AWSSDK.Extensions.NETCore.Setup; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -34,17 +34,23 @@ internal class ClientFactory private static readonly Type[] EMPTY_TYPES = Array.Empty(); private static readonly object[] EMPTY_PARAMETERS = Array.Empty(); - private Type _serviceInterfaceType; - private AWSOptions _awsOptions; + private readonly Type _serviceInterfaceType; + private readonly AWSOptions _awsOptions; + private readonly IAWSCredentialsFactory _credentialsFactory; + private readonly ILoggerFactory _loggerFactory; + private readonly IConfiguration _configuration; /// /// Constructs an instance of the ClientFactory /// /// The type object for the Amazon service client interface, for example IAmazonS3. - internal ClientFactory(Type type, AWSOptions awsOptions) + internal ClientFactory(Type type, AWSOptions awsOptions, IAWSCredentialsFactory credentialsFactory, ILoggerFactory loggerFactory, IConfiguration configuration) { _serviceInterfaceType = type; _awsOptions = awsOptions; + _credentialsFactory = credentialsFactory; + _loggerFactory = loggerFactory; + _configuration = configuration; } /// @@ -53,24 +59,22 @@ internal ClientFactory(Type type, AWSOptions awsOptions) /// /// The dependency injection provider. /// The AWS service client - internal object CreateServiceClient(IServiceProvider provider) + internal object CreateServiceClient() { - var loggerFactory = provider.GetService(); - var logger = loggerFactory?.CreateLogger("AWSSDK"); + var logger = _loggerFactory?.CreateLogger("AWSSDK"); - var options = _awsOptions ?? provider.GetService(); - if(options == null) + var options = _awsOptions; + if(_awsOptions == null) { - var configuration = provider.GetService(); - if(configuration != null) + if(_configuration != null) { - options = configuration.GetAWSOptions(); + options = _configuration.GetAWSOptions(); if (options != null) logger?.LogInformation("Found AWS options in IConfiguration"); } } - return CreateServiceClient(logger, _serviceInterfaceType, options); + return CreateServiceClient(logger, _serviceInterfaceType, options, _credentialsFactory); } /// @@ -79,10 +83,10 @@ internal object CreateServiceClient(IServiceProvider provider) /// /// The dependency injection provider. /// The AWS service client - internal static IAmazonService CreateServiceClient(ILogger logger, Type serviceInterfaceType, AWSOptions options) + internal static IAmazonService CreateServiceClient(ILogger logger, Type serviceInterfaceType, AWSOptions options, IAWSCredentialsFactory credentialsFactory) { PerformGlobalConfig(logger, options); - var credentials = CreateCredentials(logger, options); + var credentials = credentialsFactory.Create(); if (!string.IsNullOrEmpty(options?.SessionRoleArn)) { @@ -160,51 +164,6 @@ private static AmazonServiceClient CreateClient(Type serviceInterfaceType, AWSCr return constructor.Invoke(new object[] { credentials, config }) as AmazonServiceClient; } - /// - /// Creates the AWSCredentials using either the profile indicated from the AWSOptions object - /// of the SDK fallback credentials search. - /// - /// - /// - private static AWSCredentials CreateCredentials(ILogger logger, AWSOptions options) - { - if (options != null) - { - if (options.Credentials != null) - { - logger?.LogInformation("Using AWS credentials specified with the AWSOptions.Credentials property"); - return options.Credentials; - } - if (!string.IsNullOrEmpty(options.Profile)) - { - var chain = new CredentialProfileStoreChain(options.ProfilesLocation); - AWSCredentials result; - if (chain.TryGetAWSCredentials(options.Profile, out result)) - { - logger?.LogInformation($"Found AWS credentials for the profile {options.Profile}"); - return result; - } - else - { - logger?.LogInformation($"Failed to find AWS credentials for the profile {options.Profile}"); - } - } - } - - var credentials = FallbackCredentialsFactory.GetCredentials(); - if (credentials == null) - { - logger?.LogError("Last effort to find AWS Credentials with AWS SDK's default credential search failed"); - throw new AmazonClientException("Failed to find AWS Credentials for constructing AWS service client"); - } - else - { - logger?.LogInformation("Found credentials using the AWS SDK's default credential search"); - } - - return credentials; - } - /// /// Creates the ClientConfig object for the service client. /// diff --git a/extensions/src/AWSSDK.Extensions.NETCore.Setup/DefaultAWSCredentialsFactory.cs b/extensions/src/AWSSDK.Extensions.NETCore.Setup/DefaultAWSCredentialsFactory.cs new file mode 100644 index 000000000000..dccfc0d15686 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.NETCore.Setup/DefaultAWSCredentialsFactory.cs @@ -0,0 +1,69 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +using Amazon.Extensions.NETCore.Setup; +using Amazon.Runtime; +using Amazon.Runtime.CredentialManagement; +using Microsoft.Extensions.Logging; + +namespace AWSSDK.Extensions.NETCore.Setup +{ + public class DefaultAWSCredentialsFactory : IAWSCredentialsFactory + { + private readonly AWSOptions _options; + private readonly ILogger _logger; + + public DefaultAWSCredentialsFactory(AWSOptions options, ILogger logger = null) + { + _options = options; + _logger = logger; + } + + /// + /// Creates the AWSCredentials using either AWSOptions.Credentials, AWSOptions.Profile + AWSOptions.ProfilesLocation, + /// or the SDK fallback credentials search. + /// + public AWSCredentials Create() + { + if (_options?.Credentials != null) + { + _logger?.LogInformation("Using AWS credentials specified with the AWSOptions.Credentials property"); + return _options.Credentials; + } + + if (!string.IsNullOrWhiteSpace(_options?.Profile)) + { + var chain = new CredentialProfileStoreChain(_options.ProfilesLocation); + if (chain.TryGetAWSCredentials(_options.Profile, out var result)) + { + _logger?.LogInformation("Found AWS credentials for the profile {OptionsProfile}", _options.Profile); + return result; + } + + _logger?.LogInformation("Failed to find AWS credentials for the profile {OptionsProfile}", _options.Profile); + } + + var credentials = FallbackCredentialsFactory.GetCredentials(); + if (credentials == null) + { + _logger?.LogError("Last effort to find AWS Credentials with AWS SDK's default credential search failed"); + throw new AmazonClientException("Failed to find AWS Credentials for constructing AWS service client"); + } + + _logger?.LogInformation("Found credentials using the AWS SDK's default credential search"); + + return credentials; + } + } +} \ No newline at end of file diff --git a/extensions/src/AWSSDK.Extensions.NETCore.Setup/IAWSCredentialsFactory.cs b/extensions/src/AWSSDK.Extensions.NETCore.Setup/IAWSCredentialsFactory.cs new file mode 100644 index 000000000000..b9adf3a9d364 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.NETCore.Setup/IAWSCredentialsFactory.cs @@ -0,0 +1,23 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +using Amazon.Runtime; + +namespace AWSSDK.Extensions.NETCore.Setup +{ + public interface IAWSCredentialsFactory + { + AWSCredentials Create(); + } +} \ No newline at end of file diff --git a/extensions/src/AWSSDK.Extensions.NETCore.Setup/ServiceCollectionExtensions.cs b/extensions/src/AWSSDK.Extensions.NETCore.Setup/ServiceCollectionExtensions.cs index 242092db89bc..dec641eba33b 100644 --- a/extensions/src/AWSSDK.Extensions.NETCore.Setup/ServiceCollectionExtensions.cs +++ b/extensions/src/AWSSDK.Extensions.NETCore.Setup/ServiceCollectionExtensions.cs @@ -13,15 +13,12 @@ * permissions and limitations under the License. */ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using Microsoft.Extensions.DependencyInjection; - using Amazon.Runtime; using Amazon.Extensions.NETCore.Setup; +using AWSSDK.Extensions.NETCore.Setup; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; namespace Microsoft.Extensions.DependencyInjection { @@ -43,8 +40,7 @@ public static class ServiceCollectionExtensions /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. public static IServiceCollection AddDefaultAWSOptions(this IServiceCollection collection, AWSOptions options) { - collection.Add(new ServiceDescriptor(typeof(AWSOptions), options)); - return collection; + return collection.AddDefaultAWSOptions(_ => options); } /// @@ -66,6 +62,50 @@ public static IServiceCollection AddDefaultAWSOptions( return collection; } + /// + /// Adds the DefaultAWSCredentialsFactory object to the dependency injection framework + /// This factory will be used to create the credentials for the Amazon service clients. + /// + public static IServiceCollection AddCredentialsFactory(this IServiceCollection collection) + { + return collection.AddCredentialsFactoryInternal(); + } + + /// + /// Adds the DefaultAWSCredentialsFactory object to the dependency injection framework + /// if no IAWSCredentialsFactory is already registered. + /// This factory will be used to create the credentials for the Amazon service clients. + /// + public static IServiceCollection TryAddCredentialsFactory(this IServiceCollection collection) + { + return collection.AddCredentialsFactoryInternal(tryAdd: true); + } + + /// + /// Adds a IAWSCredentialsFactory object obtained via th provided awsCredentialsFactoryFunc to the dependency + /// injection framework. This factory will be used to create the credentials for the Amazon service clients. + /// + public static IServiceCollection AddCredentialsFactory( + this IServiceCollection collection, + Func awsCredentialsFactoryFunc, + ServiceLifetime lifetime = ServiceLifetime.Singleton) + { + return collection.AddCredentialsFactoryInternal(awsCredentialsFactoryFunc, lifetime, tryAdd: false); + } + + /// + /// Adds a IAWSCredentialsFactory object obtained via th provided awsCredentialsFactoryFunc to the dependency + /// injection framework if no IAWSCredentialsFactory is already registered. + /// This factory will be used to create the credentials for the Amazon service clients. + /// + public static IServiceCollection TryAddCredentialsFactory( + this IServiceCollection collection, + Func awsCredentialsFactoryFunc, + ServiceLifetime lifetime = ServiceLifetime.Singleton) + { + return collection.AddCredentialsFactoryInternal(awsCredentialsFactoryFunc, lifetime, tryAdd: true); + } + /// /// Adds the Amazon service client to the dependency injection framework. The Amazon service client is not /// created until it is requested. If the ServiceLifetime property is set to Singleton, the default, then the same @@ -77,7 +117,7 @@ public static IServiceCollection AddDefaultAWSOptions( /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. public static IServiceCollection AddAWSService(this IServiceCollection collection, ServiceLifetime lifetime = ServiceLifetime.Singleton) where T : IAmazonService { - return AddAWSService(collection, null, lifetime); + return collection.AddAWSServiceInternal(lifetime: lifetime); } /// @@ -89,15 +129,40 @@ public static IServiceCollection AddAWSService(this IServiceCollection collec /// /// The AWS options used to create the service client overriding the default AWS options added using AddDefaultAWSOptions. /// The lifetime of the service client created. The default is Singleton. + /// + /// A func which takes an IServiceProvider and the AWSOptions provided to this call and returns an IAWSCredentialsFactory used to create the service client. + /// Must be provided if options are provided to this call _and_ a custom IAWSCredentialsFactory is registered via [Try]AddCredentialsFactory; otherwise the + /// default will be used. + /// /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. - public static IServiceCollection AddAWSService(this IServiceCollection collection, AWSOptions options, ServiceLifetime lifetime = ServiceLifetime.Singleton) where T : IAmazonService + public static IServiceCollection AddAWSService(this IServiceCollection collection, AWSOptions options, ServiceLifetime lifetime = ServiceLifetime.Singleton, Func credentialsFactoryFunc = null) where T : IAmazonService { - Func factory = - new ClientFactory(typeof(T), options).CreateServiceClient; + return collection.AddAWSServiceInternal(_ => options, credentialsFactoryFunc: credentialsFactoryFunc, lifetime: lifetime); + } - var descriptor = new ServiceDescriptor(typeof(T), factory, lifetime); - collection.Add(descriptor); - return collection; + /// + /// Adds the Amazon service client to the dependency injection framework. The Amazon service client is not + /// created until it is requested. If the ServiceLifetime property is set to Singleton, the default, then the same + /// instance will be reused for the lifetime of the process and the object should not be disposed. + /// + /// The AWS service interface, like IAmazonS3. + /// + /// A func which returns the AWS options used to create the service client overriding the default AWS options added using AddDefaultAWSOptions. + /// + /// A func which takes an IServiceProvider and the AWSOptions resolved by optionsFunc and returns an IAWSCredentialsFactory used to create the service client. + /// Must be provided if options are provided to this call _and_ a custom IAWSCredentialsFactory is registered via [Try]AddCredentialsFactory; otherwise the + /// default will be used. + /// + /// The lifetime of the service client created. The default is Singleton. + /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. + public static IServiceCollection AddAWSService( + this IServiceCollection collection, + Func optionsFunc, + Func credentialsFactoryFunc = null, + ServiceLifetime lifetime = ServiceLifetime.Singleton) + where T : IAmazonService + { + return collection.AddAWSServiceInternal(optionsFunc, credentialsFactoryFunc, lifetime); } /// @@ -111,7 +176,7 @@ public static IServiceCollection AddAWSService(this IServiceCollection collec /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. public static IServiceCollection TryAddAWSService(this IServiceCollection collection, ServiceLifetime lifetime = ServiceLifetime.Singleton) where T : IAmazonService { - return TryAddAWSService(collection, null, lifetime); + return collection.AddAWSServiceInternal(lifetime: lifetime, tryAdd: true); } /// @@ -123,15 +188,135 @@ public static IServiceCollection TryAddAWSService(this IServiceCollection col /// /// The AWS options used to create the service client overriding the default AWS options added using AddDefaultAWSOptions. /// The lifetime of the service client created. The default is Singleton. + /// + /// A func which takes an IServiceProvider and the AWSOptions resolved by optionsFunc and returns an IAWSCredentialsFactory used to create the service client. + /// Must be provided if options are provided to this call _and_ a custom IAWSCredentialsFactory is registered via [Try]AddCredentialsFactory; otherwise the + /// default will be used. + /// + /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. + public static IServiceCollection TryAddAWSService(this IServiceCollection collection, AWSOptions options, ServiceLifetime lifetime = ServiceLifetime.Singleton, Func credentialsFactoryFunc = null) where T : IAmazonService + { + return collection.AddAWSServiceInternal(_ => options, lifetime: lifetime, credentialsFactoryFunc: credentialsFactoryFunc, tryAdd: true); + } + + /// + /// Adds the Amazon service client to the dependency injection framework if the service type hasn't already been registered. + /// The Amazon service client is not created until it is requested. If the ServiceLifetime property is set to Singleton, + /// the default, then the same instance will be reused for the lifetime of the process and the object should not be disposed. + /// + /// The AWS service interface, like IAmazonS3. + /// + /// A func which returns the AWS options used to create the service client overriding the default AWS options added using AddDefaultAWSOptions. + /// + /// A func which takes an IServiceProvider and the AWSOptions resolved by optionsFunc and returns an IAWSCredentialsFactory used to create the service client. + /// Must be provided if options are provided to this call _and_ a custom IAWSCredentialsFactory is registered via [Try]AddCredentialsFactory; otherwise the + /// default will be used. + /// + /// The lifetime of the service client created. The default is Singleton. /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. - public static IServiceCollection TryAddAWSService(this IServiceCollection collection, AWSOptions options, ServiceLifetime lifetime = ServiceLifetime.Singleton) where T : IAmazonService + public static IServiceCollection TryAddAWSService( + this IServiceCollection collection, + Func optionsFunc, + Func credentialsFactoryFunc = null, + ServiceLifetime lifetime = ServiceLifetime.Singleton) + where T : IAmazonService { - Func factory = - new ClientFactory(typeof(T), options).CreateServiceClient; + return collection.AddAWSServiceInternal(optionsFunc, credentialsFactoryFunc, lifetime, tryAdd: true); + } + + private static IServiceCollection AddCredentialsFactoryInternal( + this IServiceCollection collection, + Func awsCredentialsFactoryFunc = null, + ServiceLifetime lifetime = ServiceLifetime.Singleton, + bool tryAdd = false) + { + if (awsCredentialsFactoryFunc != null) + { + collection.SetCustomCredentialsFactoryRegistered(); + } + + awsCredentialsFactoryFunc = awsCredentialsFactoryFunc ?? (sp => sp.CreateDefaultCredentialsFactory(sp.GetService())); + + var serviceDescriptor = new ServiceDescriptor(typeof(IAWSCredentialsFactory), awsCredentialsFactoryFunc, lifetime); + + if (tryAdd) + { + collection.TryAdd(serviceDescriptor); + } + else + { + collection.Add(serviceDescriptor); + } + + return collection; + } + + private static IServiceCollection AddAWSServiceInternal( + this IServiceCollection collection, + Func optionsFunc = null, + Func credentialsFactoryFunc = null, + ServiceLifetime lifetime = ServiceLifetime.Singleton, + bool tryAdd = false) + where T : IAmazonService + { + var descriptor = new ServiceDescriptor( + typeof(T), + sp => CreateServiceClient(sp, optionsFunc, credentialsFactoryFunc), + lifetime); + + if (tryAdd) + { + collection.TryAdd(descriptor); + } + else + { + collection.Add(descriptor); + } - var descriptor = new ServiceDescriptor(typeof(T), factory, lifetime); - collection.TryAdd(descriptor); return collection; } + + private static object CreateServiceClient( + IServiceProvider sp, + Func optionsFunc = null, + Func credentialsFactoryFunc = null) + { + var options = optionsFunc?.Invoke(sp) + ?? sp.GetService() + ?? sp.GetService()?.GetAWSOptions(); + + if (optionsFunc != null && + credentialsFactoryFunc == null && + sp.CustomCredentialsFactoryRegistered() && // If we're using the default credentials factory, no harm done since we're creating one with the provided AWSOptions below. + options?.Equals(sp.GetService()) != true) + { + throw new ArgumentNullException( + nameof(credentialsFactoryFunc), + "A credentialsFactoryFunc must be provided when options(Func) is provided and [Try]AddCredentialsFactory is called with a custom IAWSCredentialsFactory. " + + "Not doing this would result in the ServiceClient using a different AWSOptions object than the IAWSCredentialsFactory"); + } + + var awsCredentialsFactory = credentialsFactoryFunc?.Invoke(sp, options) + ?? sp.GetService() + ?? sp.CreateDefaultCredentialsFactory(options); + + var clientFactory = new ClientFactory( + typeof(T), + options, + awsCredentialsFactory, + sp.GetService(), + sp.GetService()); + + return clientFactory.CreateServiceClient(); + } + + private static IAWSCredentialsFactory CreateDefaultCredentialsFactory(this IServiceProvider sp, AWSOptions options) => + new DefaultAWSCredentialsFactory(options, sp.GetService()?.CreateLogger()); + + private static bool CustomCredentialsFactoryRegistered(this IServiceProvider sp) => sp.GetService() != null; + + private static void SetCustomCredentialsFactoryRegistered(this IServiceCollection collection) => collection.AddSingleton(); + + private class CustomCredentialsFactoryRegisteredType { } } } diff --git a/extensions/test/NETCore.SetupTests/DependencyInjectionTests.cs b/extensions/test/NETCore.SetupTests/DependencyInjectionTests.cs index d1c38275cb47..d84d9c91fd32 100644 --- a/extensions/test/NETCore.SetupTests/DependencyInjectionTests.cs +++ b/extensions/test/NETCore.SetupTests/DependencyInjectionTests.cs @@ -8,6 +8,9 @@ using Amazon.Extensions.NETCore.Setup; using Moq; using System; +using Amazon.Runtime; +using AWSSDK.Extensions.NETCore.Setup; +using Castle.Core.Logging; namespace DependencyInjectionTests { @@ -149,6 +152,282 @@ public void InjectS3ClientWithFactoryBuiltConfig() Assert.Equal(expectRegion, controller.S3Client.Config.RegionEndpoint); } + [Fact] + public void InjectS3ClientWithOverridingConfigAndCustomCredentialsProviderFunc() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(config.GetAWSOptions()); + services.AddAWSService( + new AWSOptions {Region = RegionEndpoint.EUCentral1 }, + credentialsFactoryFunc: (sp, options) => new DefaultAWSCredentialsFactory(options, Mock.Of>())); + + var serviceProvider = services.BuildServiceProvider(); + + var controller = ActivatorUtilities.CreateInstance(serviceProvider); + Assert.NotNull(controller.S3Client); + Assert.Equal(RegionEndpoint.EUCentral1, controller.S3Client.Config.RegionEndpoint); + } + + [Fact] + public void GivenCustomAddCredentialsFactoryCall_WhenInjectingS3Client_ThenUseCustomCredentials() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + var mockCredentialsFactory = new Mock(); + mockCredentialsFactory + .Setup(x => x.Create()) + .Returns(new BasicAWSCredentials("test", "test")); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(config.GetAWSOptions()); + services.AddCredentialsFactory(_ => mockCredentialsFactory.Object); + services.AddAWSService(); + + var serviceProvider = services.BuildServiceProvider(); + + var controller = ActivatorUtilities.CreateInstance(serviceProvider); + Assert.NotNull(controller.S3Client); + Assert.Equal(RegionEndpoint.USWest2, controller.S3Client.Config.RegionEndpoint); + mockCredentialsFactory.Verify(x => x.Create(), Times.Once); + } + + [Fact] + public void GivenCustomAddCredentialsFactory_WhenInjectingS3ClientWithOverridingConfigAndCustomCredentialsProviderFunc_ThenUseOverride() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + var mockCredentialsFactory = new Mock(); + mockCredentialsFactory + .Setup(x => x.Create()) + .Returns(new BasicAWSCredentials("test", "test")); + + var mockOverridenCredentialsFactory = new Mock(); + mockOverridenCredentialsFactory + .Setup(x => x.Create()) + .Returns(new BasicAWSCredentials("overriden", "overriden")); + + var awsOptions = new AWSOptions {Region = RegionEndpoint.EUCentral1 }; + AWSOptions providedOptionsToCredentialsFactoryFunc = new AWSOptions(); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(config.GetAWSOptions()); + services.AddCredentialsFactory(sp => mockCredentialsFactory.Object); + services.AddAWSService( + awsOptions, + credentialsFactoryFunc: (sp, options) => + { + providedOptionsToCredentialsFactoryFunc = options; + return mockOverridenCredentialsFactory.Object; + }); + + var serviceProvider = services.BuildServiceProvider(); + + var controller = ActivatorUtilities.CreateInstance(serviceProvider); + Assert.NotNull(controller.S3Client); + Assert.Equal(RegionEndpoint.EUCentral1, controller.S3Client.Config.RegionEndpoint); + Assert.Equal(providedOptionsToCredentialsFactoryFunc, awsOptions); + mockOverridenCredentialsFactory.Verify(x => x.Create(), Times.Once); + mockCredentialsFactory.Verify(x => x.Create(), Times.Never); + } + + [Fact] + public void GivenCustomAddCredentialsFactory_WhenInjectingS3ClientWithOverridingConfigAndNoCustomCredentialsProviderFunc_ThrowException() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + var mockCredentialsFactory = new Mock(); + mockCredentialsFactory + .Setup(x => x.Create()) + .Returns(new BasicAWSCredentials("test", "test")); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(config.GetAWSOptions()); + services.AddCredentialsFactory(sp => mockCredentialsFactory.Object); + services.AddAWSService(new AWSOptions {Region = RegionEndpoint.EUCentral1 }); + + var serviceProvider = services.BuildServiceProvider(); + + Func controllerAction = () => ActivatorUtilities.CreateInstance(serviceProvider); + Assert.Throws(controllerAction); + mockCredentialsFactory.Verify(x => x.Create(), Times.Never); + } + + [Fact] + public void GivenDefaultAddCredentialsFactory_WhenInjectingS3ClientWithSameConfigAsRegisteredAndNoCustomCredentialsProviderFunc_ThenWork() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + var awsOptions = new AWSOptions {Region = RegionEndpoint.EUCentral1 }; + var mockCredentialsFactory = new Mock(); + mockCredentialsFactory + .Setup(x => x.Create()) + .Returns(new BasicAWSCredentials("test", "test")); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(awsOptions); + services.AddCredentialsFactory(_ => mockCredentialsFactory.Object); + services.AddAWSService(awsOptions); + + var serviceProvider = services.BuildServiceProvider(); + + var controller = ActivatorUtilities.CreateInstance(serviceProvider); + Assert.NotNull(controller.S3Client); + Assert.Equal(RegionEndpoint.EUCentral1, controller.S3Client.Config.RegionEndpoint); + mockCredentialsFactory.Verify(x => x.Create(), Times.Once); + } + + [Fact] + public void GivenDefaultAddCredentialsFactoryCall_WhenInjectingS3ClientWithOverridingConfigAndNoCustomCredentialsProviderFunc_ThenWork() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(config.GetAWSOptions()); + services.AddCredentialsFactory(); + services.AddAWSService(new AWSOptions {Region = RegionEndpoint.EUCentral1 }); + + var serviceProvider = services.BuildServiceProvider(); + + var controller = ActivatorUtilities.CreateInstance(serviceProvider); + Assert.NotNull(controller.S3Client); + Assert.Equal(RegionEndpoint.EUCentral1, controller.S3Client.Config.RegionEndpoint); + } + + [Fact] + public void GivenDefaultAddCredentialsFactoryCall_WhenInjectingS3Client_ThenWork() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(config.GetAWSOptions()); + services.AddCredentialsFactory(); + services.AddAWSService(); + + var serviceProvider = services.BuildServiceProvider(); + + var controller = ActivatorUtilities.CreateInstance(serviceProvider); + Assert.NotNull(controller.S3Client); + Assert.Equal(RegionEndpoint.USWest2, controller.S3Client.Config.RegionEndpoint); + } + + [Fact] + public void GivenNoAddCredentialsFactoryCall_WhenInjectingS3ClientWithOverridingConfigAndNoCustomCredentialsProviderFunc_ThenUseDefault() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(config.GetAWSOptions()); + services.AddAWSService(new AWSOptions {Region = RegionEndpoint.EUCentral1 }); + + var serviceProvider = services.BuildServiceProvider(); + + var controller = ActivatorUtilities.CreateInstance(serviceProvider); + Assert.NotNull(controller.S3Client); + Assert.Equal(RegionEndpoint.EUCentral1, controller.S3Client.Config.RegionEndpoint); + } + + [Fact] + public void GivenNoAddCredentialsFactoryCall_WhenInjectingS3ClientWithNoOverridingConfigAndNoCustomCredentialsProviderFunc_ThenUseDefault() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(config.GetAWSOptions()); + services.AddAWSService(); + + var serviceProvider = services.BuildServiceProvider(); + + var controller = ActivatorUtilities.CreateInstance(serviceProvider); + Assert.NotNull(controller.S3Client); + Assert.Equal(RegionEndpoint.USWest2, controller.S3Client.Config.RegionEndpoint); + } + + [Fact] + public void GivenNoAddCredentialsFactoryCall_WhenInjectingS3ClientWithNoOverridingConfigButCustomCredentialsProviderFunc_ThenUseCustom() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + var mockOverridenCredentialsFactory = new Mock(); + mockOverridenCredentialsFactory + .Setup(x => x.Create()) + .Returns(new BasicAWSCredentials("overriden", "overriden")); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(config.GetAWSOptions()); + services.AddAWSService((AWSOptions)null, credentialsFactoryFunc: (sp, options) => mockOverridenCredentialsFactory.Object); + + var serviceProvider = services.BuildServiceProvider(); + + var controller = ActivatorUtilities.CreateInstance(serviceProvider); + Assert.NotNull(controller.S3Client); + Assert.Equal(RegionEndpoint.USWest2, controller.S3Client.Config.RegionEndpoint); + mockOverridenCredentialsFactory.Verify(x => x.Create(), Times.Once); + } + + [Fact] + public void GivenCustomAddCredentialsFactoryCall_WhenInjectingS3ClientWithNoOverridingConfigButCustomCredentialsProviderFunc_ThenUseCustom() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("./TestFiles/GetClientConfigSettingsTest.json"); + + IConfiguration config = builder.Build(); + + var mockCredentialsFactory = new Mock(); + mockCredentialsFactory + .Setup(x => x.Create()) + .Returns(new BasicAWSCredentials("test", "test")); + + var mockOverridenCredentialsFactory = new Mock(); + mockOverridenCredentialsFactory + .Setup(x => x.Create()) + .Returns(new BasicAWSCredentials("overriden", "overriden")); + + ServiceCollection services = new ServiceCollection(); + services.AddDefaultAWSOptions(config.GetAWSOptions()); + services.AddCredentialsFactory(_ => mockCredentialsFactory.Object); + services.AddAWSService((AWSOptions)null, credentialsFactoryFunc: (sp, options) => mockOverridenCredentialsFactory.Object); + + var serviceProvider = services.BuildServiceProvider(); + + var controller = ActivatorUtilities.CreateInstance(serviceProvider); + Assert.NotNull(controller.S3Client); + Assert.Equal(RegionEndpoint.USWest2, controller.S3Client.Config.RegionEndpoint); + mockOverridenCredentialsFactory.Verify(x => x.Create(), Times.Once); + mockCredentialsFactory.Verify(x => x.Create(), Times.Never); + } + public class TestController { public IAmazonS3 S3Client { get; private set; }