-
Notifications
You must be signed in to change notification settings - Fork 72
Expand file tree
/
Copy pathCosmosExtensionServices.cs
More file actions
158 lines (143 loc) · 6.86 KB
/
CosmosExtensionServices.cs
File metadata and controls
158 lines (143 loc) · 6.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Keys.Cryptography;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Encryption;
using Microsoft.Extensions.Logging;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;
namespace Cosmos.DataTransfer.CosmosExtension
{
public static class CosmosExtensionServices
{
// Static HttpClient instances with different configurations for reuse across connections
// This avoids connection exhaustion and properly handles credentials
private static readonly Lazy<HttpClient> _httpClientWithDefaultCredentials = new Lazy<HttpClient>(() =>
{
var handler = new HttpClientHandler
{
Credentials = CredentialCache.DefaultNetworkCredentials,
PreAuthenticate = false
};
return new HttpClient(handler);
});
private static readonly Lazy<HttpClient> _httpClientWithDefaultCredentialsAndPreAuth = new Lazy<HttpClient>(() =>
{
var handler = new HttpClientHandler
{
Credentials = CredentialCache.DefaultNetworkCredentials,
PreAuthenticate = true
};
return new HttpClient(handler);
});
public static CosmosClient CreateClient(CosmosSettingsBase settings, string displayName, ILogger logger, string? sourceDisplayName = null)
{
string userAgentString = CreateUserAgentString(displayName, sourceDisplayName);
var cosmosSerializer = new RawJsonCosmosSerializer();
if (settings is CosmosSinkSettings sinkSettings)
{
cosmosSerializer.SerializerSettings.NullValueHandling = sinkSettings.IgnoreNullValues
? Newtonsoft.Json.NullValueHandling.Ignore
: Newtonsoft.Json.NullValueHandling.Include;
}
var clientOptions = new CosmosClientOptions
{
ConnectionMode = settings.ConnectionMode,
ApplicationName = userAgentString,
AllowBulkExecution = settings.AllowBulkExecution,
EnableContentResponseOnWrite = settings.EnableContentResponseOnWrite,
Serializer = cosmosSerializer,
LimitToEndpoint = settings.LimitToEndpoint,
};
if (!string.IsNullOrEmpty(settings.WebProxy)){
var webProxy = new WebProxy(settings.WebProxy);
if (settings.UseDefaultProxyCredentials)
{
webProxy.UseDefaultCredentials = true;
}
clientOptions.WebProxy = webProxy;
}
// Configure the HttpClient with default credentials if requested
// This enables authenticated proxy support for the underlying HTTP connections
if (settings.UseDefaultCredentials)
{
clientOptions.HttpClientFactory = settings.PreAuthenticate
? () => _httpClientWithDefaultCredentialsAndPreAuth.Value
: () => _httpClientWithDefaultCredentials.Value;
}
// Disable SSL certificate validation for development scenarios
if (settings.DisableSslValidation)
{
logger.LogWarning("SSL certificate validation is DISABLED. This should ONLY be used for development scenarios. Never use in production.");
clientOptions.ServerCertificateCustomValidationCallback = (cert, chain, errors) => true;
}
CosmosClient? cosmosClient;
if (settings.UseRbacAuth)
{
TokenCredential tokenCredential = new DefaultAzureCredential(includeInteractiveCredentials: settings.EnableInteractiveCredentials);
if(settings.InitClientEncryption)
{
var keyResolver = new KeyResolver(tokenCredential);
cosmosClient = new CosmosClient(settings.AccountEndpoint, tokenCredential, clientOptions)
.WithEncryption(keyResolver, KeyEncryptionKeyResolverName.AzureKeyVault);
}
else
{
cosmosClient = new CosmosClient(settings.AccountEndpoint, tokenCredential, clientOptions);
}
}
else
{
cosmosClient = new CosmosClient(settings.ConnectionString, clientOptions);
}
return cosmosClient;
}
private static string CreateUserAgentString(string displayName, string? sourceDisplayName)
{
// based on:
//UserAgentSuffix = String.Format(CultureInfo.InvariantCulture, Resources.CustomUserAgentSuffixFormat,
// entryAssembly == null ? Resources.UnknownEntryAssembly : entryAssembly.GetName().Name,
// Assembly.GetExecutingAssembly().GetName().Version,
// context.SourceName, context.SinkName,
// isShardedImport ? Resources.ShardedImportDesignator : String.Empty)
string sourceName = StripSpecialChars(sourceDisplayName ?? "");
string sinkName = StripSpecialChars(displayName);
var entryAssembly = Assembly.GetEntryAssembly();
bool isShardedImport = false;
string userAgentString = string.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2}-{3}{4}",
entryAssembly == null ? "dtr" : entryAssembly.GetName().Name,
Assembly.GetExecutingAssembly().GetName().Version,
sourceName, sinkName,
isShardedImport ? "-Sharded" : string.Empty);
return userAgentString;
}
private static string StripSpecialChars(string displayName)
{
return Regex.Replace(displayName, "[^\\w]", "", RegexOptions.Compiled);
}
public static async Task VerifyContainerAccess(Container? container, string? name, ILogger logger, CancellationToken cancellationToken)
{
if (container == null)
{
logger.LogError("Failed to initialize Container {Container}", name);
throw new Exception("Cosmos container unavailable for write");
}
try
{
var props = await container.ReadContainerAsync(cancellationToken: cancellationToken);
if (props.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new Exception("Unable to read Container properties");
}
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to connect to CosmosDB. Please check your connection settings and try again.");
throw new InvalidOperationException("Failed to create CosmosClient");
}
}
}
}