Skip to content

Commit 927fd21

Browse files
abhishekkumamsAniruddh25seantleonard
authored
Adding application Name for mssql connections (#1620)
## Why make this change? - Closes #1532 ## What is this change? - Checking if Application Name is already present in the MsSql connection stirng - if yes, we append DAB APP NAME with ',' as seperator - else, we add a Application Name property to connection string. - Parsing happens only once, and the runtimeConfig object is updated with the connectionString with Application name. - So, subsequent calls do not do manipulation on conn_string. ## How was this tested? - [X] Kusto Queries - [X] Unit Tests ## Sample Request(s) - Application Name for Dab will be appended while connecting to Azure SQL DB for telemetry. - if Application Name is specified by the developer then DAB will append to it, - else will add Application Name property **Below are the Kusto Logs** ![image](https://github.com/Azure/data-api-builder/assets/102276754/30dc7bc4-16bb-42ac-9f5e-9370e182bf2b) ![image](https://github.com/Azure/data-api-builder/assets/102276754/3aff2cf6-e12c-4ac2-b4ce-6b7336da3efc) --------- Co-authored-by: Aniruddh Munde <[email protected]> Co-authored-by: Sean Leonard <[email protected]>
1 parent 60e38a2 commit 927fd21

21 files changed

Lines changed: 284 additions & 99 deletions

src/Azure.DataApiBuilder.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
2929
EndProject
3030
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.DataApiBuilder.Core", "Core\Azure.DataApiBuilder.Core.csproj", "{9668083A-8A54-40E8-9C19-789B317D3F41}"
3131
EndProject
32+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.DataApiBuilder.Product", "Product\Azure.DataApiBuilder.Product.csproj", "{E3D2076C-EE49-43A0-8F92-5FC41EC99DA7}"
33+
EndProject
3234
Global
3335
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3436
Debug|Any CPU = Debug|Any CPU
@@ -67,6 +69,10 @@ Global
6769
{9668083A-8A54-40E8-9C19-789B317D3F41}.Debug|Any CPU.Build.0 = Debug|Any CPU
6870
{9668083A-8A54-40E8-9C19-789B317D3F41}.Release|Any CPU.ActiveCfg = Release|Any CPU
6971
{9668083A-8A54-40E8-9C19-789B317D3F41}.Release|Any CPU.Build.0 = Release|Any CPU
72+
{E3D2076C-EE49-43A0-8F92-5FC41EC99DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
73+
{E3D2076C-EE49-43A0-8F92-5FC41EC99DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
74+
{E3D2076C-EE49-43A0-8F92-5FC41EC99DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
75+
{E3D2076C-EE49-43A0-8F92-5FC41EC99DA7}.Release|Any CPU.Build.0 = Release|Any CPU
7076
EndGlobalSection
7177
GlobalSection(SolutionProperties) = preSolution
7278
HideSolutionNode = FALSE

src/Cli.Tests/EndToEndTests.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using Azure.DataApiBuilder.Service;
4+
using Azure.DataApiBuilder.Product;
5+
using Microsoft.Data.SqlClient;
6+
using static Azure.DataApiBuilder.Product.ProductInfo;
57

68
namespace Cli.Tests;
79

@@ -104,11 +106,14 @@ public void TestInitForCosmosDBPostgreSql()
104106
[TestMethod]
105107
public void TestInitializingRestAndGraphQLGlobalSettings()
106108
{
107-
string[] args = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql", "--rest.path", "/rest-api", "--rest.disabled", "--graphql.path", "/graphql-api" };
109+
string[] args = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--connection-string", SAMPLE_TEST_CONN_STRING, "--database-type", "mssql", "--rest.path", "/rest-api", "--rest.disabled", "--graphql.path", "/graphql-api" };
108110
Program.Execute(args, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
109111

110112
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
111113

114+
SqlConnectionStringBuilder builder = new(runtimeConfig.DataSource.ConnectionString);
115+
Assert.AreEqual(DEFAULT_APP_NAME, builder.ApplicationName);
116+
112117
Assert.IsNotNull(runtimeConfig);
113118
Assert.AreEqual(DatabaseType.MSSQL, runtimeConfig.DataSource.DatabaseType);
114119
Assert.IsNotNull(runtimeConfig.Runtime);
@@ -124,7 +129,7 @@ public void TestInitializingRestAndGraphQLGlobalSettings()
124129
[TestMethod]
125130
public void TestAddEntity()
126131
{
127-
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--host-mode", "development", "--database-type", "mssql", "--connection-string", "localhost:5000", "--auth.provider", "StaticWebApps" };
132+
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--host-mode", "development", "--database-type", "mssql", "--connection-string", SAMPLE_TEST_CONN_STRING, "--auth.provider", "StaticWebApps" };
128133
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
129134

130135
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
@@ -185,7 +190,7 @@ public void TestVerifyAuthenticationOptions()
185190
[DataRow("prod", HostMode.Production, false)]
186191
public void EnsureHostModeEnumIsCaseInsensitive(string hostMode, HostMode hostModeEnumType, bool expectSuccess)
187192
{
188-
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--host-mode", hostMode, "--database-type", "mssql", "--connection-string", "localhost:5000" };
193+
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--host-mode", hostMode, "--database-type", "mssql", "--connection-string", SAMPLE_TEST_CONN_STRING };
189194
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
190195

191196
_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig);
@@ -206,7 +211,7 @@ public void EnsureHostModeEnumIsCaseInsensitive(string hostMode, HostMode hostMo
206211
[TestMethod]
207212
public void TestAddEntityWithoutIEnumerable()
208213
{
209-
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql", "--connection-string", "localhost:5000" };
214+
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql", "--connection-string", SAMPLE_TEST_CONN_STRING };
210215
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
211216

212217
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig), "Expected to parse the config file.");
@@ -239,7 +244,7 @@ public void TestAddEntityWithoutIEnumerable()
239244
[TestMethod]
240245
public Task TestConfigGeneratedAfterAddingEntityWithoutIEnumerables()
241246
{
242-
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql", "--connection-string", "localhost:5000",
247+
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql", "--connection-string", SAMPLE_TEST_CONN_STRING,
243248
"--set-session-context", "true" };
244249
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
245250

@@ -261,7 +266,7 @@ public Task TestConfigGeneratedAfterAddingEntityWithoutIEnumerables()
261266
public Task TestConfigGeneratedAfterAddingEntityWithSourceAsStoredProcedure()
262267
{
263268
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql",
264-
"--host-mode", "Development", "--connection-string", "testconnectionstring", "--set-session-context", "true" };
269+
"--host-mode", "Development", "--connection-string", SAMPLE_TEST_CONN_STRING, "--set-session-context", "true" };
265270
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
266271

267272
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
@@ -302,7 +307,7 @@ public Task TestConfigGeneratedAfterUpdatingEntityWithSourceAsStoredProcedure()
302307
public Task TestAddingStoredProcedureWithRestMethodsAndGraphQLOperations()
303308
{
304309
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql",
305-
"--host-mode", "Development", "--connection-string", "testconnectionstring", "--set-session-context", "true" };
310+
"--host-mode", "Development", "--connection-string", SAMPLE_TEST_CONN_STRING, "--set-session-context", "true" };
306311
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
307312

308313
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
@@ -324,7 +329,7 @@ public Task TestAddingStoredProcedureWithRestMethodsAndGraphQLOperations()
324329
public Task TestUpdatingStoredProcedureWithRestMethodsAndGraphQLOperations()
325330
{
326331
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql",
327-
"--host-mode", "Development", "--connection-string", "testconnectionstring", "--set-session-context", "true" };
332+
"--host-mode", "Development", "--connection-string", SAMPLE_TEST_CONN_STRING, "--set-session-context", "true" };
328333
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
329334

330335
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
@@ -352,7 +357,7 @@ public Task TestUpdatingStoredProcedureWithRestMethodsAndGraphQLOperations()
352357
public Task TestConfigGeneratedAfterAddingEntityWithSourceWithDefaultType()
353358
{
354359
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql", "--host-mode", "Development",
355-
"--connection-string", "testconnectionstring", "--set-session-context", "true" };
360+
"--connection-string", SAMPLE_TEST_CONN_STRING, "--set-session-context", "true" };
356361
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
357362

358363
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
@@ -374,7 +379,7 @@ public Task TestConfigGeneratedAfterAddingEntityWithSourceWithDefaultType()
374379
public void TestUpdateEntity()
375380
{
376381
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type",
377-
"mssql", "--connection-string", "localhost:5000" };
382+
"mssql", "--connection-string", SAMPLE_TEST_CONN_STRING };
378383
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
379384

380385
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
@@ -476,7 +481,7 @@ public void TestUpdateEntity()
476481
public Task TestUpdatingStoredProcedureWithRestMethods()
477482
{
478483
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql",
479-
"--host-mode", "Development", "--connection-string", "testconnectionstring", "--set-session-context", "true" };
484+
"--host-mode", "Development", "--connection-string", SAMPLE_TEST_CONN_STRING, "--set-session-context", "true" };
480485
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
481486

482487
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
@@ -733,7 +738,7 @@ public async Task TestExitOfRuntimeEngineWithInvalidConfig(
733738
public void TestBaseRouteIsConfigurableForSWA(string authProvider, bool isExceptionExpected)
734739
{
735740
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--host-mode", "development", "--database-type", "mssql",
736-
"--connection-string", "localhost:5000", "--auth.provider", authProvider, "--runtime.base-route", "base-route" };
741+
"--connection-string", SAMPLE_TEST_CONN_STRING, "--auth.provider", authProvider, "--runtime.base-route", "base-route" };
737742

738743
if (!Enum.TryParse(authProvider, ignoreCase: true, out EasyAuthType _))
739744
{

src/Cli.Tests/TestHelper.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ public static class TestHelper
88
// Config file name for tests
99
public const string TEST_RUNTIME_CONFIG_FILE = "dab-config-test.json";
1010

11+
public const string SAMPLE_TEST_CONN_STRING = "Data Source=<>;Initial Catalog=<>;User ID=<>;Password=<>;";
12+
1113
// test schema for cosmosDB
1214
public const string TEST_SCHEMA_FILE = "test-schema.gql";
1315
public const string DAB_DRAFT_SCHEMA_TEST_PATH = "https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json";
@@ -70,7 +72,7 @@ public static Process ExecuteDabCommand(string command, string flags)
7072
public const string SAMPLE_SCHEMA_DATA_SOURCE = SCHEMA_PROPERTY + "," + @"
7173
""data-source"": {
7274
""database-type"": ""mssql"",
73-
""connection-string"": ""testconnectionstring""
75+
""connection-string"": """ + SAMPLE_TEST_CONN_STRING + @"""
7476
}
7577
";
7678

src/Cli.Tests/UpdateEntityTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1042,7 +1042,7 @@ private static string GetInitialConfigString()
10421042
@"""$schema"": """ + DAB_DRAFT_SCHEMA_TEST_PATH + @"""" + "," +
10431043
@"""data-source"": {
10441044
""database-type"": ""mssql"",
1045-
""connection-string"": ""testconnectionstring""
1045+
""connection-string"": """ + SAMPLE_TEST_CONN_STRING + @"""
10461046
},
10471047
""runtime"": {
10481048
""rest"": {

src/Cli/Commands/AddOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
using System.IO.Abstractions;
55
using Azure.DataApiBuilder.Config;
6-
using Azure.DataApiBuilder.Service;
6+
using Azure.DataApiBuilder.Product;
77
using CommandLine;
88
using Microsoft.Extensions.Logging;
99
using static Cli.Utils;

src/Cli/Commands/InitOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System.IO.Abstractions;
55
using Azure.DataApiBuilder.Config;
66
using Azure.DataApiBuilder.Config.ObjectModel;
7-
using Azure.DataApiBuilder.Service;
7+
using Azure.DataApiBuilder.Product;
88
using CommandLine;
99
using Microsoft.Extensions.Logging;
1010
using static Cli.Utils;

src/Cli/Commands/StartOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
using System.IO.Abstractions;
55
using Azure.DataApiBuilder.Config;
6-
using Azure.DataApiBuilder.Service;
6+
using Azure.DataApiBuilder.Product;
77
using CommandLine;
88
using Microsoft.Extensions.Logging;
99
using static Cli.Utils;

src/Cli/Commands/UpdateOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
using System.IO.Abstractions;
55
using Azure.DataApiBuilder.Config;
6-
using Azure.DataApiBuilder.Service;
6+
using Azure.DataApiBuilder.Product;
77
using CommandLine;
88
using Microsoft.Extensions.Logging;
99
using static Cli.Utils;

src/Config/Azure.DataApiBuilder.Config.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>net6.0</TargetFramework>
@@ -15,6 +15,8 @@
1515
<ItemGroup>
1616
<PackageReference Include="Microsoft.AspNetCore.Authorization" />
1717
<PackageReference Include="System.IO.Abstractions" />
18+
<PackageReference Include="System.Drawing.Common" />
19+
<PackageReference Include="Microsoft.Data.SqlClient" />
1820
<PackageReference Include="StyleCop.Analyzers">
1921
<PrivateAssets>all</PrivateAssets>
2022
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -23,4 +25,8 @@
2325
<PackageReference Include="Humanizer" />
2426
</ItemGroup>
2527

28+
<ItemGroup>
29+
<ProjectReference Include="..\Product\Azure.DataApiBuilder.Product.csproj" />
30+
</ItemGroup>
31+
2632
</Project>

src/Config/RuntimeConfigLoader.cs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22
// Licensed under the MIT License.
33

44
using System.Diagnostics.CodeAnalysis;
5+
using System.Net;
6+
using System.Runtime.CompilerServices;
57
using System.Text.Json;
68
using System.Text.Json.Serialization;
79
using Azure.DataApiBuilder.Config.Converters;
810
using Azure.DataApiBuilder.Config.NamingPolicies;
911
using Azure.DataApiBuilder.Config.ObjectModel;
12+
using Azure.DataApiBuilder.Product;
13+
using Azure.DataApiBuilder.Service.Exceptions;
14+
using Microsoft.Data.SqlClient;
1015
using Microsoft.Extensions.Logging;
1116

17+
[assembly: InternalsVisibleTo("Azure.DataApiBuilder.Service.Tests")]
1218
namespace Azure.DataApiBuilder.Config;
1319

1420
public abstract class RuntimeConfigLoader
@@ -52,10 +58,20 @@ public static bool TryParseConfig(string json, [NotNullWhen(true)] out RuntimeCo
5258
return false;
5359
}
5460

61+
string updatedConnectionString = config.DataSource.ConnectionString;
62+
5563
if (!string.IsNullOrEmpty(connectionString))
5664
{
57-
config = config with { DataSource = config.DataSource with { ConnectionString = connectionString } };
65+
updatedConnectionString = connectionString;
66+
}
67+
68+
// Add Application Name for telemetry for MsSQL
69+
if (config.DataSource.DatabaseType is DatabaseType.MSSQL)
70+
{
71+
updatedConnectionString = GetConnectionStringWithApplicationName(updatedConnectionString);
5872
}
73+
74+
config = config with { DataSource = config.DataSource with { ConnectionString = updatedConnectionString } };
5975
}
6076
catch (JsonException ex)
6177
{
@@ -101,4 +117,57 @@ public static JsonSerializerOptions GetSerializationOptions()
101117
options.Converters.Add(new StringJsonConverterFactory());
102118
return options;
103119
}
120+
121+
/// <summary>
122+
/// It adds or replaces a property in the connection string with `Application Name` property.
123+
/// If the connection string already contains the property, it appends the property `Application Name` to the connection string,
124+
/// else add the Application Name property with DataApiBuilder Application Name based on hosted/oss platform.
125+
/// </summary>
126+
/// <param name="connectionString">Connection string for connecting to database.</param>
127+
/// <returns>Updated connection string with `Application Name` property.</returns>
128+
internal static string GetConnectionStringWithApplicationName(string connectionString)
129+
{
130+
// If the connection string is null, empty, or whitespace, return it as is.
131+
if (string.IsNullOrWhiteSpace(connectionString))
132+
{
133+
return connectionString;
134+
}
135+
136+
// Get the application name using ProductInfo.GetDataApiBuilderUserAgent().
137+
string applicationName = ProductInfo.GetDataApiBuilderUserAgent();
138+
139+
// Create a StringBuilder from the connection string.
140+
SqlConnectionStringBuilder connectionStringBuilder;
141+
try
142+
{
143+
connectionStringBuilder = new SqlConnectionStringBuilder(connectionString);
144+
}
145+
catch (Exception ex)
146+
{
147+
throw new DataApiBuilderException(
148+
message: DataApiBuilderException.CONNECTION_STRING_ERROR_MESSAGE,
149+
statusCode: HttpStatusCode.ServiceUnavailable,
150+
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization,
151+
innerException: ex);
152+
}
153+
154+
string defaultApplicationName = new SqlConnectionStringBuilder().ApplicationName;
155+
156+
// If the connection string does not contain the `Application Name` property, add it.
157+
// or if the connection string contains the `Application Name` property with default SqlClient library value, replace it with
158+
// the DataApiBuilder Application Name.
159+
if (string.IsNullOrWhiteSpace(connectionStringBuilder.ApplicationName)
160+
|| connectionStringBuilder.ApplicationName.Equals(defaultApplicationName, StringComparison.OrdinalIgnoreCase))
161+
{
162+
connectionStringBuilder.ApplicationName = applicationName;
163+
}
164+
else
165+
{
166+
// If the connection string contains the `Application Name` property with a value, update the value by adding the DataApiBuilder Application Name.
167+
connectionStringBuilder.ApplicationName += $",{applicationName}";
168+
}
169+
170+
// Return the updated connection string.
171+
return connectionStringBuilder.ConnectionString;
172+
}
104173
}

0 commit comments

Comments
 (0)