From 0538944957f0f9212d8288f05a5e29bfa394314f Mon Sep 17 00:00:00 2001 From: chgagnon Date: Sun, 12 Dec 2021 12:54:35 -0800 Subject: [PATCH 1/9] initial --- .editorconfig | 3 +- ...rosoft.Azure.WebJobs.Extensions.Sql.csproj | 1 + src/SqlBindingConfigProvider.cs | 4 +- src/SqlConverters.cs | 11 +- src/Telemetry/Telemetry.cs | 185 ++++++++++++++++++ src/Telemetry/TelemetryCommonProperties.cs | 105 ++++++++++ test/Unit/SqlInputBindingTests.cs | 2 +- 7 files changed, 306 insertions(+), 5 deletions(-) create mode 100644 src/Telemetry/Telemetry.cs create mode 100644 src/Telemetry/TelemetryCommonProperties.cs diff --git a/.editorconfig b/.editorconfig index 2136cb7cc..51631450f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,8 @@ root = true [*.cs] dotnet_analyzer_diagnostic.severity = error - +dotnet_diagnostic.IDE0130.severity = none + # Documentation related errors, remove once they are fixed dotnet_diagnostic.CS1591.severity = none dotnet_diagnostic.CS1573.severity = none diff --git a/src/Microsoft.Azure.WebJobs.Extensions.Sql.csproj b/src/Microsoft.Azure.WebJobs.Extensions.Sql.csproj index 25a73dedd..691e8919b 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.Sql.csproj +++ b/src/Microsoft.Azure.WebJobs.Extensions.Sql.csproj @@ -25,6 +25,7 @@ + diff --git a/src/SqlBindingConfigProvider.cs b/src/SqlBindingConfigProvider.cs index e69313e15..e9c245395 100644 --- a/src/SqlBindingConfigProvider.cs +++ b/src/SqlBindingConfigProvider.cs @@ -49,9 +49,9 @@ public void Initialize(ExtensionConfigContext context) FluentBindingRule inputOutputRule = context.AddBindingRule(); var converter = new SqlConverter(this._configuration); inputOutputRule.BindToInput(converter); - inputOutputRule.BindToInput(typeof(SqlGenericsConverter), this._configuration); + inputOutputRule.BindToInput(typeof(SqlGenericsConverter), this._configuration, this._loggerFactory); inputOutputRule.BindToCollector(typeof(SqlAsyncCollectorBuilder<>), this._configuration, this._loggerFactory); - inputOutputRule.BindToInput(typeof(SqlGenericsConverter<>), this._configuration); + inputOutputRule.BindToInput(typeof(SqlGenericsConverter<>), this._configuration, this._loggerFactory); } } } \ No newline at end of file diff --git a/src/SqlConverters.cs b/src/SqlConverters.cs index 535738b0c..b40084ad5 100644 --- a/src/SqlConverters.cs +++ b/src/SqlConverters.cs @@ -6,8 +6,10 @@ using System.Data; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.WebJobs.Logging; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; namespace Microsoft.Azure.WebJobs.Extensions.Sql @@ -52,6 +54,8 @@ internal class SqlGenericsConverter : IAsyncConverter /// Initializes a new instance of the "/> class. /// @@ -59,9 +63,10 @@ internal class SqlGenericsConverter : IAsyncConverter /// Thrown if the configuration is null /// - public SqlGenericsConverter(IConfiguration configuration) + public SqlGenericsConverter(IConfiguration configuration, ILoggerFactory loggerFactory) { this._configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + this._logger = loggerFactory?.CreateLogger(LogCategories.Bindings) ?? throw new ArgumentNullException(nameof(loggerFactory)); } /// @@ -105,6 +110,8 @@ async Task IAsyncConverter.ConvertAsync(SqlAttribu /// public virtual async Task BuildItemFromAttributeAsync(SqlAttribute attribute) { + this._logger.LogInformation($"Fetching data {attribute.CommandText}."); + var properties = new Dictionary(); using SqlConnection connection = SqlBindingUtilities.BuildConnection(attribute.ConnectionStringSetting, this._configuration); // Ideally, we would like to move away from using SqlDataAdapter both here and in the // SqlAsyncCollector since it does not support asynchronous operations. @@ -113,6 +120,8 @@ public virtual async Task BuildItemFromAttributeAsync(SqlAttribute attri using SqlCommand command = SqlBindingUtilities.BuildCommand(attribute, connection); adapter.SelectCommand = command; await connection.OpenAsync(); + properties.Add("ServerVersion", connection.ServerVersion); + Telemetry.Telemetry.Instance.TrackEvent("inputQuery", properties, null, this._logger); var dataTable = new DataTable(); adapter.Fill(dataTable); return JsonConvert.SerializeObject(dataTable); diff --git a/src/Telemetry/Telemetry.cs b/src/Telemetry/Telemetry.cs new file mode 100644 index 000000000..e1d4607d4 --- /dev/null +++ b/src/Telemetry/Telemetry.cs @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Extensibility; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Telemetry +{ + public sealed class Telemetry + { + internal static Telemetry Instance = new Telemetry(typeof(Telemetry).Assembly.GetName().Version.ToString(), "azure-functions-sql-ext"); + + private readonly string _eventsNamespace; + internal static string CurrentSessionId; + private TelemetryClient _client; + private Dictionary _commonProperties; + private Dictionary _commonMeasurements; + private Task _trackEventTask; + + private const string InstrumentationKey = "9f1f76dd-a432-4b93-ba9e-c98336deacb1"; + public const string TelemetryOptout = "AZUREFUNCTIONS_SQLEXT_TELEMETRY_OPTOUT"; + + public const string WelcomeMessage = @"Welcome to .NET Interactive! +--------------------- +Telemetry +--------- +The .NET Core tools collect usage data in order to help us improve your experience.The data is anonymous and doesn't include command-line arguments. The data is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_INTERACTIVE_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell. +"; + + public Telemetry( + string productVersion, + string eventsNamespace, + string sessionId = null, + bool blockThreadInitialization = false) + { + if (string.IsNullOrWhiteSpace(eventsNamespace)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventsNamespace)); + } + this._eventsNamespace = eventsNamespace; + this.Enabled = !GetEnvironmentVariableAsBool(TelemetryOptout); // && PermissionExists(sentinel); + + if (!this.Enabled) + { + return; + } + + // Store the session ID in a static field so that it can be reused + CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); + + if (blockThreadInitialization) + { + this.InitializeTelemetry(productVersion); + } + else + { + //initialize in task to offload to parallel thread + this._trackEventTask = Task.Factory.StartNew(() => this.InitializeTelemetry(productVersion)); + } + } + + public bool Enabled { get; } + + // public static bool SkipFirstTimeExperience => GetEnvironmentVariableAsBool(FirstTimeUseNoticeSentinel.SkipFirstTimeExperienceEnvironmentVariableName, false); + + private static bool GetEnvironmentVariableAsBool(string name, bool defaultValue = false) + { + string str = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(str)) + { + return defaultValue; + } + + switch (str.ToLowerInvariant()) + { + case "true": + case "1": + case "yes": + return true; + case "false": + case "0": + case "no": + return false; + default: + return defaultValue; + } + } + + public void TrackEvent(string eventName, IDictionary properties, + IDictionary measurements, ILogger logger) + { + logger.LogInformation($"Sending event {eventName}"); + if (!this.Enabled) + { + return; + } + + //continue task in existing parallel thread + this._trackEventTask = this._trackEventTask.ContinueWith( + x => this.TrackEventTask(eventName, properties, measurements) + ); + } + + private void InitializeTelemetry(string productVersion) + { + try + { + var config = new TelemetryConfiguration(InstrumentationKey); + this._client = new TelemetryClient(config); + this._client.Context.Session.Id = CurrentSessionId; + this._client.Context.Device.OperatingSystem = RuntimeInformation.OSDescription; + + this._commonProperties = new TelemetryCommonProperties(productVersion).GetTelemetryCommonProperties(); + this._commonMeasurements = new Dictionary(); + } + catch (Exception e) + { + this._client = null; + // we don't want to fail the tool if telemetry fails. + Debug.Fail(e.ToString()); + } + } + + private void TrackEventTask( + string eventName, + IDictionary properties, + IDictionary measurements) + { + if (this._client is null) + { + return; + } + + try + { + Dictionary eventProperties = this.GetEventProperties(properties); + Dictionary eventMeasurements = this.GetEventMeasures(measurements); + + this._client.TrackEvent($"{this._eventsNamespace}/{eventName}", eventProperties, eventMeasurements); + this._client.Flush(); + } + catch (Exception e) + { + Debug.Fail(e.ToString()); + } + } + + private Dictionary GetEventMeasures(IDictionary measurements) + { + var eventMeasurements = new Dictionary(this._commonMeasurements); + if (measurements != null) + { + foreach (KeyValuePair measurement in measurements) + { + eventMeasurements[measurement.Key] = measurement.Value; + } + } + return eventMeasurements; + } + + private Dictionary GetEventProperties(IDictionary properties) + { + if (properties != null) + { + var eventProperties = new Dictionary(this._commonProperties); + foreach (KeyValuePair property in properties) + { + eventProperties[property.Key] = property.Value; + } + return eventProperties; + } + else + { + return this._commonProperties; + } + } + } +} + diff --git a/src/Telemetry/TelemetryCommonProperties.cs b/src/Telemetry/TelemetryCommonProperties.cs new file mode 100644 index 000000000..ea966ce84 --- /dev/null +++ b/src/Telemetry/TelemetryCommonProperties.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Telemetry +{ + public class TelemetryCommonProperties + { + private readonly string _productVersion; + + public TelemetryCommonProperties( + string productVersion) + // Func hasher = null, + // Func getMACAddress = null, + // IUserLevelCacheWriter userLevelCacheWriter = null) + { + this._productVersion = productVersion; + // this._hasher = hasher ?? Sha256Hasher.Hash; + // this._getMACAddress = getMACAddress ?? MacAddressGetter.GetMacAddress; + // this._userLevelCacheWriter = userLevelCacheWriter ?? new UserLevelCacheWriter(productVersion); + } + + // private readonly IUserLevelCacheWriter _userLevelCacheWriter; + // private readonly IDockerContainerDetector _dockerContainerDetector; + // private readonly Func _hasher; + // private readonly Func _getMACAddress; + private const string OSVersion = "OS Version"; + // private const string OSPlatform = "OS Platform"; + // private const string RuntimeId = "Runtime Id"; + private const string ProductVersion = "Product Version"; + // private const string MachineId = "Machine ID"; + // private const string KernelVersion = "Kernel Version"; + + // private const string MachineIdCacheKey = "MachineId"; + + public Dictionary GetTelemetryCommonProperties() + { + return new Dictionary + { + {OSVersion, RuntimeInformation.OSDescription}, + //{OSPlatform, RuntimeEnvironment.OperatingSystemPlatform.ToString()}, + //{RuntimeId, RuntimeEnvironment.GetRuntimeIdentifier()}, + {ProductVersion, this._productVersion}, + //{MachineId, GetMachineId()}, + //{KernelVersion, GetKernelVersion()} + }; + } + /* + private string GetMachineId() + { + return _userLevelCacheWriter.RunWithCache(MachineIdCacheKey, () => + { + string macAddress = _getMACAddress(); + return macAddress is not null + ? _hasher(macAddress) + : Guid.NewGuid().ToString(); + }); + } + */ + + /// + /// Returns a string identifying the OS kernel. + /// For Unix this currently comes from "uname -srv". + /// For Windows this currently comes from RtlGetVersion(). + /// + /// Here are some example values: + /// + /// Alpine.36 Linux 4.9.60-linuxkit-aufs #1 SMP Mon Nov 6 16:00:12 UTC 2017 + /// Centos.73 Linux 3.10.0-514.26.2.el7.x86_64 #1 SMP Tue Jul 4 15:04:05 UTC 2017 + /// Debian.87 Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.39-1+deb8u2 (2017-03-07) + /// Debian.90 Linux 4.9.0-2-amd64 #1 SMP Debian 4.9.18-1 (2017-03-30) + /// fedora.25 Linux 4.11.3-202.fc25.x86_64 #1 SMP Mon Jun 5 16:38:21 UTC 2017 + /// Fedora.26 Linux 4.14.15-200.fc26.x86_64 #1 SMP Wed Jan 24 04:26:15 UTC 2018 + /// Fedora.27 Linux 4.14.14-300.fc27.x86_64 #1 SMP Fri Jan 19 13:19:54 UTC 2018 + /// OpenSuse.423 Linux 4.4.104-39-default #1 SMP Thu Jan 4 08:11:03 UTC 2018 (7db1912) + /// RedHat.69 Linux 2.6.32-696.20.1.el6.x86_64 #1 SMP Fri Jan 12 15:07:59 EST 2018 + /// RedHat.72 Linux 3.10.0-514.21.1.el7.x86_64 #1 SMP Sat Apr 22 02:41:35 EDT 2017 + /// RedHat.73 Linux 3.10.0-514.21.1.el7.x86_64 #1 SMP Sat Apr 22 02:41:35 EDT 2017 + /// SLES.12 Linux 4.4.103-6.38-default #1 SMP Mon Dec 25 20:44:33 UTC 2017 (e4b9067) + /// suse.422 Linux 4.4.49-16-default #1 SMP Sun Feb 19 17:40:35 UTC 2017 (70e9954) + /// Ubuntu.1404 Linux 3.19.0-65-generic #73~14.04.1-Ubuntu SMP Wed Jun 29 21:05:22 UTC 2016 + /// Ubuntu.1604 Linux 4.13.0-1005-azure #7-Ubuntu SMP Mon Jan 8 21:37:36 UTC 2018 + /// Ubuntu.1604.WSL Linux 4.4.0-43-Microsoft #1-Microsoft Wed Dec 31 14:42:53 PST 2014 + /// Ubuntu.1610 Linux 4.8.0-45-generic #48-Ubuntu SMP Fri Mar 24 11:46:39 UTC 2017 + /// Ubuntu.1704 Linux 4.10.0-19-generic #21-Ubuntu SMP Thu Apr 6 17:04:57 UTC 2017 + /// Ubuntu.1710 Linux 4.13.0-25-generic #29-Ubuntu SMP Mon Jan 8 21:14:41 UTC 2018 + /// OSX1012 Darwin 16.7.0 Darwin Kernel Version 16.7.0: Thu Jan 11 22:59:40 PST 2018; root:xnu-3789.73.8~1/RELEASE_X86_64 + /// OSX1013 Darwin 17.4.0 Darwin Kernel Version 17.4.0: Sun Dec 17 09:19:54 PST 2017; root:xnu-4570.41.2~1/RELEASE_X86_64 + /// Windows.10 Microsoft Windows 10.0.14393 + /// Windows.10.Core Microsoft Windows 10.0.14393 + /// Windows.10.Nano Microsoft Windows 10.0.14393 + /// Windows.7 Microsoft Windows 6.1.7601 S + /// Windows.81 Microsoft Windows 6.3.9600 + /// + /* + private static string GetKernelVersion() + { + return RuntimeInformation.OSDescription; + } + */ + } +} + diff --git a/test/Unit/SqlInputBindingTests.cs b/test/Unit/SqlInputBindingTests.cs index 2eba40984..fe7dd9cff 100644 --- a/test/Unit/SqlInputBindingTests.cs +++ b/test/Unit/SqlInputBindingTests.cs @@ -27,7 +27,7 @@ public void TestNullConfiguration() Assert.Throws(() => new SqlBindingConfigProvider(null, loggerFactory.Object)); Assert.Throws(() => new SqlBindingConfigProvider(config.Object, null)); Assert.Throws(() => new SqlConverter(null)); - Assert.Throws(() => new SqlGenericsConverter(null)); + Assert.Throws(() => new SqlGenericsConverter(null, loggerFactory.Object)); } [Fact] From 11a41395c09049066c6b41c73b60d5e8d3c8c4e4 Mon Sep 17 00:00:00 2001 From: chgagnon Date: Fri, 21 Jan 2022 11:47:37 -0800 Subject: [PATCH 2/9] Cleanup --- src/SqlBindingConfigProvider.cs | 1 + src/Telemetry/Telemetry.cs | 94 ++++++++-------------- src/Telemetry/TelemetryCommonProperties.cs | 79 +----------------- src/Telemetry/TelemetryUtils.cs | 21 +++++ src/Utils.cs | 64 +++++++++++++++ 5 files changed, 124 insertions(+), 135 deletions(-) create mode 100644 src/Telemetry/TelemetryUtils.cs create mode 100644 src/Utils.cs diff --git a/src/SqlBindingConfigProvider.cs b/src/SqlBindingConfigProvider.cs index e9c245395..782210ee8 100644 --- a/src/SqlBindingConfigProvider.cs +++ b/src/SqlBindingConfigProvider.cs @@ -45,6 +45,7 @@ public void Initialize(ExtensionConfigContext context) { throw new ArgumentNullException(nameof(context)); } + Telemetry.Telemetry.Instance.Initialize(this._configuration, this._loggerFactory); #pragma warning disable CS0618 // Fine to use this for our stuff FluentBindingRule inputOutputRule = context.AddBindingRule(); var converter = new SqlConverter(this._configuration); diff --git a/src/Telemetry/Telemetry.cs b/src/Telemetry/Telemetry.cs index e1d4607d4..0339c143b 100644 --- a/src/Telemetry/Telemetry.cs +++ b/src/Telemetry/Telemetry.cs @@ -9,97 +9,73 @@ using Microsoft.Extensions.Logging; using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.Extensions.Configuration; +using Microsoft.Azure.WebJobs.Logging; namespace Microsoft.Azure.WebJobs.Extensions.Sql.Telemetry { public sealed class Telemetry { - internal static Telemetry Instance = new Telemetry(typeof(Telemetry).Assembly.GetName().Version.ToString(), "azure-functions-sql-ext"); + internal static Telemetry Instance = new Telemetry(); - private readonly string _eventsNamespace; + private const string EventsNamespace = "azure-functions-sql-bindings"; internal static string CurrentSessionId; private TelemetryClient _client; private Dictionary _commonProperties; private Dictionary _commonMeasurements; private Task _trackEventTask; - - private const string InstrumentationKey = "9f1f76dd-a432-4b93-ba9e-c98336deacb1"; - public const string TelemetryOptout = "AZUREFUNCTIONS_SQLEXT_TELEMETRY_OPTOUT"; - - public const string WelcomeMessage = @"Welcome to .NET Interactive! + private ILogger _logger; + private bool _initialized; + private const string InstrumentationKey = "98697a1c-1416-486a-99ac-c6c74ebe5ebd"; + /// + /// The environment variable used for opting out of telemetry + /// + public const string TelemetryOptoutEnvVar = "AZUREFUNCTIONS_SQLBINDINGS_TELEMETRY_OPTOUT"; + /// + /// The app setting used for opting out of telemetry + /// + public const string TelemetryOptoutSetting = "AzureFunctionsSqlBindingsTelemetryOptOut"; + + public const string WelcomeMessage = @"SQL Bindings for Azure Functions --------------------- Telemetry --------- -The .NET Core tools collect usage data in order to help us improve your experience.The data is anonymous and doesn't include command-line arguments. The data is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_INTERACTIVE_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell. +This extension collect usage data in order to help us improve your experience. The data is anonymous and doesn't include any personal information. You can opt-out of telemetry by setting the " + TelemetryOptoutEnvVar + " environment variable or the " + TelemetryOptoutSetting + @" + app setting to '1', 'true' or 'yes'; "; - public Telemetry( - string productVersion, - string eventsNamespace, - string sessionId = null, - bool blockThreadInitialization = false) + public void Initialize(IConfiguration config, ILoggerFactory loggerFactory) { - if (string.IsNullOrWhiteSpace(eventsNamespace)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventsNamespace)); - } - this._eventsNamespace = eventsNamespace; - this.Enabled = !GetEnvironmentVariableAsBool(TelemetryOptout); // && PermissionExists(sentinel); - + this._logger = loggerFactory.CreateLogger(LogCategories.Bindings); + this.Enabled = !(Utils.GetEnvironmentVariableAsBool(TelemetryOptoutEnvVar) || Utils.GetConfigSettingAsBool(TelemetryOptoutSetting, config)); if (!this.Enabled) { + this._logger.LogInformation("Telemetry disabled"); return; } - + this._logger.LogInformation(WelcomeMessage); // Store the session ID in a static field so that it can be reused - CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); + CurrentSessionId = Guid.NewGuid().ToString(); - if (blockThreadInitialization) - { - this.InitializeTelemetry(productVersion); - } - else - { - //initialize in task to offload to parallel thread - this._trackEventTask = Task.Factory.StartNew(() => this.InitializeTelemetry(productVersion)); - } + //initialize in task to offload to parallel thread + string productVersion = typeof(Telemetry).Assembly.GetName().Version.ToString(); + this._trackEventTask = Task.Factory.StartNew(() => this.InitializeTelemetry(productVersion)); + this._initialized = true; } - public bool Enabled { get; } + public bool Enabled { get; private set; } - // public static bool SkipFirstTimeExperience => GetEnvironmentVariableAsBool(FirstTimeUseNoticeSentinel.SkipFirstTimeExperienceEnvironmentVariableName, false); - - private static bool GetEnvironmentVariableAsBool(string name, bool defaultValue = false) + public void TrackEvent(string eventName, IDictionary properties, + IDictionary measurements) { - string str = Environment.GetEnvironmentVariable(name); - if (string.IsNullOrEmpty(str)) - { - return defaultValue; - } - - switch (str.ToLowerInvariant()) + if (!this._initialized) { - case "true": - case "1": - case "yes": - return true; - case "false": - case "0": - case "no": - return false; - default: - return defaultValue; + return; } - } - - public void TrackEvent(string eventName, IDictionary properties, - IDictionary measurements, ILogger logger) - { - logger.LogInformation($"Sending event {eventName}"); if (!this.Enabled) { return; } + this._logger.LogInformation($"Sending event {eventName}"); //continue task in existing parallel thread this._trackEventTask = this._trackEventTask.ContinueWith( @@ -142,7 +118,7 @@ private void TrackEventTask( Dictionary eventProperties = this.GetEventProperties(properties); Dictionary eventMeasurements = this.GetEventMeasures(measurements); - this._client.TrackEvent($"{this._eventsNamespace}/{eventName}", eventProperties, eventMeasurements); + this._client.TrackEvent($"{EventsNamespace}/{eventName}", eventProperties, eventMeasurements); this._client.Flush(); } catch (Exception e) diff --git a/src/Telemetry/TelemetryCommonProperties.cs b/src/Telemetry/TelemetryCommonProperties.cs index ea966ce84..d384d5bf7 100644 --- a/src/Telemetry/TelemetryCommonProperties.cs +++ b/src/Telemetry/TelemetryCommonProperties.cs @@ -12,94 +12,21 @@ public class TelemetryCommonProperties public TelemetryCommonProperties( string productVersion) - // Func hasher = null, - // Func getMACAddress = null, - // IUserLevelCacheWriter userLevelCacheWriter = null) { this._productVersion = productVersion; - // this._hasher = hasher ?? Sha256Hasher.Hash; - // this._getMACAddress = getMACAddress ?? MacAddressGetter.GetMacAddress; - // this._userLevelCacheWriter = userLevelCacheWriter ?? new UserLevelCacheWriter(productVersion); } - // private readonly IUserLevelCacheWriter _userLevelCacheWriter; - // private readonly IDockerContainerDetector _dockerContainerDetector; - // private readonly Func _hasher; - // private readonly Func _getMACAddress; - private const string OSVersion = "OS Version"; - // private const string OSPlatform = "OS Platform"; - // private const string RuntimeId = "Runtime Id"; - private const string ProductVersion = "Product Version"; - // private const string MachineId = "Machine ID"; - // private const string KernelVersion = "Kernel Version"; - - // private const string MachineIdCacheKey = "MachineId"; + private const string OSVersion = "OSVersion"; + private const string ProductVersion = "ProductVersion"; public Dictionary GetTelemetryCommonProperties() { return new Dictionary { {OSVersion, RuntimeInformation.OSDescription}, - //{OSPlatform, RuntimeEnvironment.OperatingSystemPlatform.ToString()}, - //{RuntimeId, RuntimeEnvironment.GetRuntimeIdentifier()}, - {ProductVersion, this._productVersion}, - //{MachineId, GetMachineId()}, - //{KernelVersion, GetKernelVersion()} + {ProductVersion, this._productVersion} }; } - /* - private string GetMachineId() - { - return _userLevelCacheWriter.RunWithCache(MachineIdCacheKey, () => - { - string macAddress = _getMACAddress(); - return macAddress is not null - ? _hasher(macAddress) - : Guid.NewGuid().ToString(); - }); - } - */ - - /// - /// Returns a string identifying the OS kernel. - /// For Unix this currently comes from "uname -srv". - /// For Windows this currently comes from RtlGetVersion(). - /// - /// Here are some example values: - /// - /// Alpine.36 Linux 4.9.60-linuxkit-aufs #1 SMP Mon Nov 6 16:00:12 UTC 2017 - /// Centos.73 Linux 3.10.0-514.26.2.el7.x86_64 #1 SMP Tue Jul 4 15:04:05 UTC 2017 - /// Debian.87 Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.39-1+deb8u2 (2017-03-07) - /// Debian.90 Linux 4.9.0-2-amd64 #1 SMP Debian 4.9.18-1 (2017-03-30) - /// fedora.25 Linux 4.11.3-202.fc25.x86_64 #1 SMP Mon Jun 5 16:38:21 UTC 2017 - /// Fedora.26 Linux 4.14.15-200.fc26.x86_64 #1 SMP Wed Jan 24 04:26:15 UTC 2018 - /// Fedora.27 Linux 4.14.14-300.fc27.x86_64 #1 SMP Fri Jan 19 13:19:54 UTC 2018 - /// OpenSuse.423 Linux 4.4.104-39-default #1 SMP Thu Jan 4 08:11:03 UTC 2018 (7db1912) - /// RedHat.69 Linux 2.6.32-696.20.1.el6.x86_64 #1 SMP Fri Jan 12 15:07:59 EST 2018 - /// RedHat.72 Linux 3.10.0-514.21.1.el7.x86_64 #1 SMP Sat Apr 22 02:41:35 EDT 2017 - /// RedHat.73 Linux 3.10.0-514.21.1.el7.x86_64 #1 SMP Sat Apr 22 02:41:35 EDT 2017 - /// SLES.12 Linux 4.4.103-6.38-default #1 SMP Mon Dec 25 20:44:33 UTC 2017 (e4b9067) - /// suse.422 Linux 4.4.49-16-default #1 SMP Sun Feb 19 17:40:35 UTC 2017 (70e9954) - /// Ubuntu.1404 Linux 3.19.0-65-generic #73~14.04.1-Ubuntu SMP Wed Jun 29 21:05:22 UTC 2016 - /// Ubuntu.1604 Linux 4.13.0-1005-azure #7-Ubuntu SMP Mon Jan 8 21:37:36 UTC 2018 - /// Ubuntu.1604.WSL Linux 4.4.0-43-Microsoft #1-Microsoft Wed Dec 31 14:42:53 PST 2014 - /// Ubuntu.1610 Linux 4.8.0-45-generic #48-Ubuntu SMP Fri Mar 24 11:46:39 UTC 2017 - /// Ubuntu.1704 Linux 4.10.0-19-generic #21-Ubuntu SMP Thu Apr 6 17:04:57 UTC 2017 - /// Ubuntu.1710 Linux 4.13.0-25-generic #29-Ubuntu SMP Mon Jan 8 21:14:41 UTC 2018 - /// OSX1012 Darwin 16.7.0 Darwin Kernel Version 16.7.0: Thu Jan 11 22:59:40 PST 2018; root:xnu-3789.73.8~1/RELEASE_X86_64 - /// OSX1013 Darwin 17.4.0 Darwin Kernel Version 17.4.0: Sun Dec 17 09:19:54 PST 2017; root:xnu-4570.41.2~1/RELEASE_X86_64 - /// Windows.10 Microsoft Windows 10.0.14393 - /// Windows.10.Core Microsoft Windows 10.0.14393 - /// Windows.10.Nano Microsoft Windows 10.0.14393 - /// Windows.7 Microsoft Windows 6.1.7601 S - /// Windows.81 Microsoft Windows 6.3.9600 - /// - /* - private static string GetKernelVersion() - { - return RuntimeInformation.OSDescription; - } - */ } } diff --git a/src/Telemetry/TelemetryUtils.cs b/src/Telemetry/TelemetryUtils.cs new file mode 100644 index 000000000..d1b22f55f --- /dev/null +++ b/src/Telemetry/TelemetryUtils.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.Data.SqlClient; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Telemetry +{ + public static class TelemetryUtils + { + /// + /// Adds common connection properties to the property bag for a telemetry event. + /// + /// The property bag to add our connection properties to + /// The connection to add properties of + public static void AddConnectionProps(this Dictionary props, SqlConnection conn) + { + props.Add(nameof(SqlConnection.ServerVersion), conn.ServerVersion); + } + } +} \ No newline at end of file diff --git a/src/Utils.cs b/src/Utils.cs new file mode 100644 index 000000000..bb9f553ef --- /dev/null +++ b/src/Utils.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql +{ + public static class Utils + { + /// + /// Gets the specified environment variable and converts it to a boolean. + /// + /// Name of the environment variable + /// Value to use if the variable doesn't exist or is unable to be parsed + /// True if the variable exists and is set to a value that can be parsed as true, false otherwise + public static bool GetEnvironmentVariableAsBool(string name, bool defaultValue = false) + { + string str = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(str)) + { + return defaultValue; + } + + return str.AsBool(defaultValue); + } + + /// + /// Gets the specified configuration setting and converts it to a boolean. + /// + /// Key name of the setting + /// The config option to retrieve the value from + /// Value to use if the setting doesn't exist or is unable to be parsed + /// True if the setting exists and is set to a value that can be parsed as true, false otherwise + public static bool GetConfigSettingAsBool(string name, IConfiguration config, bool defaultValue = false) + { + return config.GetValue(name, defaultValue.ToString()).AsBool(defaultValue); + } + + /// + /// Converts the string into an equivalent boolean value. This is used instead of Convert.ToBool since that + /// doesn't handle converting the string value "1". + /// + /// The string to convert + /// Value to use if the string is unable to be converted, default is false + /// + private static bool AsBool(this string str, bool defaultValue = false) + { + switch (str.ToLowerInvariant()) + { + case "true": + case "1": + case "yes": + return true; + case "false": + case "0": + case "no": + return false; + default: + return defaultValue; + } + } + } +} From 642ef66e91fe190b16b291a7b7ec8f1be1589518 Mon Sep 17 00:00:00 2001 From: chgagnon Date: Fri, 21 Jan 2022 11:51:37 -0800 Subject: [PATCH 3/9] opt out + cleanup --- samples/local.settings.json | 5 ++--- src/Telemetry/Telemetry.cs | 8 ++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/samples/local.settings.json b/samples/local.settings.json index 068a09e74..2718bb5f5 100644 --- a/samples/local.settings.json +++ b/samples/local.settings.json @@ -1,8 +1,7 @@ { "IsEncrypted": false, "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", - "SqlConnectionString": "" -} + "AzureFunctionsSqlBindingsTelemetryOptOut": true + } } \ No newline at end of file diff --git a/src/Telemetry/Telemetry.cs b/src/Telemetry/Telemetry.cs index 0339c143b..a3c8a7213 100644 --- a/src/Telemetry/Telemetry.cs +++ b/src/Telemetry/Telemetry.cs @@ -56,8 +56,8 @@ public void Initialize(IConfiguration config, ILoggerFactory loggerFactory) // Store the session ID in a static field so that it can be reused CurrentSessionId = Guid.NewGuid().ToString(); - //initialize in task to offload to parallel thread string productVersion = typeof(Telemetry).Assembly.GetName().Version.ToString(); + //initialize in task to offload to parallel thread this._trackEventTask = Task.Factory.StartNew(() => this.InitializeTelemetry(productVersion)); this._initialized = true; } @@ -67,11 +67,7 @@ public void Initialize(IConfiguration config, ILoggerFactory loggerFactory) public void TrackEvent(string eventName, IDictionary properties, IDictionary measurements) { - if (!this._initialized) - { - return; - } - if (!this.Enabled) + if (!this._initialized || !this.Enabled) { return; } From daf05199eb6caead59981ebf2a9a80b24aecbb6b Mon Sep 17 00:00:00 2001 From: chgagnon Date: Fri, 21 Jan 2022 11:55:36 -0800 Subject: [PATCH 4/9] Remove extra stuff --- src/SqlConverters.cs | 11 +---------- test/Unit/SqlInputBindingTests.cs | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/SqlConverters.cs b/src/SqlConverters.cs index b40084ad5..535738b0c 100644 --- a/src/SqlConverters.cs +++ b/src/SqlConverters.cs @@ -6,10 +6,8 @@ using System.Data; using System.Threading; using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Logging; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; using Newtonsoft.Json; namespace Microsoft.Azure.WebJobs.Extensions.Sql @@ -54,8 +52,6 @@ internal class SqlGenericsConverter : IAsyncConverter /// Initializes a new instance of the "/> class. /// @@ -63,10 +59,9 @@ internal class SqlGenericsConverter : IAsyncConverter /// Thrown if the configuration is null /// - public SqlGenericsConverter(IConfiguration configuration, ILoggerFactory loggerFactory) + public SqlGenericsConverter(IConfiguration configuration) { this._configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - this._logger = loggerFactory?.CreateLogger(LogCategories.Bindings) ?? throw new ArgumentNullException(nameof(loggerFactory)); } /// @@ -110,8 +105,6 @@ async Task IAsyncConverter.ConvertAsync(SqlAttribu /// public virtual async Task BuildItemFromAttributeAsync(SqlAttribute attribute) { - this._logger.LogInformation($"Fetching data {attribute.CommandText}."); - var properties = new Dictionary(); using SqlConnection connection = SqlBindingUtilities.BuildConnection(attribute.ConnectionStringSetting, this._configuration); // Ideally, we would like to move away from using SqlDataAdapter both here and in the // SqlAsyncCollector since it does not support asynchronous operations. @@ -120,8 +113,6 @@ public virtual async Task BuildItemFromAttributeAsync(SqlAttribute attri using SqlCommand command = SqlBindingUtilities.BuildCommand(attribute, connection); adapter.SelectCommand = command; await connection.OpenAsync(); - properties.Add("ServerVersion", connection.ServerVersion); - Telemetry.Telemetry.Instance.TrackEvent("inputQuery", properties, null, this._logger); var dataTable = new DataTable(); adapter.Fill(dataTable); return JsonConvert.SerializeObject(dataTable); diff --git a/test/Unit/SqlInputBindingTests.cs b/test/Unit/SqlInputBindingTests.cs index fe7dd9cff..2eba40984 100644 --- a/test/Unit/SqlInputBindingTests.cs +++ b/test/Unit/SqlInputBindingTests.cs @@ -27,7 +27,7 @@ public void TestNullConfiguration() Assert.Throws(() => new SqlBindingConfigProvider(null, loggerFactory.Object)); Assert.Throws(() => new SqlBindingConfigProvider(config.Object, null)); Assert.Throws(() => new SqlConverter(null)); - Assert.Throws(() => new SqlGenericsConverter(null, loggerFactory.Object)); + Assert.Throws(() => new SqlGenericsConverter(null)); } [Fact] From eea3e5f7b6514063a1d184f30f3ac5220e5e7bed Mon Sep 17 00:00:00 2001 From: chgagnon Date: Fri, 21 Jan 2022 12:02:11 -0800 Subject: [PATCH 5/9] more cleanup --- .editorconfig | 3 ++- src/Microsoft.Azure.WebJobs.Extensions.Sql.csproj | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 51631450f..e2f0c4ea8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,8 +7,9 @@ root = true [*.cs] dotnet_analyzer_diagnostic.severity = error +# Namespace does not match folder structure - Ideally this should be enabled but it seems to have issues with root level files so disabling for now dotnet_diagnostic.IDE0130.severity = none - + # Documentation related errors, remove once they are fixed dotnet_diagnostic.CS1591.severity = none dotnet_diagnostic.CS1573.severity = none diff --git a/src/Microsoft.Azure.WebJobs.Extensions.Sql.csproj b/src/Microsoft.Azure.WebJobs.Extensions.Sql.csproj index 691e8919b..2b88a3617 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.Sql.csproj +++ b/src/Microsoft.Azure.WebJobs.Extensions.Sql.csproj @@ -18,8 +18,6 @@ MIT https://github.com/Azure/azure-functions-sql-extension pkgicon.png - - true true From ed599555e5dc4ddf75d4cc4e30886a29a38f40d8 Mon Sep 17 00:00:00 2001 From: chgagnon Date: Fri, 21 Jan 2022 12:03:56 -0800 Subject: [PATCH 6/9] Undo unneeded changes --- samples/local.settings.json | 1 + src/SqlBindingConfigProvider.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/local.settings.json b/samples/local.settings.json index 2718bb5f5..4f2e2a1a4 100644 --- a/samples/local.settings.json +++ b/samples/local.settings.json @@ -1,6 +1,7 @@ { "IsEncrypted": false, "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "AzureFunctionsSqlBindingsTelemetryOptOut": true } diff --git a/src/SqlBindingConfigProvider.cs b/src/SqlBindingConfigProvider.cs index 782210ee8..f42100aef 100644 --- a/src/SqlBindingConfigProvider.cs +++ b/src/SqlBindingConfigProvider.cs @@ -50,9 +50,9 @@ public void Initialize(ExtensionConfigContext context) FluentBindingRule inputOutputRule = context.AddBindingRule(); var converter = new SqlConverter(this._configuration); inputOutputRule.BindToInput(converter); - inputOutputRule.BindToInput(typeof(SqlGenericsConverter), this._configuration, this._loggerFactory); + inputOutputRule.BindToInput(typeof(SqlGenericsConverter), this._configuration); inputOutputRule.BindToCollector(typeof(SqlAsyncCollectorBuilder<>), this._configuration, this._loggerFactory); - inputOutputRule.BindToInput(typeof(SqlGenericsConverter<>), this._configuration, this._loggerFactory); + inputOutputRule.BindToInput(typeof(SqlGenericsConverter<>), this._configuration); } } } \ No newline at end of file From d83a1a8d0960ff0f3e5237ba75fcf547faf87562 Mon Sep 17 00:00:00 2001 From: chgagnon Date: Fri, 21 Jan 2022 12:12:19 -0800 Subject: [PATCH 7/9] Add README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 5e56d275e..974e945b6 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Further information on the Azure SQL binding for Azure Functions is also availab - [Single Row](#single-row) - [Primary Keys and Identity Columns](#primary-keys-and-identity-columns) - [Known Issues](#known-issues) + - [Telemetry](#telemetry) - [Trademarks](#trademarks) ## Quick Start @@ -526,6 +527,9 @@ This changes if one of the primary key columns is an identity column though. In - Output bindings against tables with columns of data types `NTEXT`, `TEXT`, or `IMAGE` are not supported and data upserts will fail. These types [will be removed](https://docs.microsoft.com/sql/t-sql/data-types/ntext-text-and-image-transact-sql) in a future version of SQL Server and are not compatible with the `OPENJSON` function used by this Azure Functions binding. - Case-sensitive [collations](https://docs.microsoft.com/sql/relational-databases/collations/collation-and-unicode-support#Collation_Defn) are not currently supported. This functionality will be added in a future release. [#133](https://github.com/Azure/azure-functions-sql-extension/issues/133) tracks progress on this issue. +## Telemetry + +This extension collect usage data in order to help us improve your experience. The data is anonymous and doesn't include any personal information. You can opt-out of telemetry by setting the `AZUREFUNCTIONS_SQLBINDINGS_TELEMETRY_OPTOUT` environment variable or the `AzureFunctionsSqlBindingsTelemetryOptOut` app setting (in your `*.settings.json` file) to '1', 'true' or 'yes'; ## Trademarks This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft’s Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party’s policies. \ No newline at end of file From 8b74eca3c459f92bb71bdf220c91cd34d77dfb04 Mon Sep 17 00:00:00 2001 From: chgagnon Date: Fri, 21 Jan 2022 12:37:42 -0800 Subject: [PATCH 8/9] Add back in connection string setting --- samples/local.settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/local.settings.json b/samples/local.settings.json index 4f2e2a1a4..0f0fd5e26 100644 --- a/samples/local.settings.json +++ b/samples/local.settings.json @@ -3,6 +3,7 @@ "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", + "SqlConnectionString": "", "AzureFunctionsSqlBindingsTelemetryOptOut": true } } \ No newline at end of file From 72e5a0218b7c2a98268287bd69fbc1212c861902 Mon Sep 17 00:00:00 2001 From: chgagnon Date: Fri, 21 Jan 2022 14:13:05 -0800 Subject: [PATCH 9/9] PR comments --- README.md | 1 + .../template-steps-build-test.yml | 2 + samples/local.settings.json | 3 +- src/Telemetry/Telemetry.cs | 2 +- test/Common/TestConfiguration.cs | 36 +++++++++++ test/Common/TestConfigurationSection.cs | 36 +++++++++++ test/Unit/UtilsTests.cs | 63 +++++++++++++++++++ 7 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 test/Common/TestConfiguration.cs create mode 100644 test/Common/TestConfigurationSection.cs create mode 100644 test/Unit/UtilsTests.cs diff --git a/README.md b/README.md index 974e945b6..53599c456 100644 --- a/README.md +++ b/README.md @@ -530,6 +530,7 @@ This changes if one of the primary key columns is an identity column though. In ## Telemetry This extension collect usage data in order to help us improve your experience. The data is anonymous and doesn't include any personal information. You can opt-out of telemetry by setting the `AZUREFUNCTIONS_SQLBINDINGS_TELEMETRY_OPTOUT` environment variable or the `AzureFunctionsSqlBindingsTelemetryOptOut` app setting (in your `*.settings.json` file) to '1', 'true' or 'yes'; + ## Trademarks This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft’s Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party’s policies. \ No newline at end of file diff --git a/builds/azure-pipelines/template-steps-build-test.yml b/builds/azure-pipelines/template-steps-build-test.yml index 1c2227da7..87d6b0af6 100644 --- a/builds/azure-pipelines/template-steps-build-test.yml +++ b/builds/azure-pipelines/template-steps-build-test.yml @@ -191,6 +191,7 @@ steps: env: TEST_SERVER: '$(testServer)' NODE_MODULES_PATH: '$(nodeModulesPath)' + AZUREFUNCTIONS_SQLBINDINGS_TELEMETRY_OPTOUT: '1' inputs: command: test projects: '${{ parameters.solution }}' @@ -201,6 +202,7 @@ steps: displayName: '.NET Test on Linux' env: SA_PASSWORD: '$(serverPassword)' + AZUREFUNCTIONS_SQLBINDINGS_TELEMETRY_OPTOUT: '1' inputs: command: test projects: '${{ parameters.solution }}' diff --git a/samples/local.settings.json b/samples/local.settings.json index 0f0fd5e26..e3a7a2e09 100644 --- a/samples/local.settings.json +++ b/samples/local.settings.json @@ -3,7 +3,6 @@ "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", - "SqlConnectionString": "", - "AzureFunctionsSqlBindingsTelemetryOptOut": true + "SqlConnectionString": "" } } \ No newline at end of file diff --git a/src/Telemetry/Telemetry.cs b/src/Telemetry/Telemetry.cs index a3c8a7213..c23d7d704 100644 --- a/src/Telemetry/Telemetry.cs +++ b/src/Telemetry/Telemetry.cs @@ -36,7 +36,7 @@ public sealed class Telemetry /// public const string TelemetryOptoutSetting = "AzureFunctionsSqlBindingsTelemetryOptOut"; - public const string WelcomeMessage = @"SQL Bindings for Azure Functions + public const string WelcomeMessage = @"Azure SQL binding for Azure Functions --------------------- Telemetry --------- diff --git a/test/Common/TestConfiguration.cs b/test/Common/TestConfiguration.cs new file mode 100644 index 000000000..dc9ae267e --- /dev/null +++ b/test/Common/TestConfiguration.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common +{ + internal class TestConfiguration : IConfiguration + { + private readonly IDictionary _sections = new Dictionary(); + public void AddSection(string key, IConfigurationSection section) + { + this._sections[key] = section; + } + + string IConfiguration.this[string key] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + IEnumerable IConfiguration.GetChildren() + { + throw new NotImplementedException(); + } + + IChangeToken IConfiguration.GetReloadToken() + { + throw new NotImplementedException(); + } + + IConfigurationSection IConfiguration.GetSection(string key) + { + return this._sections[key]; + } + } +} diff --git a/test/Common/TestConfigurationSection.cs b/test/Common/TestConfigurationSection.cs new file mode 100644 index 000000000..4b0f8362e --- /dev/null +++ b/test/Common/TestConfigurationSection.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common +{ + internal class TestConfigurationSection : IConfigurationSection + { + string IConfiguration.this[string key] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + string IConfigurationSection.Key => throw new NotImplementedException(); + + string IConfigurationSection.Path => throw new NotImplementedException(); + + string IConfigurationSection.Value { get; set; } + + IEnumerable IConfiguration.GetChildren() + { + throw new NotImplementedException(); + } + + IChangeToken IConfiguration.GetReloadToken() + { + throw new NotImplementedException(); + } + + IConfigurationSection IConfiguration.GetSection(string key) + { + throw new NotImplementedException(); + } + } +} diff --git a/test/Unit/UtilsTests.cs b/test/Unit/UtilsTests.cs new file mode 100644 index 000000000..4fa4a2729 --- /dev/null +++ b/test/Unit/UtilsTests.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Unit +{ + public class UtilsTests + { + private const string TestEnvVar = "AzureFunctionsSqlBindingsTestEnvVar"; + private const string TestConfigSetting = "AzureFunctionsSqlBindingsTestConfigSetting"; + + [Theory] + [InlineData(null, false)] // Doesn't exist, get default value + [InlineData(null, true, true)] // Doesn't exist, get default value (set explicitly) + [InlineData("1", true)] + [InlineData("true", true)] + [InlineData("TRUE", true)] + [InlineData("yes", true)] + [InlineData("YES", true)] + [InlineData("0", false)] + [InlineData("false", false)] + [InlineData("FALSE", false)] + [InlineData("no", false)] + [InlineData("NO", false)] + [InlineData("2", false)] + [InlineData("SomeOtherValue", false)] + public void GetEnvironmentVariableAsBool(string value, bool expectedValue, bool defaultValue = false) + { + Environment.SetEnvironmentVariable(TestEnvVar, value?.ToString()); + bool actualValue = Utils.GetEnvironmentVariableAsBool(TestEnvVar, defaultValue); + Assert.Equal(expectedValue, actualValue); + } + + [Theory] + [InlineData(null, false)] // Doesn't exist, get default value + [InlineData(null, true, true)] // Doesn't exist, get default value (set explicitly) + [InlineData("1", true)] + [InlineData("true", true)] + [InlineData("TRUE", true)] + [InlineData("yes", true)] + [InlineData("YES", true)] + [InlineData("0", false)] + [InlineData("false", false)] + [InlineData("FALSE", false)] + [InlineData("no", false)] + [InlineData("NO", false)] + [InlineData("2", false)] + [InlineData("SomeOtherValue", false)] + public void GetConfigSettingAsBool(string value, bool expectedValue, bool defaultValue = false) + { + var config = new TestConfiguration(); + IConfigurationSection configSection = new TestConfigurationSection(); + configSection.Value = value; + config.AddSection(TestConfigSetting, configSection); + bool actualValue = Utils.GetConfigSettingAsBool(TestConfigSetting, config, defaultValue); + Assert.Equal(expectedValue, actualValue); + } + } +}