/*
* 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 System;
using System.Reflection;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Amazon.Extensions.NETCore.Setup
{
///
/// The factory class for creating AWS service clients from the AWS SDK for .NET.
///
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;
///
/// Constructs an instance of the ClientFactory
///
/// The type object for the Amazon service client interface, for example IAmazonS3.
internal ClientFactory(Type type, AWSOptions awsOptions)
{
_serviceInterfaceType = type;
_awsOptions = awsOptions;
}
///
/// Creates the AWS service client that implements the service client interface. The AWSOptions object
/// will be searched for in the IServiceProvider.
///
/// The dependency injection provider.
/// The AWS service client
internal object CreateServiceClient(IServiceProvider provider)
{
var loggerFactory = provider.GetService();
var logger = loggerFactory?.CreateLogger("AWSSDK");
var options = _awsOptions ?? provider.GetService();
if(options == null)
{
var configuration = provider.GetService();
if(configuration != null)
{
options = configuration.GetAWSOptions();
if (options != null)
logger?.LogInformation("Found AWS options in IConfiguration");
}
}
return CreateServiceClient(logger, _serviceInterfaceType, options);
}
///
/// Creates the AWS service client that implements the service client interface. The AWSOptions object
/// will be searched for in the IServiceProvider.
///
/// The dependency injection provider.
/// The AWS service client
internal static IAmazonService CreateServiceClient(ILogger logger, Type serviceInterfaceType, AWSOptions options)
{
PerformGlobalConfig(logger, options);
var credentials = CreateCredentials(logger, options);
if (!string.IsNullOrEmpty(options?.SessionRoleArn))
{
credentials = new AssumeRoleAWSCredentials(credentials, options.SessionRoleArn, options.SessionName);
}
var config = CreateConfig(serviceInterfaceType, options);
var client = CreateClient(serviceInterfaceType, credentials, config);
return client as IAmazonService;
}
///
/// Performs all of the global settings that have been specified in AWSOptions.
///
///
///
private static void PerformGlobalConfig(ILogger logger, AWSOptions options)
{
if(options?.Logging != null)
{
if(options.Logging.LogTo.HasValue && AWSConfigs.LoggingConfig.LogTo != options.Logging.LogTo.Value)
{
AWSConfigs.LoggingConfig.LogTo = options.Logging.LogTo.Value;
logger?.LogDebug($"Configuring SDK LogTo: {AWSConfigs.LoggingConfig.LogTo}");
}
if (options.Logging.LogResponses.HasValue && AWSConfigs.LoggingConfig.LogResponses != options.Logging.LogResponses.Value)
{
AWSConfigs.LoggingConfig.LogResponses = options.Logging.LogResponses.Value;
logger?.LogDebug($"Configuring SDK LogResponses: {AWSConfigs.LoggingConfig.LogResponses}");
}
if (options.Logging.LogMetrics.HasValue && AWSConfigs.LoggingConfig.LogMetrics != options.Logging.LogMetrics.Value)
{
AWSConfigs.LoggingConfig.LogMetrics = options.Logging.LogMetrics.Value;
logger?.LogDebug($"Configuring SDK LogMetrics: {AWSConfigs.LoggingConfig.LogMetrics}");
}
if (options.Logging.LogResponsesSizeLimit.HasValue && AWSConfigs.LoggingConfig.LogResponsesSizeLimit != options.Logging.LogResponsesSizeLimit.Value)
{
AWSConfigs.LoggingConfig.LogResponsesSizeLimit = options.Logging.LogResponsesSizeLimit.Value;
logger?.LogDebug($"Configuring SDK LogResponsesSizeLimit: {AWSConfigs.LoggingConfig.LogResponsesSizeLimit}");
}
}
}
///
/// Creates the service client using the credentials and client config.
///
///
///
///
private static AmazonServiceClient CreateClient(Type serviceInterfaceType, AWSCredentials credentials, ClientConfig config)
{
var clientTypeName = serviceInterfaceType.Namespace + "." + serviceInterfaceType.Name.Substring(1) + "Client";
var clientType = serviceInterfaceType.GetTypeInfo().Assembly.GetType(clientTypeName);
if (clientType == null)
{
throw new AmazonClientException($"Failed to find service client {clientTypeName} which implements {serviceInterfaceType.FullName}.");
}
var constructor = clientType.GetConstructor(new Type[] { typeof(AWSCredentials), config.GetType() });
if (constructor == null)
{
throw new AmazonClientException($"Service client {clientTypeName} missing a constructor with parameters AWSCredentials and {config.GetType().FullName}.");
}
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.
///
///
///
private static ClientConfig CreateConfig(Type serviceInterfaceType, AWSOptions options)
{
var configTypeName = serviceInterfaceType.Namespace + "." + serviceInterfaceType.Name.Substring(1) + "Config";
var configType = serviceInterfaceType.GetTypeInfo().Assembly.GetType(configTypeName);
var constructor = configType.GetConstructor(EMPTY_TYPES);
ClientConfig config = constructor.Invoke(EMPTY_PARAMETERS) as ClientConfig;
if(options == null)
{
options = new AWSOptions();
}
if (options.DefaultConfigurationMode.HasValue)
{
config.DefaultConfigurationMode = options.DefaultConfigurationMode.Value;
}
var defaultConfig = options.DefaultClientConfig;
var emptyArray = new object[0];
var singleArray = new object[1];
var clientConfigTypeInfo = options.DefaultClientConfig.GetType();
var properties = clientConfigTypeInfo.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
if (property.GetMethod != null && property.SetMethod != null)
{
// Skip RegionEndpoint because it is set below and calling the get method on the
// property triggers the default region fallback mechanism.
if (string.Equals(property.Name, "RegionEndpoint", StringComparison.Ordinal))
continue;
// DefaultConfigurationMode is skipped from the DefaultClientConfig because it is expected to be set
// at the top level of AWSOptions which is done before this loop.
if (string.Equals(property.Name, "DefaultConfigurationMode", StringComparison.Ordinal))
continue;
// Skip setting RetryMode if it is set to legacy but the DefaultConfigurationMode is not legacy.
// This will allow the retry mode to be configured from the DefaultConfiguration.
// This is a workaround to handle the inability to tell if RetryMode was explicitly set.
if (string.Equals(property.Name, "RetryMode", StringComparison.Ordinal) &&
defaultConfig.RetryMode == RequestRetryMode.Legacy &&
config.DefaultConfigurationMode != DefaultConfigurationMode.Legacy)
continue;
singleArray[0] = property.GetMethod.Invoke(defaultConfig, emptyArray);
if (singleArray[0] != null)
{
property.SetMethod.Invoke(config, singleArray);
}
}
}
// Setting RegionEndpoint only if ServiceURL was not set, because ServiceURL value will be lost otherwise
if (options.Region != null && string.IsNullOrEmpty(defaultConfig.ServiceURL))
{
config.RegionEndpoint = options.Region;
}
return config;
}
}
}