diff --git a/sqltoolsservice.sln b/sqltoolsservice.sln
index 6dca984b..a6d67b4c 100644
--- a/sqltoolsservice.sln
+++ b/sqltoolsservice.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26510.0
+VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2BBD7364-054F-4693-97CD-1C395E3E84A9}"
EndProject
@@ -72,6 +72,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SqlTools.ServiceL
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SqlTools.Serialization", "src\Microsoft.SqlTools.Serialization\Microsoft.SqlTools.Serialization.csproj", "{75E1A89F-9DF6-4DA3-9EF1-5FD966331E06}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SqlTools.ResourceProvider", "src\Microsoft.SqlTools.ResourceProvider\Microsoft.SqlTools.ResourceProvider.csproj", "{6FEE7E14-8A1D-454E-8F7C-B63597801787}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SqlTools.ResourceProvider.Core", "src\Microsoft.SqlTools.ResourceProvider.Core\Microsoft.SqlTools.ResourceProvider.Core.csproj", "{70E63BC1-2C82-41C0-89D6-272FD3C7B0C9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SqlTools.ResourceProvider.DefaultImpl", "src\Microsoft.SqlTools.ResourceProvider.DefaultImpl\Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj", "{EFB39C03-F7D2-4E8D-BE51-09121CD71973}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -145,6 +151,24 @@ Global
{75E1A89F-9DF6-4DA3-9EF1-5FD966331E06}.Integration|Any CPU.Build.0 = Debug|Any CPU
{75E1A89F-9DF6-4DA3-9EF1-5FD966331E06}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75E1A89F-9DF6-4DA3-9EF1-5FD966331E06}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6FEE7E14-8A1D-454E-8F7C-B63597801787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6FEE7E14-8A1D-454E-8F7C-B63597801787}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6FEE7E14-8A1D-454E-8F7C-B63597801787}.Integration|Any CPU.ActiveCfg = Debug|Any CPU
+ {6FEE7E14-8A1D-454E-8F7C-B63597801787}.Integration|Any CPU.Build.0 = Debug|Any CPU
+ {6FEE7E14-8A1D-454E-8F7C-B63597801787}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6FEE7E14-8A1D-454E-8F7C-B63597801787}.Release|Any CPU.Build.0 = Release|Any CPU
+ {70E63BC1-2C82-41C0-89D6-272FD3C7B0C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {70E63BC1-2C82-41C0-89D6-272FD3C7B0C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {70E63BC1-2C82-41C0-89D6-272FD3C7B0C9}.Integration|Any CPU.ActiveCfg = Debug|Any CPU
+ {70E63BC1-2C82-41C0-89D6-272FD3C7B0C9}.Integration|Any CPU.Build.0 = Debug|Any CPU
+ {70E63BC1-2C82-41C0-89D6-272FD3C7B0C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {70E63BC1-2C82-41C0-89D6-272FD3C7B0C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EFB39C03-F7D2-4E8D-BE51-09121CD71973}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EFB39C03-F7D2-4E8D-BE51-09121CD71973}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EFB39C03-F7D2-4E8D-BE51-09121CD71973}.Integration|Any CPU.ActiveCfg = Debug|Any CPU
+ {EFB39C03-F7D2-4E8D-BE51-09121CD71973}.Integration|Any CPU.Build.0 = Debug|Any CPU
+ {EFB39C03-F7D2-4E8D-BE51-09121CD71973}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EFB39C03-F7D2-4E8D-BE51-09121CD71973}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -163,6 +187,9 @@ Global
{501DB3B2-AF92-41CF-82F6-780F9C37C219} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}
{2C290C58-C98D-46B2-BCED-44D9B67F6D31} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}
{75E1A89F-9DF6-4DA3-9EF1-5FD966331E06} = {2BBD7364-054F-4693-97CD-1C395E3E84A9}
+ {6FEE7E14-8A1D-454E-8F7C-B63597801787} = {2BBD7364-054F-4693-97CD-1C395E3E84A9}
+ {70E63BC1-2C82-41C0-89D6-272FD3C7B0C9} = {2BBD7364-054F-4693-97CD-1C395E3E84A9}
+ {EFB39C03-F7D2-4E8D-BE51-09121CD71973} = {2BBD7364-054F-4693-97CD-1C395E3E84A9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B31CDF4B-2851-45E5-8C5F-BE97125D9DD8}
diff --git a/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj b/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj
index 5b8ec632..961fecaa 100644
--- a/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj
+++ b/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj
@@ -26,13 +26,14 @@
-
+
-
+
+
diff --git a/src/Microsoft.SqlTools.Hosting/Extensibility/ExportStandardMetadataAttribute.cs b/src/Microsoft.SqlTools.Hosting/Extensibility/ExportStandardMetadataAttribute.cs
new file mode 100644
index 00000000..3c90a1a7
--- /dev/null
+++ b/src/Microsoft.SqlTools.Hosting/Extensibility/ExportStandardMetadataAttribute.cs
@@ -0,0 +1,44 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Composition;
+
+namespace Microsoft.SqlTools.Extensibility
+{
+ ///
+ /// Base attribute class for all export definitions.
+ ///
+ [MetadataAttribute]
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+ public abstract class ExportStandardMetadataAttribute : ExportAttribute, IStandardMetadata
+ {
+ ///
+ /// Base class for DAC extensibility exports
+ ///
+ protected ExportStandardMetadataAttribute(Type contractType, string id, string displayName = null)
+ : base(contractType)
+ {
+ Id = id;
+ DisplayName = displayName;
+ }
+
+
+ ///
+ /// The version of this extension
+ ///
+ public string Version { get; set; }
+
+ ///
+ /// The id of the extension
+ ///
+ public string Id { get; private set; }
+
+ ///
+ /// The display name for the extension
+ ///
+ public virtual string DisplayName { get; private set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.Hosting/Extensibility/IStandardMetadata.cs b/src/Microsoft.SqlTools.Hosting/Extensibility/IStandardMetadata.cs
new file mode 100644
index 00000000..a9e65748
--- /dev/null
+++ b/src/Microsoft.SqlTools.Hosting/Extensibility/IStandardMetadata.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+namespace Microsoft.SqlTools.Extensibility
+{
+ ///
+ /// Standard Metadata needed for extensions.
+ ///
+ public interface IStandardMetadata
+ {
+ ///
+ /// Extension version. Should be in the format "1.0.0.0" or similar
+ ///
+ string Version { get; }
+
+ ///
+ /// Unique Id used to identify the export.
+ ///
+ string Id { get; }
+
+ ///
+ /// Optional Display name describing the export type
+ ///
+ string DisplayName { get; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.Hosting/Microsoft.SqlTools.Hosting.csproj b/src/Microsoft.SqlTools.Hosting/Microsoft.SqlTools.Hosting.csproj
index ac92d32c..d0aecb44 100644
--- a/src/Microsoft.SqlTools.Hosting/Microsoft.SqlTools.Hosting.csproj
+++ b/src/Microsoft.SqlTools.Hosting/Microsoft.SqlTools.Hosting.csproj
@@ -1,6 +1,6 @@
- netcoreapp2.0
+ netstandard2.0falsefalsefalse
@@ -10,11 +10,9 @@
truetrueportable
- win7-x64;win7-x86;ubuntu.14.04-x64;ubuntu.16.04-x64;centos.7-x64;rhel.7.2-x64;debian.8-x64;fedora.23-x64;opensuse.13.2-x64;osx.10.11-x64
-
@@ -25,6 +23,7 @@
+
diff --git a/src/Microsoft.SqlTools.Hosting/Utility/AutoLock.cs b/src/Microsoft.SqlTools.Hosting/Utility/AutoLock.cs
new file mode 100644
index 00000000..06dff105
--- /dev/null
+++ b/src/Microsoft.SqlTools.Hosting/Utility/AutoLock.cs
@@ -0,0 +1,55 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading;
+
+namespace Microsoft.SqlTools.Utility
+{
+ ///
+ /// A wrapper around the ReaderWriterLock to make sure the locks are released even if the action fails
+ ///
+ public class AutoLock
+ {
+ private readonly ReaderWriterLock _lock;
+ private readonly bool _isWriteLocked;
+
+ ///
+ /// Creates new lock given type of lock and timeout
+ ///
+ public AutoLock(ReaderWriterLock lockObj, bool isWriteLock, TimeSpan timeOut, Action action, out Exception exception)
+ {
+ exception = null;
+ try
+ {
+ _lock = lockObj;
+ _isWriteLocked = isWriteLock;
+ if (_isWriteLocked)
+ {
+ _lock.AcquireWriterLock(timeOut);
+ }
+ else
+ {
+ _lock.AcquireReaderLock(timeOut);
+ }
+ action();
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ }
+ finally
+ {
+ if (_isWriteLocked && _lock.IsWriterLockHeld)
+ {
+ _lock.ReleaseWriterLock();
+ }
+ else if (!_isWriteLocked && _lock.IsReaderLockHeld)
+ {
+ _lock.ReleaseReaderLock();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs b/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs
index 7967b07c..bc56d254 100644
--- a/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs
+++ b/src/Microsoft.SqlTools.Hosting/Utility/CommandOptions.cs
@@ -50,7 +50,7 @@ namespace Microsoft.SqlTools.Hosting.Utility
ShouldExit = true;
return;
default:
- ErrorMessage += String.Format("Unknown argument \"{0}\"" + Environment.NewLine, argName);
+ ErrorMessage += string.Format("Unknown argument \"{0}\"" + Environment.NewLine, argName);
break;
}
}
diff --git a/src/Microsoft.SqlTools.Hosting/Utility/ConcurrentCache.cs b/src/Microsoft.SqlTools.Hosting/Utility/ConcurrentCache.cs
new file mode 100644
index 00000000..aace98f3
--- /dev/null
+++ b/src/Microsoft.SqlTools.Hosting/Utility/ConcurrentCache.cs
@@ -0,0 +1,82 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Microsoft.SqlTools.Utility
+{
+ public class ConcurrentCache
+ {
+ private readonly Dictionary _cache = new Dictionary();
+ private readonly ReaderWriterLock _readerWriterLock = new ReaderWriterLock();
+ private readonly TimeSpan _timeout = TimeSpan.FromHours(1);
+
+ public void ClearCache(IEnumerable keys)
+ {
+ Exception exception;
+ new AutoLock(_readerWriterLock, true, _timeout, () =>
+ {
+ {
+ foreach (var key in keys)
+ {
+ if (_cache.ContainsKey(key))
+ {
+ _cache.Remove(key);
+ }
+ }
+ }
+ }, out exception);
+ if (exception != null)
+ {
+ throw exception;
+ }
+ }
+
+ public T Get(string key)
+ {
+ T result = default(T);
+ Exception exception;
+ new AutoLock(_readerWriterLock, false, _timeout, () =>
+ {
+ if (_cache.ContainsKey(key))
+ {
+ result = _cache[key];
+ }
+ }, out exception);
+ if (exception != null)
+ {
+ throw exception;
+ }
+
+ return result;
+ }
+
+ public T UpdateCache(string key, T newValue)
+ {
+ T result = newValue;
+
+ Exception exception;
+ new AutoLock(_readerWriterLock, true, _timeout, () =>
+ {
+ bool isDefined = _cache.ContainsKey(key);
+ if (!isDefined)
+ {
+ _cache.Add(key, newValue);
+ }
+ else
+ {
+ result = _cache[key];
+ }
+ }, out exception);
+ if (exception != null)
+ {
+ throw exception;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.Hosting/packages.config b/src/Microsoft.SqlTools.Hosting/packages.config
index 77fd4961..0e62f811 100644
--- a/src/Microsoft.SqlTools.Hosting/packages.config
+++ b/src/Microsoft.SqlTools.Hosting/packages.config
@@ -1,7 +1,6 @@
-
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAccountManager.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAccountManager.cs
new file mode 100644
index 00000000..f35eb926
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAccountManager.cs
@@ -0,0 +1,57 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication
+{
+ ///
+ /// An account manager has the information of currently logged in user and can authenticate the user
+ /// Implementing classes must add a
+ /// to the class in order to be found by the extension manager,
+ /// and to define the type and category supported
+ ///
+ public interface IAccountManager : IExportable
+ {
+ ///
+ /// Returns true is user needs reauthentication
+ ///
+ Task GetUserNeedsReauthenticationAsync();
+
+ ///
+ /// Authenticates the user
+ ///
+ Task AuthenticateAsync();
+
+ ///
+ /// Prompt the login dialog to login to a new use that has not been cached
+ ///
+ Task AddUserAccountAsync();
+
+ ///
+ /// Set the current loaged in user in the cache
+ ///
+ Task SetCurrentAccountAsync(object account);
+
+ ///
+ /// Returns the current logged in user
+ ///
+ Task GetCurrentAccountAsync();
+
+ ///
+ /// Returns true if the API supports a login control
+ ///
+ bool HasLoginDialog
+ {
+ get;
+ }
+
+ ///
+ /// Event to raise when the current logged in user changed
+ ///
+ event EventHandler CurrentAccountChanged;
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureAuthenticationManager.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureAuthenticationManager.cs
new file mode 100644
index 00000000..ed18238b
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureAuthenticationManager.cs
@@ -0,0 +1,43 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication
+{
+ ///
+ /// Provides functionality to authenticate to Azure and discover associated accounts and subscriptions
+ ///
+ public interface IAzureAuthenticationManager : IAccountManager
+ {
+ ///
+ /// User accounts associated to the logged in user
+ ///
+ IEnumerable UserAccounts
+ {
+ get;
+ }
+
+ ///
+ /// Azure subscriptions associated to the logged in user
+ ///
+ Task> GetSubscriptionsAsync();
+
+ ///
+ /// Returns user's azure subscriptions
+ ///
+ Task> GetSelectedSubscriptionsAsync();
+
+ ///
+ /// Finds a subscription given subscription id
+ ///
+ bool TryParseSubscriptionIdentifier(string value, out IAzureSubscriptionIdentifier subscription);
+
+ ///
+ /// Stores the selected subscriptions given the ids
+ ///
+ Task SetSelectedSubscriptionsAsync(IEnumerable subscriptionIds);
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureSubscriptionContext.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureSubscriptionContext.cs
new file mode 100644
index 00000000..2384edcc
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureSubscriptionContext.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication
+{
+ /// Contains information about an Azure subscription
+ public interface IAzureSubscriptionContext : IEquatable
+ {
+ ///
+ /// Subscription Identifier
+ ///
+ IAzureSubscriptionIdentifier Subscription
+ {
+ get;
+ }
+
+ ///
+ /// Subscription name
+ ///
+ string SubscriptionName
+ {
+ get;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureSubscriptionIdentifier.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureSubscriptionIdentifier.cs
new file mode 100644
index 00000000..2fcb8056
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureSubscriptionIdentifier.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication
+{
+ ///
+ /// Contains information about an azure subscription identifier
+ ///
+ public interface IAzureSubscriptionIdentifier : IEquatable
+ {
+ IAzureUserAccount UserAccount
+ {
+ get;
+ }
+
+ ///
+ /// Service endpoint
+ ///
+ Uri ServiceManagementEndpoint
+ {
+ get;
+ }
+
+ ///
+ /// Subscription id
+ ///
+ string SubscriptionId
+ {
+ get;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccount.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccount.cs
new file mode 100644
index 00000000..c02e049d
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccount.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication
+{
+ ///
+ /// Contains information about an Azure user account
+ ///
+ public interface IAzureUserAccount : IEquatable, IUserAccount
+ {
+ ///
+ /// User Account Display Info
+ ///
+ IAzureUserAccountDisplayInfo DisplayInfo
+ {
+ get;
+ }
+
+ ///
+ /// Tenant Id
+ ///
+ string TenantId
+ {
+ get;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccountDisplayInfo.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccountDisplayInfo.cs
new file mode 100644
index 00000000..d6dfab5d
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccountDisplayInfo.cs
@@ -0,0 +1,54 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication
+{
+ ///
+ /// Contains information about an Azure user display
+ ///
+ public interface IAzureUserAccountDisplayInfo : IEquatable
+ {
+ ///
+ /// Account Display Name
+ ///
+ string AccountDisplayName
+ {
+ get;
+ }
+
+ ///
+ /// Account Logo
+ ///
+ byte[] AccountLogo
+ {
+ get;
+ }
+
+ ///
+ /// Provider Dislay Name
+ ///
+ string ProviderDisplayName
+ {
+ get;
+ }
+
+ ///
+ /// Provider Logo
+ ///
+ byte[] ProviderLogo
+ {
+ get;
+ }
+
+ ///
+ /// User Name
+ ///
+ string UserName
+ {
+ get;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccountSubscriptionContext.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccountSubscriptionContext.cs
new file mode 100644
index 00000000..588096a0
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccountSubscriptionContext.cs
@@ -0,0 +1,23 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication
+{
+ ///
+ /// Contains information about an Azure user account subscription
+ ///
+ public interface IAzureUserAccountSubscriptionContext :
+ IAzureSubscriptionContext, IEquatable
+ {
+ ///
+ /// User Account
+ ///
+ IAzureUserAccount UserAccount
+ {
+ get;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IUserAccount.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IUserAccount.cs
new file mode 100644
index 00000000..c1c5e497
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IUserAccount.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication
+{
+ ///
+ /// User account authentication information to be used by
+ ///
+ public interface IUserAccount
+ {
+ ///
+ /// The unique Id for the user
+ ///
+ string UniqueId
+ {
+ get;
+ }
+
+ ///
+ /// Returns true if user needs reauthentication
+ ///
+ bool NeedsReauthentication
+ {
+ get;
+ }
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Common/CommonUtil.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/CommonUtil.cs
new file mode 100644
index 00000000..f090c87b
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/CommonUtil.cs
@@ -0,0 +1,102 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+
+using System;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Utilities used by resource provider related code
+ ///
+ public static class CommonUtil
+ {
+ private const int KeyValueNameLength = 1024; // 1024 should be enough for registry key value name.
+
+ //********************************************************************************************
+ ///
+ /// Throw an exception if the object is null.
+ ///
+ /// the object to check
+ /// the variable or parameter name to display
+ //********************************************************************************************
+ public static void CheckForNull(Object var, string varName)
+ {
+ if (var == null)
+ {
+ throw new ArgumentNullException(varName);
+ }
+ }
+
+ //********************************************************************************************
+ ///
+ /// Throw an exception if a string is null or empty.
+ ///
+ /// string to check
+ /// the variable or parameter name to display
+ //********************************************************************************************
+ public static void CheckStringForNullOrEmpty(string stringVar, string stringVarName)
+ {
+ CheckStringForNullOrEmpty(stringVar, stringVarName, false);
+ }
+
+ //********************************************************************************************
+ ///
+ /// Throw an exception if a string is null or empty.
+ ///
+ /// string to check
+ /// the variable or parameter name to display
+ /// If true, will trim the string after it is determined not to be null
+ //********************************************************************************************
+ public static void CheckStringForNullOrEmpty(string stringVar, string stringVarName, bool trim)
+ {
+ CheckForNull(stringVar, stringVarName);
+ if (trim == true)
+ {
+ stringVar = stringVar.Trim();
+ }
+ if (stringVar.Length == 0)
+ {
+ throw new ArgumentException("EmptyStringNotAllowed", stringVarName);
+ }
+ }
+
+ public static bool SameString(string value1, string value2)
+ {
+ return (value1 == null && value2 == null) || (value2 != null && value2.Equals(value1));
+ }
+
+ public static bool SameUri(Uri value1, Uri value2)
+ {
+ return (value1 == null && value2 == null) || (value2 != null && value2.Equals(value1));
+ }
+
+ public static bool SameSubscriptionIdentifier(IAzureSubscriptionIdentifier value1,
+ IAzureSubscriptionIdentifier value2)
+ {
+ return (value1 == null && value2 == null) || (value2 != null && value2.Equals(value1));
+ }
+
+ public static bool SameUserAccount(IAzureUserAccount value1, IAzureUserAccount value2)
+ {
+ return (value1 == null && value2 == null) || (value2 != null && value2.Equals(value1));
+ }
+
+ public static string GetExceptionMessage(Exception e)
+ {
+ string message;
+
+#if DEBUG
+ string nl2 = Environment.NewLine + Environment.NewLine;
+ message = e.Message + nl2 + "DEBUG ONLY:" + nl2 + e.ToString();
+#else
+ message = e.Message;
+#endif
+
+ return message;
+ }
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ConnectionConstants.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ConnectionConstants.cs
new file mode 100644
index 00000000..478ae03f
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ConnectionConstants.cs
@@ -0,0 +1,20 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Telemetry constants for Connection Dialog
+ ///
+ internal static class ConnectionConstants
+ {
+ #region connection value constants
+ public static int SqlAzureEngineEditionId = 5;
+ ///
+ /// Constant value for master database name
+ ///
+ public const string MasterDatabaseName = "master";
+ #endregion
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ExceptionUtil.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ExceptionUtil.cs
new file mode 100644
index 00000000..2a5207df
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ExceptionUtil.cs
@@ -0,0 +1,69 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Data.Common;
+using System.Data.SqlClient;
+using System.Linq;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Extension methods and utils for exceptions
+ ///
+ internal static class ExceptionUtil
+ {
+ ///
+ /// Returns true if given exception if any of the inner exceptions is UserNeedsAuthenticationException
+ ///
+ internal static bool IsUserNeedsReauthenticateException(this Exception ex)
+ {
+ return ex.IsExceptionType(typeof(UserNeedsAuthenticationException));
+ }
+
+ ///
+ /// Returns true if given exception if any of the inner exceptions is sql exception
+ ///
+ internal static bool IsDbException(this Exception ex)
+ {
+ return ex.IsExceptionType(typeof (DbException));
+ }
+
+ ///
+ /// Returns true if given exception if any of the inner exceptions is same type of given type
+ ///
+ internal static bool IsExceptionType(this Exception ex, Type type)
+ {
+ if (ex == null)
+ {
+ return false;
+ }
+ if (ex is AggregateException)
+ {
+ var aggregateException = (AggregateException)ex;
+ return aggregateException.InnerExceptions != null &&
+ aggregateException.InnerExceptions.Any(inner => inner.IsExceptionType(type));
+ }
+ else if (type.IsAssignableFrom(ex.GetType()) || (ex.InnerException != null && ex.InnerException.IsExceptionType(type)))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ internal static string GetExceptionMessage(this Exception ex)
+ {
+ string errorMessage = string.Empty;
+ if (ex != null)
+ {
+ errorMessage = ex.Message;
+ if (ex.InnerException != null)
+ {
+ errorMessage += " " + ex.InnerException.Message;
+ }
+ }
+ return errorMessage;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ServiceExceptionBase.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ServiceExceptionBase.cs
new file mode 100644
index 00000000..7640c8b2
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ServiceExceptionBase.cs
@@ -0,0 +1,90 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Net;
+using System.Runtime.Serialization;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Base class for service exceptions
+ ///
+ public abstract class ServiceExceptionBase : Exception
+ {
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class.
+ ///
+ protected ServiceExceptionBase()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ protected ServiceExceptionBase(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The Http error code.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public ServiceExceptionBase(string message, HttpStatusCode httpStatusCode, Exception innerException = null)
+ : this(message, (int)httpStatusCode, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The Http error code.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public ServiceExceptionBase(string message, int httpStatusCode, Exception innerException)
+ : base(message, innerException)
+ {
+ HttpStatusCode = (HttpStatusCode)httpStatusCode;
+
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message
+ /// and a reference to the inner exception that is the cause of this exception.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ protected ServiceExceptionBase(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with serialized data.
+ ///
+ /// The SerializationInfo that holds the serialized object data about the exception being thrown.
+ /// The StreamingContext that contains contextual information about the source or destination.
+ protected ServiceExceptionBase(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+
+ ///
+ /// The Http status code included in the exception
+ ///
+ public HttpStatusCode HttpStatusCode
+ {
+ get; set;
+ }
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ServiceFailedException.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ServiceFailedException.cs
new file mode 100644
index 00000000..bbe98c7b
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/ServiceFailedException.cs
@@ -0,0 +1,67 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// The exception is used if any operation inside a service or provider fails
+ ///
+ [Serializable]
+ public class ServiceFailedException : ServiceExceptionBase
+ {
+ ///
+ /// Initializes a new instance of the ServiceFailedException class.
+ ///
+ public ServiceFailedException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the ServiceFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ public ServiceFailedException(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the ServiceFailedException class with a specified error message
+ /// and a reference to the inner exception that is the cause of this exception.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public ServiceFailedException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the ServiceFailedException class with serialized data.
+ ///
+ /// The SerializationInfo that holds the serialized object data about the exception being thrown.
+ /// The StreamingContext that contains contextual information about the source or destination.
+ public ServiceFailedException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+
+ ///
+ /// Creates a new instance of ServiceFailedException by adding the server definition info to the given message
+ ///
+ internal static ServiceFailedException CreateException(string message, ServerDefinition serverDefinition, Exception innerException)
+ {
+ return new ServiceFailedException(
+ string.Format(CultureInfo.CurrentCulture, message,
+ serverDefinition != null ? serverDefinition.ServerType : string.Empty,
+ serverDefinition != null ? serverDefinition.Category : string.Empty), innerException);
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Common/UserNeedsAuthenticationException.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/UserNeedsAuthenticationException.cs
new file mode 100644
index 00000000..a3049e73
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Common/UserNeedsAuthenticationException.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Runtime.Serialization;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// The exception is used if any operation fails becauase user needs to reauthenticate
+ ///
+ public class UserNeedsAuthenticationException : ServiceExceptionBase
+ {
+ ///
+ /// Initializes a new instance of the ServiceFailedException class.
+ ///
+ public UserNeedsAuthenticationException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the ServiceFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ public UserNeedsAuthenticationException(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the ServiceFailedException class with a specified error message
+ /// and a reference to the inner exception that is the cause of this exception.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public UserNeedsAuthenticationException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the ServiceFailedException class with serialized data.
+ ///
+ /// The SerializationInfo that holds the serialized object data about the exception being thrown.
+ /// The StreamingContext that contains contextual information about the source or destination.
+ public UserNeedsAuthenticationException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureDatabaseDiscoveryProvider.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureDatabaseDiscoveryProvider.cs
new file mode 100644
index 00000000..37ffb1ca
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureDatabaseDiscoveryProvider.cs
@@ -0,0 +1,328 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+using Microsoft.SqlTools.Utility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Default implementation for for Azure Sql databases.
+ /// A discovery provider capable of finding Sql Azure databases for a specific Azure user account.
+ ///
+
+ [Exportable(
+ ServerTypes.SqlServer,
+ Categories.Azure,
+ typeof(IDatabaseDiscoveryProvider),
+ "Microsoft.SqlServer.ConnectionServices.Azure.AzureDatabaseDiscoveryProvider")]
+ public class AzureDatabaseDiscoveryProvider : ExportableBase, IDatabaseDiscoveryProvider, ISecureService, ICacheable>
+ {
+ private IAzureResourceManager _azureResourceManagerWrapper;
+ private IAzureAuthenticationManager _azureAccountManager;
+ private IDatabaseDiscoveryProvider _defaultDatabaseDiscoveryProvider;
+ private readonly ConcurrentCache> _cache = new ConcurrentCache>();
+
+ public AzureDatabaseDiscoveryProvider()
+ {
+ // Duplicate the exportable attribute as at present we do not support filtering using extensiondescriptor.
+ // The attribute is preserved in order to simplify ability to backport into existing tools
+ Metadata = new ExportableMetadata(
+ ServerTypes.SqlServer,
+ Categories.Azure,
+ "Microsoft.SqlServer.ConnectionServices.Azure.AzureDatabaseDiscoveryProvider");
+ }
+
+ ///
+ /// the event to raise when a database is found
+ ///
+ public event EventHandler DatabaseFound;
+
+ ///
+ /// Updates the cache for current selected subscriptions
+ ///
+ /// The new cached data
+ public async Task> RefreshCacheAsync(CancellationToken cancellationToken)
+ {
+ ServiceResponse result = new ServiceResponse();
+
+ if (await ClearCacheAsync())
+ {
+ result = await GetDatabaseInstancesAsync(serverName: null, cancellationToken: cancellationToken);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Clears the cache for current selected subscriptions
+ ///
+ /// True if cache refreshed successfully. Otherwise returns false
+ public async Task ClearCacheAsync()
+ {
+ bool result = false;
+
+ if (AzureResourceManager != null && AccountManager != null && AzureAccountManager != null)
+ {
+ try
+ {
+ IEnumerable subscriptions = await GetSubscriptionsAsync();
+ _cache.ClearCache(subscriptions.Select(x => x.Subscription.SubscriptionId));
+ result = true;
+ }
+ catch (Exception ex)
+ {
+ TraceException(TraceEventType.Error, TraceId.AzureResource, ex, "Failed to refresh the cache");
+ result = false;
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Returns the databases for given connection info.
+ /// The connection info should be used to make the connection for getting databases not the account manager
+ ///
+ //public async Task> GetDatabaseInstancesAsync(UIConnectionInfo uiConnectionInfo, CancellationToken cancellationToken)
+ //{
+ // ServiceResponse result = null;
+ // if (DefaultDatabaseDiscoveryProvider != null && DefaultDatabaseDiscoveryProvider != this)
+ // {
+ // result = await DefaultDatabaseDiscoveryProvider.GetDatabaseInstancesAsync(uiConnectionInfo, cancellationToken);
+ // }
+ // else
+ // {
+ // result = new ServiceResponse(); //TODO: add error that we couldn't find any default database provider
+ // }
+ // return result;
+ //}
+
+ ///
+ /// Returns the databases for given server name. Using the account manager to get the databases
+ ///
+ public async Task> GetDatabaseInstancesAsync(string serverName, CancellationToken cancellationToken)
+ {
+ ServiceResponse result = null;
+ if (AzureResourceManager != null && AccountManager != null && AzureAccountManager != null)
+ {
+ try
+ {
+ //if connection is passed, we need to search all subscriptions not selected ones
+ IEnumerable subscriptions = await GetSubscriptionsAsync(string.IsNullOrEmpty(serverName));
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ result = await AzureUtil.ExecuteGetAzureResourceAsParallel(null, subscriptions, serverName, cancellationToken,
+ GetDatabaseForSubscriptionAsync);
+ }
+
+ }
+ catch (Exception ex)
+ {
+ result = new ServiceResponse(ex);
+ }
+ }
+
+ result = result ?? new ServiceResponse();
+ return result;
+ }
+
+ ///
+ /// Returns the resource manager that has same metadata as this class
+ ///
+ public IAzureResourceManager AzureResourceManager
+ {
+ get
+ {
+ return (_azureResourceManagerWrapper = _azureResourceManagerWrapper ?? GetService());
+ }
+ set
+ {
+ _azureResourceManagerWrapper = value;
+ }
+ }
+
+ ///
+ /// Returns the account manager that has same metadata as this class
+ ///
+ public IAzureAuthenticationManager AzureAccountManager
+ {
+ get
+ {
+ return (_azureAccountManager = _azureAccountManager ?? GetService());
+ }
+ }
+
+ ///
+ /// Returns the account manager that has same metadata as this class
+ ///
+ public IDatabaseDiscoveryProvider DefaultDatabaseDiscoveryProvider
+ {
+ get
+ {
+ return (_defaultDatabaseDiscoveryProvider = _defaultDatabaseDiscoveryProvider ?? GetService(null));
+ }
+ }
+
+ ///
+ /// Account Manager
+ ///
+ public IAccountManager AccountManager
+ {
+ get
+ {
+ return AzureAccountManager;
+
+ }
+ set
+ {
+ _azureAccountManager = value as IAzureAuthenticationManager;
+ }
+ }
+
+ ///
+ /// Returns azure subscriptions.
+ ///
+ private async Task> GetSubscriptionsAsync(bool selectedOnly = true)
+ {
+ try
+ {
+ return selectedOnly ? await AzureAccountManager.GetSelectedSubscriptionsAsync() : await AzureAccountManager.GetSubscriptionsAsync();
+ }
+ catch (Exception ex)
+ {
+ throw new ServiceFailedException(string.Format(CultureInfo.CurrentCulture, SR.AzureSubscriptionFailedErrorMessage, ex));
+ }
+ }
+
+ ///
+ /// There was a wired nullReferencedException was running the tasks parallel. It only got fixed when I put the getting from cache insed an async method
+ ///
+ private Task> GetFromCacheAsync(string key)
+ {
+ return Task.Factory.StartNew(() => _cache.Get(key));
+ }
+
+ ///
+ /// Returns a list of Azure sql databases for given subscription
+ ///
+ private async Task> GetDatabaseForSubscriptionAsync(IAzureResourceManagementSession parentSession,
+ IAzureUserAccountSubscriptionContext input, string serverName,
+ CancellationToken cancellationToken, CancellationToken internalCancellationToken)
+ {
+ ServiceResponse result = null;
+ bool shouldFilter = !string.IsNullOrEmpty(serverName);
+ try
+ {
+ string key = input.Subscription.SubscriptionId;
+
+ //when the data was coming from cache and no async mthod was called the parallel tasks running crashed so I had to call this line async to fix it
+ result = await GetFromCacheAsync(key);
+ if (result == null)
+ {
+ //this will only get the databases for the given server name
+ result = await GetDatabaseForSubscriptionFromServiceAsync(input, serverName, cancellationToken, internalCancellationToken);
+ }
+ else if (shouldFilter)
+ {
+ //we should filter the result because the cached data includes databases for all servers
+ result = new ServiceResponse(result.Data.Where(x => x.ServerInstanceInfo.FullyQualifiedDomainName == serverName),
+ result.Errors);
+ }
+
+ //only update the cache if server name is not passes so the result is not filtered. The cache data supposed to be the data for all server
+ if (!shouldFilter && !cancellationToken.IsCancellationRequested)
+ {
+ result = _cache.UpdateCache(key, result);
+ }
+ }
+ catch (Exception ex)
+ {
+ result = new ServiceResponse(ex);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Returns a list of Azure sql databases for given subscription
+ ///
+ private async Task> GetDatabaseForSubscriptionFromServiceAsync(
+ IAzureUserAccountSubscriptionContext input, string serverName,
+ CancellationToken cancellationToken, CancellationToken internalCancellationToken)
+ {
+ ServiceResponse result = null;
+
+ try
+ {
+ if (!cancellationToken.IsCancellationRequested && !internalCancellationToken.IsCancellationRequested)
+ {
+ using (IAzureResourceManagementSession session = await AzureResourceManager.CreateSessionAsync(input))
+ {
+ //find the server matches with the given servername which should be only one
+ bool shouldFilter = !string.IsNullOrEmpty(serverName);
+ IEnumerable sqlAzureServers = await AzureResourceManager.GetSqlServerAzureResourcesAsync(session);
+ IEnumerable filteredServers = !shouldFilter ? sqlAzureServers : sqlAzureServers.Where(x =>
+ x.FullyQualifiedDomainName != null &&
+ x.FullyQualifiedDomainName.Equals(serverName,
+ StringComparison.OrdinalIgnoreCase));
+
+ IList filteredServersList = filteredServers.ToList();
+ result = await GetDatabasesForSubscriptionServersAsync(session, filteredServersList.ToList(), cancellationToken);
+
+ //Set response Found to true to notify the other tasks to cancel
+ if (shouldFilter && filteredServersList.Any())
+ {
+ result.Found = true;
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ result = new ServiceResponse(ex);
+ }
+
+ return result ?? new ServiceResponse();
+ }
+
+ private async Task> GetDatabasesForSubscriptionServersAsync(IAzureResourceManagementSession session,
+ IList filteredServersList, CancellationToken cancellationToken)
+ {
+ ServiceResponse result = null;
+ AzureServerDatabaseDiscoveryProvider azureServerDatabaseDiscoveryProvider = new AzureServerDatabaseDiscoveryProvider(AzureResourceManager, session, ServerDefinition);
+ azureServerDatabaseDiscoveryProvider.DatabaseFound += AzureServerDatabaseDiscoveryProviderOnDatabaseFound;
+ if (filteredServersList.Any())
+ {
+ result = await azureServerDatabaseDiscoveryProvider.GetDatabasesForServers(filteredServersList, cancellationToken);
+ }
+
+ return result ?? new ServiceResponse();
+ }
+
+ private void AzureServerDatabaseDiscoveryProviderOnDatabaseFound(object sender, DatabaseInfoEventArgs databaseInfoEventArgs)
+ {
+ OnDatabaseFound(databaseInfoEventArgs.Database);
+ }
+
+ ///
+ /// Raises DatabaseFound event with the given databases info
+ ///
+ private void OnDatabaseFound(DatabaseInstanceInfo databaseInfo)
+ {
+ if (DatabaseFound != null)
+ {
+ DatabaseFound(this, new DatabaseInfoEventArgs() { Database = databaseInfo });
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureServerDatabaseDiscoveryProvider.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureServerDatabaseDiscoveryProvider.cs
new file mode 100644
index 00000000..fac48b6a
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureServerDatabaseDiscoveryProvider.cs
@@ -0,0 +1,123 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ internal class AzureServerDatabaseDiscoveryProvider
+ {
+ private readonly IAzureResourceManagementSession _session;
+ ///
+ /// the event to raise when a database is found
+ ///
+ public event EventHandler DatabaseFound;
+
+ public AzureServerDatabaseDiscoveryProvider(IAzureResourceManager azureResourceManager, IAzureResourceManagementSession session, ServerDefinition serverDefinition)
+ {
+ CommonUtil.CheckForNull(session, "session");
+ CommonUtil.CheckForNull(azureResourceManager, "azureResourceManager");
+ _session = session;
+ AzureResourceManager = azureResourceManager;
+
+ ServerDefinition = serverDefinition ?? ServerDefinition.Default;
+ }
+
+ ///
+ /// Returns the resource manager that has same metadata as this class
+ ///
+ internal IAzureResourceManager AzureResourceManager { get; set; }
+
+ private ServerDefinition ServerDefinition { get; set; }
+
+ public async Task> GetDatabasesForServers(IList serverResources, CancellationToken cancellationToken)
+ {
+ ServiceResponse < DatabaseInstanceInfo > result = new ServiceResponse();
+ if (serverResources != null)
+ {
+ result = await AzureUtil.ExecuteGetAzureResourceAsParallel(_session, serverResources, null, cancellationToken, GetDatabasesForServerFromService);
+ }
+ return result;
+ }
+
+ private async Task> GetDatabasesForServerFromService(
+ IAzureResourceManagementSession session,
+ IAzureSqlServerResource azureSqlServer,
+ string serverName,
+ CancellationToken cancellationToken,
+ CancellationToken internalCancellationToken)
+ {
+ try
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ServiceResponse();
+ }
+ ServerInstanceInfo serverInstanceInfo = new ServerInstanceInfo(ServerDefinition)
+ {
+ Name = azureSqlServer.Name,
+ FullyQualifiedDomainName = azureSqlServer.FullyQualifiedDomainName,
+ AdministratorLogin = azureSqlServer.AdministratorLogin
+ };
+ OnDatabaseFound(new DatabaseInstanceInfo(serverInstanceInfo));
+ IEnumerable databases = await AzureResourceManager.GetAzureDatabasesAsync(
+ session,
+ azureSqlServer.ResourceGroupName,
+ azureSqlServer.Name);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return new ServiceResponse();
+ }
+ else
+ {
+ IEnumerable data = databases.Select(x => ConvertToModel(serverInstanceInfo, x));
+ ServiceResponse result = new ServiceResponse(data);
+ foreach (var databaseInstance in result.Data)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ break;
+ }
+ OnDatabaseFound(databaseInstance);
+ }
+
+ return result;
+ }
+ }
+ catch (Exception ex)
+ {
+ return new ServiceResponse(ex);
+ }
+ }
+
+ ///
+ /// Raises DatabaseFound event with the given databases info
+ ///
+ private void OnDatabaseFound(DatabaseInstanceInfo databaseInfo)
+ {
+ if (DatabaseFound != null)
+ {
+ DatabaseFound(this, new DatabaseInfoEventArgs() { Database = databaseInfo });
+ }
+ }
+
+ ///
+ /// Converts the resource to DatabaseInstanceInfo
+ ///
+ private DatabaseInstanceInfo ConvertToModel(ServerInstanceInfo serverInstanceInfo, IAzureResource azureResource)
+ {
+ DatabaseInstanceInfo databaseInstance = new DatabaseInstanceInfo(serverInstanceInfo)
+ {
+ Name = azureResource.Name.Replace(serverInstanceInfo.Name + "/", "")
+ };
+
+ return databaseInstance;
+ }
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureSqlServerDiscoveryProvider.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureSqlServerDiscoveryProvider.cs
new file mode 100644
index 00000000..79c9ae51
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureSqlServerDiscoveryProvider.cs
@@ -0,0 +1,125 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Default implementation for for Azure Sql servers.
+ /// A discovery provider capable of finding Sql Azure servers for a specific Azure user account.
+ ///
+ [Exportable(
+ ServerTypes.SqlServer,
+ Categories.Azure,
+ typeof(IServerDiscoveryProvider),
+ "Microsoft.SqlServer.ConnectionServices.Azure.AzureServerDiscoveryProvider")]
+ internal class AzureSqlServerDiscoveryProvider : ExportableBase, IServerDiscoveryProvider, ISecureService
+ {
+ private IAzureResourceManager _azureResourceManagerWrapper;
+ private IAzureAuthenticationManager _azureAccountManager;
+
+ public AzureSqlServerDiscoveryProvider()
+ {
+ // Duplicate the exportable attribute as at present we do not support filtering using extensiondescriptor.
+ // The attribute is preserved in order to simplify ability to backport into existing tools
+ Metadata = new ExportableMetadata(
+ ServerTypes.SqlServer,
+ Categories.Azure,
+ "Microsoft.SqlServer.ConnectionServices.Azure.AzureServerDiscoveryProvider");
+ }
+
+ public async Task> GetServerInstancesAsync()
+ {
+ ServiceResponse result = new ServiceResponse();
+ List serverInstances = new List();
+
+ if (AccountManager != null && AzureAccountManager != null && AzureResourceManager != null)
+ {
+ try
+ {
+ IEnumerable subscriptions =
+ await AzureAccountManager.GetSelectedSubscriptionsAsync();
+ if (subscriptions != null)
+ {
+ foreach (IAzureUserAccountSubscriptionContext subscription in subscriptions)
+ {
+ using (IAzureResourceManagementSession session = await AzureResourceManager.CreateSessionAsync(subscription))
+ {
+ IEnumerable azureResources =
+ await AzureResourceManager.GetSqlServerAzureResourcesAsync(session);
+ serverInstances.AddRange(
+ azureResources.Select(x =>
+ new ServerInstanceInfo(ServerDefinition)
+ {
+ Name = x.Name,
+ FullyQualifiedDomainName = x.FullyQualifiedDomainName,
+ AdministratorLogin = x.AdministratorLogin
+ }));
+ }
+ }
+ }
+ result = new ServiceResponse(serverInstances);
+ }
+ catch (Exception ex)
+ {
+ result = new ServiceResponse(serverInstances, new List() {ex});
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Returns the resource manager that has same metadata as this class
+ ///
+ public IAzureResourceManager AzureResourceManager
+ {
+ get
+ {
+ return (_azureResourceManagerWrapper =
+ _azureResourceManagerWrapper ??
+ GetService());
+ }
+ internal set
+ {
+ _azureResourceManagerWrapper = value;
+ }
+ }
+
+ ///
+ /// Returns the account manager that has same metadata as this class
+ ///
+ public IAzureAuthenticationManager AzureAccountManager
+ {
+ get
+ {
+ return (_azureAccountManager =
+ _azureAccountManager ??
+ GetService());
+ }
+ }
+
+ ///
+ /// Account Manager
+ ///
+ public IAccountManager AccountManager
+ {
+ get
+ {
+ return AzureAccountManager;
+
+ }
+ internal set
+ {
+ _azureAccountManager = value as IAzureAuthenticationManager;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureUtil.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureUtil.cs
new file mode 100644
index 00000000..abde06a0
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/AzureUtil.cs
@@ -0,0 +1,113 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ internal static class AzureUtil
+ {
+ ///
+ /// Execute an async action for each input in the a list of input in parallel.
+ /// If any task fails, adds the exeption message to the response errors
+ /// If cancellation token is set to cancel, returns empty response
+ ///
+ /// Resource management session to use to call the resource manager
+ /// List of inputs
+ /// server name to filter the result
+ /// Cancellation token
+ /// Async action
+ /// ServiceResponse including the list of data and errors
+ public static async Task> ExecuteGetAzureResourceAsParallel(
+ IAzureResourceManagementSession session,
+ IEnumerable inputs,
+ string serverName,
+ CancellationToken cancellationToken,
+ Func>> asyncAction
+ )
+ {
+ List mergedResult = new List();
+ List mergedErrors = new List();
+ try
+ {
+ if (inputs == null)
+ {
+ return new ServiceResponse(mergedResult);
+ }
+ List inputList = inputs.ToList();
+
+ ServiceResponse[] resultList = new ServiceResponse[inputList.Count];
+ CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
+ var tasks = Enumerable.Range(0, inputList.Count())
+ .Select(async i =>
+ {
+ ServiceResponse result = await GetResult(session, inputList[i], serverName, cancellationToken,
+ cancellationTokenSource.Token, asyncAction);
+ //server name is used to filter the result and if the data is already found not, we need to cancel the other tasks
+ if (!string.IsNullOrEmpty(serverName) && result.Found)
+ {
+ cancellationTokenSource.Cancel();
+ }
+ resultList[i] = result;
+ return result;
+ }
+ );
+
+ await Task.WhenAll(tasks);
+
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ foreach (ServiceResponse resultForEachInput in resultList)
+ {
+ mergedResult.AddRange(resultForEachInput.Data);
+ mergedErrors.AddRange(resultForEachInput.Errors);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ mergedErrors.Add(ex);
+ return new ServiceResponse(mergedResult, mergedErrors);
+ }
+ return new ServiceResponse(mergedResult, mergedErrors);
+ }
+
+ private static async Task> GetResult(
+ IAzureResourceManagementSession session,
+ TInput input,
+ string serverName,
+ CancellationToken cancellationToken,
+ CancellationToken internalCancellationToken,
+ Func>> asyncAction
+ )
+ {
+ if (cancellationToken.IsCancellationRequested || internalCancellationToken.IsCancellationRequested)
+ {
+ return new ServiceResponse();
+ }
+ try
+ {
+ return await asyncAction(session, input, serverName, cancellationToken, internalCancellationToken);
+ }
+ catch (Exception ex)
+ {
+ return new ServiceResponse(ex);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/DatabaseInfoEventArgs.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/DatabaseInfoEventArgs.cs
new file mode 100644
index 00000000..fb3cf1b0
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/DatabaseInfoEventArgs.cs
@@ -0,0 +1,22 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Event arguments to use for database info events
+ ///
+ public class DatabaseInfoEventArgs : EventArgs
+ {
+ ///
+ /// Database Info
+ ///
+ public DatabaseInstanceInfo Database
+ {
+ get; set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/DatabaseInstanceInfo.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/DatabaseInstanceInfo.cs
new file mode 100644
index 00000000..43e2e1e7
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/DatabaseInstanceInfo.cs
@@ -0,0 +1,62 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Includes the data for a discovered database
+ ///
+ public class DatabaseInstanceInfo
+ {
+ ///
+ /// Default constructor to initialize the instance
+ ///
+ ///
+ public DatabaseInstanceInfo(ServerInstanceInfo serverInstanceInfo)
+ {
+ ServerInstanceInfo = serverInstanceInfo;
+ }
+
+ public DatabaseInstanceInfo(ServerDefinition serverDefinition, string serverName, string databaseName)
+ {
+ ServerInstanceInfo = new ServerInstanceInfo(serverDefinition)
+ {
+ Name = serverName
+ };
+ Name = databaseName;
+ }
+
+ ///
+ /// Server instance info associated to the database instance
+ ///
+ public ServerInstanceInfo ServerInstanceInfo
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Database Name
+ ///
+ public string Name
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Returns true if the database is the master database
+ ///
+ public bool IsMaster
+ {
+ get { return ConnectionConstants.MasterDatabaseName.Equals(Name, StringComparison.OrdinalIgnoreCase); }
+ }
+
+ public bool IsDefaultDatabase { get; set; }
+
+ public bool IsSystemDatabase { get; set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResource.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResource.cs
new file mode 100644
index 00000000..84a3c1c2
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResource.cs
@@ -0,0 +1,41 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Interface for any implementation of azure resource
+ ///
+ public interface IAzureResource
+ {
+ ///
+ /// Azure Resource Name
+ ///
+ string Name { get; set; }
+
+ ///
+ /// Azure Resource Type
+ ///
+ string Type { get; set; }
+
+ ///
+ /// Azure Resource Id
+ ///
+ string Id { get; set; }
+
+ ///
+ /// Resource Group Name
+ ///
+ string ResourceGroupName
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Resource Location
+ ///
+ string Location { get; set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResourceManagementSession.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResourceManagementSession.cs
new file mode 100644
index 00000000..529b8991
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResourceManagementSession.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// A session used by . Includes all the clients that the resource management needs to get ther resources
+ ///
+ public interface IAzureResourceManagementSession : IDisposable
+ {
+ ///
+ /// Closes the session
+ ///
+ ///
+ bool CloseSession();
+
+ ///
+ /// Teh subscription for the current session
+ ///
+ IAzureUserAccountSubscriptionContext SubscriptionContext
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResourceManager.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResourceManager.cs
new file mode 100644
index 00000000..507825fb
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResourceManager.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+using Microsoft.SqlTools.ResourceProvider.Core.FirewallRule;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Provides functionality to get azure resources by making Http request to the Azure REST API
+ ///
+ public interface IAzureResourceManager : IExportable
+ {
+ ///
+ /// Returns a list of azure databases given subscription resource group name and server name
+ ///
+ /// Subscription Context which includes credentials to use in the resource manager
+ /// Resource Group Name
+ /// Server name
+ /// The list of databases
+ Task> GetAzureDatabasesAsync(
+ IAzureResourceManagementSession azureResourceManagementSession,
+ string resourceGroupName,
+ string serverName);
+
+ ///
+ /// Returns a list of azure servers given subscription
+ ///
+ /// Subscription Context which includes credentials to use in the resource manager
+ /// The list of Sql server resources
+ Task> GetSqlServerAzureResourcesAsync(
+ IAzureResourceManagementSession azureResourceManagementSession);
+
+ ///
+ /// Create new firewall rule given user subscription, Sql server resource and the firewall rule request
+ ///
+ /// Subscription Context which includes credentials to use in the resource manager
+ /// Sql server resource to create firewall rule for
+ /// Firewall rule request including the name and IP address range
+ ///
+ Task CreateFirewallRuleAsync(
+ IAzureResourceManagementSession azureResourceManagementSession,
+ IAzureSqlServerResource azureSqlServer,
+ FirewallRuleRequest firewallRuleRequest
+ );
+
+ Task CreateSessionAsync(IAzureUserAccountSubscriptionContext subscriptionContext);
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureSqlServerResource.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureSqlServerResource.cs
new file mode 100644
index 00000000..0c5e192e
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureSqlServerResource.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// An Azure Sql server resource
+ ///
+ public interface IAzureSqlServerResource : IAzureResource
+ {
+ ///
+ /// Fully qualified domain name
+ ///
+ string FullyQualifiedDomainName
+ {
+ get;
+ }
+
+ ///
+ /// Administrator Login
+ ///
+ string AdministratorLogin
+ {
+ get;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ICacheable.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ICacheable.cs
new file mode 100644
index 00000000..3cc2869b
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ICacheable.cs
@@ -0,0 +1,27 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Defines a class as cachable
+ ///
+ public interface ICacheable
+ {
+ ///
+ /// Clears the cache for current user
+ ///
+ /// True if cache refreshed successfully. Otherwise returns false
+ Task ClearCacheAsync();
+
+ ///
+ /// Updates the cache for current selected subscriptions
+ ///
+ /// The new cached data
+ Task RefreshCacheAsync(CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IDatabaseDiscoveryProvider.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IDatabaseDiscoveryProvider.cs
new file mode 100644
index 00000000..6d8df6b6
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IDatabaseDiscoveryProvider.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// A discovery provider capable of finding databases for a given server type and category.
+ /// For example: finding SQL Server databases in Azure, or on the local network.
+ /// Implementing classes must add a
+ /// to the class in order to be found by the extension manager,
+ /// and to define the type and category supported
+ ///
+ public interface IDatabaseDiscoveryProvider : IExportable
+ {
+ ///
+ /// Returns the databases for given server name.
+ ///
+ Task> GetDatabaseInstancesAsync(string serverName, CancellationToken cancellationToken);
+
+ ///
+ /// Returns the databases for given connection info.
+ /// The connection info should be used to make the connection for getting databases not the account manager
+ ///
+ //Task> GetDatabaseInstancesAsync(UIConnectionInfo uiConnectionInfo, CancellationToken cancellationToken);
+
+ ///
+ /// the event to raise when a database is found
+ ///
+ event EventHandler DatabaseFound;
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ISecureService.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ISecureService.cs
new file mode 100644
index 00000000..4e2df4fd
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ISecureService.cs
@@ -0,0 +1,22 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Defines a class as secure which requires an account to function
+ ///
+ public interface ISecureService
+ {
+ ///
+ /// Gets the account manager instance
+ ///
+ IAccountManager AccountManager
+ {
+ get;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IServerDiscoveryProvider.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IServerDiscoveryProvider.cs
new file mode 100644
index 00000000..db0c5712
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IServerDiscoveryProvider.cs
@@ -0,0 +1,25 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// A discovery provider capable of finding servers for a given server type and category.
+ /// For example: finding SQL Servers in Azure, or on the local network.
+ /// Implementing classes must add a
+ /// to the class in order to be found by the extension manager,
+ /// and to define the type and category supported
+ ///
+ public interface IServerDiscoveryProvider : IExportable
+ {
+ ///
+ /// Discovers the server instances
+ ///
+ Task> GetServerInstancesAsync();
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServerDefinition.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServerDefinition.cs
new file mode 100644
index 00000000..7d7defe7
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServerDefinition.cs
@@ -0,0 +1,64 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Defines a server grouping based on the type of server connection supported (SQL Server, Reporting Server, Analysis Server)
+ /// and a Category by which these should be shown to the user.
+ /// Built in examples of categories include Local, Network, and Azure and additional categories can be defined as needed.
+ /// Note that the Connection Dialog UI may require Category to be set for some resource types such as.
+ /// In addition a UI section matching that category may be required, or else the provider will not be used by any UI part and never be called.
+ ///
+ public interface IServerDefinition
+ {
+ ///
+ /// Category by which resources can be grouped. Built in examples of categories include Local, Network, and Azure and additional categories can be defined as needed.
+ ///
+ string Category
+ {
+ get;
+ }
+
+ ///
+ /// The type of server connection supported. Examples include SQL Server, Reporting Server, Analysis Server.
+ ///
+ string ServerType
+ {
+ get;
+ }
+ }
+
+ ///
+ /// The implementation of the server definition that implements the properties
+ ///
+ public sealed class ServerDefinition : IServerDefinition
+ {
+ private static ServerDefinition _default = new ServerDefinition(ServerTypes.SqlServer, string.Empty);
+
+ public ServerDefinition(string serverType, string category)
+ {
+ ServerType = serverType;
+ Category = category;
+ }
+
+ ///
+ ///
+ ///
+ public string ServerType { private set; get; }
+
+ ///
+ ///
+ ///
+ public string Category { private set; get; }
+
+ ///
+ /// Default value for the server definition
+ ///
+ public static ServerDefinition Default
+ {
+ get { return _default; }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServerInstanceInfo.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServerInstanceInfo.cs
new file mode 100644
index 00000000..5f66ae91
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServerInstanceInfo.cs
@@ -0,0 +1,56 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Contains information about a server discovered by
+ ///
+ public class ServerInstanceInfo
+ {
+ ///
+ /// Initializes the new instance with server and location
+ ///
+ public ServerInstanceInfo(IServerDefinition serverDefinition)
+ {
+ ServerDefinition = serverDefinition;
+ }
+
+ public ServerInstanceInfo()
+ {
+ }
+
+ public IServerDefinition ServerDefinition
+ {
+ get; private set;
+ }
+
+ ///
+ /// Server Name
+ ///
+ public string Name
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Fully qualified domain name
+ ///
+ public string FullyQualifiedDomainName
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Administrator Login
+ ///
+ public string AdministratorLogin
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServerTypes.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServerTypes.cs
new file mode 100644
index 00000000..cbdc626a
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServerTypes.cs
@@ -0,0 +1,66 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// List of built-in server types used in .
+ /// Defines a server grouping based on the type of server connection supported (SQL Server, Reporting Server, Analysis Server)
+ /// Additional server types can be defined as needed.
+ /// Note that the Connection Dialog UI may require server type to be set for some resource types such as.
+ /// In addition a UI section matching that category may be required, or else the provider will not be used by any UI part and never be called.
+ ///
+ public static class ServerTypes
+ {
+ ///
+ /// Sql server
+ ///
+ public const string SqlServer = "SqlServer";
+
+ ///
+ /// Reporting server
+ ///
+ public const string SqlReportingServer = "SqlReportingServer";
+
+ ///
+ /// Integration server
+ ///
+ public const string SqlIntegrationServer = "SqlIntegrationServer";
+
+ ///
+ /// Analysis server
+ ///
+ public const string SqlAnalysisServer = "SqlAnalysisServer";
+ }
+
+ ///
+ /// List of built-in categories used in
+ /// Defines a server grouping based on the category of server connection supported (Network, Local, Azure)
+ /// Additional categories can be defined as needed.
+ /// Note that the Connection Dialog UI may require Category to be set for some resource types such as.
+ /// In addition a UI section matching that category may be required, or else the provider will not be used by any UI part and never be called.
+ ///
+ public static class Categories
+ {
+ ///
+ /// Network category
+ ///
+ public const string Network = "network";
+
+ ///
+ /// Azure category
+ ///
+ public const string Azure = "azure";
+
+ ///
+ /// local category
+ ///
+ public const string Local = "local";
+
+ ///
+ /// local db category
+ ///
+ public const string LocalDb = "localdb";
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServiceResponse.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServiceResponse.cs
new file mode 100644
index 00000000..15619311
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/ServiceResponse.cs
@@ -0,0 +1,93 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ ///
+ /// Contains the data that a service wants to returns plus the errors happened during getting some of the data
+ ///
+ ///
+ public class ServiceResponse
+ {
+ ///
+ /// Creates new instance given data and errors
+ ///
+ public ServiceResponse(IEnumerable data, IEnumerable errors)
+ {
+ Data = data;
+ Errors = errors;
+ }
+
+ ///
+ /// Creates new instance given errors
+ ///
+ public ServiceResponse(IEnumerable errors) : this(Enumerable.Empty(), errors)
+ {
+ }
+
+ ///
+ /// Creates new instance given data
+ ///
+ public ServiceResponse(IEnumerable data) : this(data, Enumerable.Empty())
+ {
+ }
+
+ ///
+ /// Creating new empry instance
+ ///
+ public ServiceResponse() : this(Enumerable.Empty(), Enumerable.Empty())
+ {
+ }
+
+ ///
+ /// Creates new instance given exception to create the error list
+ ///
+ public ServiceResponse(Exception ex) : this(Enumerable.Empty(), new List {ex})
+ {
+ }
+
+ ///
+ /// Information a service wants to returns
+ ///
+ public IEnumerable Data { get; private set; }
+
+ ///
+ /// The errors that heppend during retrieving data
+ ///
+ public IEnumerable Errors { get; private set; }
+
+ ///
+ /// Return true if the response includes errors
+ ///
+ public bool HasError
+ {
+ get { return Errors != null && Errors.Any(); }
+ }
+
+ ///
+ /// Concatenates the messages into one error message
+ ///
+ public string ErrorMessage
+ {
+ get
+ {
+ string message = string.Empty;
+ if (HasError)
+ {
+ message = string.Join(Environment.NewLine, Errors.Select(x => x.GetExceptionMessage()));
+ }
+ return message;
+ }
+ }
+
+ ///
+ /// Returns true if a response already found. it's used when we need to filter the responses
+ ///
+ public bool Found { get; set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExportableAttribute.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExportableAttribute.cs
new file mode 100644
index 00000000..e2bf82d5
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExportableAttribute.cs
@@ -0,0 +1,112 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Composition;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Extensibility
+{
+ ///
+ /// Attribute defining a service export, and the metadata about that service. Implements IServiceMetadata,
+ /// which should be used on the importer side to ensure type consistency. Services and providers have to add this property
+ /// in order to be found by the extension manager
+ ///
+ [MetadataAttribute]
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+ public class ExportableAttribute : ExportAttribute, IExportableMetadata
+ {
+ ///
+ /// Default constructor to initialize the metadata of the exportable
+ ///
+ /// The server type supported by the exportable. If not set, exportable supports all server types
+ /// The category supported by the exportable. If not set, exportable supports all categories
+ /// The type of the exportable to be used by the extension manager to find the exportable
+ /// The unique id of the exportable. Used by the extension manager to pick only one from exportables with same id in the same assembly
+ /// The priority of the exportable. The extension manager will pick the exportable with the highest priority if multiple found
+ /// The display name of the exportable. This field is optional
+ public ExportableAttribute(
+ string serverType,
+ string category,
+ Type type,
+ string id,
+ int priority = 0,
+ string displayName = null) : base(type)
+ {
+ Category = category;
+ ServerType = serverType;
+ Id = id;
+ DisplayName = displayName;
+ Priority = priority;
+ }
+
+ ///
+ /// The constructor to define an exportable by type, id and priority only. To be used by the exportables that support all server types and categories.
+ /// For example: the implementation of can be used for all server types and categories.
+ ///
+ /// The type of the exportable to be used by the extension manager to find the exportable
+ /// The unique id of the exportable. Used by the extension manager to pick only one from exportables with same id in the same assembly
+ /// The priority of the exportable. The extension manager will pick the exportable with the highest priority if multiple found
+ /// The display name of the exportable. This field is optional
+ public ExportableAttribute(Type type, string id, int priority = 0,
+ string displayName = null) :
+ this(String.Empty, String.Empty, type, id, priority, displayName)
+ {
+ }
+
+ ///
+ /// Thye category of the service
+ ///
+ public string Category
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The server type of that the service supports
+ ///
+ public string ServerType
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The version of this extension
+ ///
+ public string Version
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// The id of the extension
+ ///
+ public string Id
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The display name for the extension
+ ///
+ public string DisplayName
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// priority of the extension. Can be used to filter the extensions if multiple found
+ ///
+ public int Priority
+ {
+ get;
+ private set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExportableBase.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExportableBase.cs
new file mode 100644
index 00000000..253e45cb
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExportableBase.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Linq;
+using Microsoft.SqlTools.Extensibility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Extensibility
+{
+ ///
+ /// The base class for all exportable classes.
+ ///
+ public abstract class ExportableBase : TraceableBase, IExportable, IComposableService
+ {
+ private ITrace _trace;
+ private ExportableStatus _exportableStatus = new ExportableStatus();
+
+ public void SetServiceProvider(IMultiServiceProvider provider)
+ {
+ ServiceProvider = provider;
+ }
+
+ ///
+ /// The exportable metadata
+ ///
+ public IExportableMetadata Metadata
+ {
+ get;
+ set;
+ }
+
+
+ ///
+ /// Gets or sets the dependency manager to provider the dependencies of the class
+ ///
+ public IMultiServiceProvider ServiceProvider
+ {
+ get;
+ private set;
+ }
+
+ public virtual ExportableStatus Status
+ {
+ get { return _exportableStatus; }
+ }
+
+ ///
+ /// Finds a service of specific type which has the same metadata as class using the dependency manager.
+ /// If multiple services found, the one with the highest priority will be returned
+ ///
+ /// The type of the service
+ /// A service of type T or null if not found
+ protected T GetService()
+ where T : IExportable
+ {
+ return GetService(Metadata);
+ }
+
+ ///
+ /// Finds a service of specific type which has the same metadata as class using the dependency manager.
+ /// If multiple services found, the one with the highest priority will be returned
+ ///
+ /// The type of the service
+ /// A service of type T or null if not found
+ protected T GetService(IExportableMetadata metadata)
+ where T : IExportable
+ {
+ //Don't try to find the service if it's the same service as current one with same metadata
+ if (ServiceProvider != null && (!(this is T) || metadata != Metadata))
+ {
+ return ServiceProvider.GetService(metadata);
+ }
+ return default(T);
+ }
+
+ ///
+ /// An instance of ITrace which is exported to the extension manager
+ ///
+ public override ITrace Trace
+ {
+ get
+ {
+ return (_trace = _trace ?? GetService());
+ }
+ set
+ {
+ _trace = value;
+ }
+ }
+
+ ///
+ /// ServerDefinition created from the metadata
+ ///
+ protected ServerDefinition ServerDefinition
+ {
+ get
+ {
+ return Metadata != null ? new ServerDefinition(Metadata.ServerType, Metadata.Category) : null;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExportableStatus.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExportableStatus.cs
new file mode 100644
index 00000000..1521e5cc
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExportableStatus.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Extensibility
+{
+ ///
+ /// Includes the status of the exportable - whether it failed to load and any error message
+ /// returned during loading
+ ///
+ public class ExportableStatus
+ {
+ ///
+ /// Returns true if the loading of the exportable failed
+ ///
+ public bool LoadingFailed
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// An error message if the loading failed
+ ///
+ public string ErrorMessage { get; set; }
+
+ ///
+ /// An info link to navigate to
+ ///
+ public string InfoLink { get; set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExtensionUtils.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExtensionUtils.cs
new file mode 100644
index 00000000..163c49e4
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ExtensionUtils.cs
@@ -0,0 +1,175 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Microsoft.SqlTools.Extensibility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Extensibility
+{
+ ///
+ /// Extension methods for exportable and service
+ ///
+ public static class ExtensionUtils
+ {
+
+ ///
+ /// Finds a service of specific type which has the same metadata as class using the service provider.
+ /// If multiple services found, the one with the highest priority will be returned
+ ///
+ /// The type of the service
+ /// A service of type T or null if not found
+ public static T GetService(this IMultiServiceProvider provider, IServerDefinition serverDefinition)
+ where T : IExportable
+ {
+ return provider.GetServices()
+ .FilterExportables(serverDefinition)
+ .OrderByDescending(s => SortOrder(s)).
+ FirstOrDefault();
+ }
+
+ private static int SortOrder(T service)
+ {
+ IExportable exportable = service as IExportable;
+ if (exportable != null)
+ {
+ return exportable.Metadata.Priority;
+ }
+ return 0;
+ }
+
+ public static IEnumerable FilterExportables(this IEnumerable exportables, IServerDefinition serverDefinition = null)
+ where T : IExportable
+ {
+ if (exportables == null)
+ {
+ return null;
+ }
+ //Get all the possible matches
+ IEnumerable allMatched = serverDefinition != null ?
+ exportables.Where(x => Match(x.Metadata, serverDefinition)).ToList() : exportables;
+ IList list = allMatched.ToList();
+
+ //If specific server type requested and the list has any item with that server type remove the others.
+ //for instance is there's server for all server types and one specifically for sql and give metadata is asking for sql then
+ //we should return the sql one even if the other service has higher priority
+
+ IList withSameServerType = list.Where(x => serverDefinition.HasSameServerName(x.Metadata)).ToList();
+ if (withSameServerType.Any())
+ {
+ list = withSameServerType;
+ }
+ IList withSameCategory = list.Where(x => serverDefinition.HasSameCategory(x.Metadata)).ToList();
+ if (withSameCategory.Any())
+ {
+ list = withSameCategory;
+ }
+ return list;
+ }
+
+ public static bool HasSameServerName(this IServerDefinition serverDefinition, IServerDefinition metadata)
+ {
+ if (serverDefinition != null && metadata != null)
+ {
+ // Note: this does not handle null <-> string.Empty equivalence. For now ignoring this as it should not matter
+ return string.Equals(serverDefinition.ServerType, metadata.ServerType, StringComparison.OrdinalIgnoreCase);
+ }
+ return false;
+ }
+
+ public static bool HasSameCategory(this IServerDefinition serverDefinition, IServerDefinition metadata)
+ {
+ if (serverDefinition != null && metadata != null)
+ {
+ // Note: this does not handle null <-> string.Empty equivalence. For now ignoring this as it should not matter
+ return string.Equals(serverDefinition.Category, metadata.Category, StringComparison.OrdinalIgnoreCase);
+ }
+ return false;
+ }
+
+ ///
+ /// Returns true if the given server definition is secure type. (i.e. Azure)
+ ///
+ internal static bool IsSecure(this IServerDefinition serverDefinition)
+ {
+ if (serverDefinition != null && serverDefinition.Category != null)
+ {
+ return serverDefinition.Category.Equals(Categories.Azure, StringComparison.OrdinalIgnoreCase);
+ }
+ return false;
+ }
+
+ internal static string GetServerDefinitionKey(this IServerDefinition serverDefinition)
+ {
+ string key = string.Empty;
+ if (serverDefinition != null)
+ {
+ key = string.Format(CultureInfo.InvariantCulture, "{0}", GetKey(serverDefinition.Category));
+ }
+
+ return key;
+ }
+
+ internal static bool EqualsServerDefinition(this IServerDefinition serverDefinition, IServerDefinition otherServerDefinition)
+ {
+ if (serverDefinition == null && otherServerDefinition == null)
+ {
+ return true;
+ }
+ if (serverDefinition != null && otherServerDefinition != null)
+ {
+ return (((string.IsNullOrEmpty(serverDefinition.Category) && string.IsNullOrEmpty(otherServerDefinition.Category)) || serverDefinition.HasSameCategory(otherServerDefinition)) &&
+ ((string.IsNullOrEmpty(serverDefinition.ServerType) && string.IsNullOrEmpty(otherServerDefinition.ServerType)) || serverDefinition.HasSameServerName(otherServerDefinition)));
+ }
+ return false;
+ }
+
+ internal static bool EmptyOrEqual(this string value1, string value2)
+ {
+ if (string.IsNullOrEmpty(value1) && string.IsNullOrEmpty(value2))
+ {
+ return true;
+ }
+ return value1 == value2;
+ }
+
+ private static string GetKey(string name)
+ {
+ return string.IsNullOrEmpty(name) ? string.Empty : name.ToUpperInvariant();
+ }
+
+ ///
+ /// Returns true if the metadata matches the given server definition
+ ///
+ public static bool Match(this IServerDefinition first, IServerDefinition other)
+ {
+ if (first == null)
+ {
+ // TODO should we handle this differently?
+ return false;
+ }
+ if (other == null)
+ {
+ return false;
+ }
+ return MatchMetaData(first.ServerType, other.ServerType)
+ && MatchMetaData(first.Category, other.Category);
+ }
+
+ ///
+ /// Returns true if the metadata value matches the given value
+ ///
+ private static bool MatchMetaData(string metaData, string requestedMetaData)
+ {
+ if (string.IsNullOrEmpty(metaData) || string.IsNullOrEmpty(requestedMetaData))
+ {
+ return true;
+ }
+ return (metaData.Equals(requestedMetaData, StringComparison.OrdinalIgnoreCase));
+ }
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/IExportable.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/IExportable.cs
new file mode 100644
index 00000000..807e1d4d
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/IExportable.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using Microsoft.SqlTools.Extensibility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Extensibility
+{
+ ///
+ /// An interface to be implemented by any class that needs to be exportable
+ ///
+ public interface IExportable : IComposableService
+ {
+ ///
+ /// The metadata assigned to the exportable
+ ///
+ IExportableMetadata Metadata
+ {
+ set; get;
+ }
+
+ ///
+ /// Returns the status of the exportable
+ ///
+ ExportableStatus Status
+ {
+ get;
+ }
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/IExportableMetadata.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/IExportableMetadata.cs
new file mode 100644
index 00000000..3c052daa
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/IExportableMetadata.cs
@@ -0,0 +1,97 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using Microsoft.SqlTools.Extensibility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Extensibility
+{
+ ///
+ /// The metadata describing an extension
+ ///
+ public interface IExportableMetadata : IStandardMetadata, IServerDefinition
+ {
+ ///
+ /// Exportable priority tobe used when multiple of same type found
+ ///
+ int Priority
+ {
+ get;
+ }
+ }
+
+ public class ExportableMetadata : IExportableMetadata
+ {
+ ///
+ /// Default constructor to initialize the metadata of the exportable
+ ///
+ /// The server type supported by the exportable. If not set, exportable supports all server types
+ /// The category supported by the exportable. If not set, exportable supports all categories
+ /// The unique id of the exportable. Used by the extension manager to pick only one from exportables with same id in the same assembly
+ /// The priority of the exportable. The extension manager will pick the exportable with the highest priority if multiple found
+ /// The display name of the exportable. This field is optional
+ public ExportableMetadata(
+ string serverType,
+ string category,
+ string id,
+ int priority = 0,
+ string displayName = null)
+ {
+ Category = category;
+ ServerType = serverType;
+ Id = id;
+ DisplayName = displayName;
+ Priority = priority;
+ }
+
+ ///
+ /// The constructor to define an exportable by type, id and priority only. To be used by the exportables that support all server types and categories.
+ /// For example: the implementation of can be used for all server types and categories.
+ ///
+ /// The unique id of the exportable. Used by the extension manager to pick only one from exportables with same id in the same assembly
+ /// The priority of the exportable. The extension manager will pick the exportable with the highest priority if multiple found
+ /// The display name of the exportable. This field is optional
+ public ExportableMetadata(string id, int priority = 0,
+ string displayName = null) :
+ this(string.Empty, string.Empty, id, priority, displayName)
+ {
+ }
+
+ public int Priority
+ {
+ get;
+ set;
+ }
+
+ public string Version
+ {
+ get;
+ set;
+ }
+
+ public string Id
+ {
+ get;
+ set;
+ }
+
+ public string DisplayName
+ {
+ get;
+ set;
+ }
+
+ public string Category
+ {
+ get;
+ set;
+ }
+
+ public string ServerType
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ITrace.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ITrace.cs
new file mode 100644
index 00000000..a03cab6f
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/ITrace.cs
@@ -0,0 +1,44 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Extensibility
+{
+ ///
+ /// Provides facility to trace code execution through calls to Trace* methods.
+ /// Implementing classes must add a
+ /// to the class in order to be found by the extension manager
+ ///
+ public interface ITrace : IExportable
+ {
+ ///
+ /// Write a formatted trace event message to the underlying trace source.
+ ///
+ /// Event type that specifies the verbosity level of the trace.
+ /// The category of the caller's product feature.
+ /// Format string of the message to be traced along with the event.
+ /// Object array containing zero or more objects to format.
+ /// True if event was successfully written
+ bool TraceEvent(TraceEventType eventType, int traceId, string message, params object[] args);
+
+ ///
+ /// Write a trace event with a message and exception details to the underlying trace source.
+ ///
+ /// Event type that specifies the verbosity level of the trace.
+ /// The category of the caller's product feature.
+ /// The exception to be logged.
+ /// String message to be traced along with the event.
+ /// Compile time property to trace the line number of the calling code. Used to trace location error occurred
+ /// Compile time property to trace the fileName of the calling code. Used to trace location error occurred
+ /// Compile time property to trace the name of the calling method. Used to trace location error occurred
+ /// True if event was successfully written
+ bool TraceException(TraceEventType eventType, int traceId, Exception exception, string message,
+ [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "",
+ [CallerMemberName] string memberName = "");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/TraceId.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/TraceId.cs
new file mode 100644
index 00000000..28dee2b8
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/TraceId.cs
@@ -0,0 +1,86 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Extensibility
+{
+ ///
+ /// Enumeration of values used as trace event identifiers that semantically represent the major categories of product features.
+ ///
+ public enum TraceId : int
+ {
+ ///
+ /// Trace Id for Azure Authentication traces
+ ///
+ AzureAuthentication = 0,
+ ///
+ /// Trace Id for azure resource traces
+ ///
+ AzureResource = 1,
+ ///
+ /// Trace Id for UI sections traces
+ ///
+ Sections = 2,
+
+ ///
+ /// Trace Id for connection traces
+ ///
+ Connection = 3,
+
+ ///
+ /// Trace Id for firewall rule traces
+ ///
+ FirewallRule = 4,
+
+ ///
+ /// Trace Id for Azure browse traces
+ ///
+ AzureSection = 5,
+
+ ///
+ /// Trace Id for network servers
+ ///
+ NetworkServers = 6,
+
+ ///
+ /// Trace Id for local servers
+ ///
+ LocalServers = 7,
+
+ ///
+ /// Trace Id for sql database discovery
+ ///
+ SqlDatabase = 8,
+
+ ///
+ /// Trace Id for local browse traces
+ ///
+ LocalSection = 9,
+
+ ///
+ /// Trace Id for account picker traces
+ ///
+ AccountPicker = 10,
+
+ ///
+ /// Trace Id for network browse traces
+ ///
+ NetworkSection = 11,
+
+ ///
+ /// Trace Id for main dialog traces
+ ///
+ UiInfra = 12,
+
+ ///
+ /// Trace Id for hostory page traces
+ ///
+ HistoryPage = 13,
+
+ ///
+ /// TraceId for Telemetry
+ ///
+ Telemetry = 14,
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/Traceable.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/Traceable.cs
new file mode 100644
index 00000000..0a62c983
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/Traceable.cs
@@ -0,0 +1,51 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using Microsoft.SqlTools.Extensibility;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Extensibility
+{
+ ///
+ /// Enables tracing feature for classes
+ ///
+ internal class Traceable : TraceableBase, IComposableService
+ {
+ private IMultiServiceProvider _serviceProvider;
+ private ITrace _trace;
+
+ public Traceable()
+ {
+ }
+
+ public Traceable(ITrace trace)
+ {
+ _trace = trace;
+ }
+
+ public override ITrace Trace
+ {
+ get
+ {
+ if (_trace == null)
+ {
+ if (_serviceProvider != null)
+ {
+ _trace = _serviceProvider.GetService();
+ }
+ }
+ return _trace;
+ }
+ set
+ {
+ _trace = value;
+ }
+ }
+
+ public void SetServiceProvider(IMultiServiceProvider provider)
+ {
+ _serviceProvider = provider;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/TraceableBase.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/TraceableBase.cs
new file mode 100644
index 00000000..8eb9046d
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Extensibility/TraceableBase.cs
@@ -0,0 +1,222 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.Extensibility
+{
+ ///
+ /// An abstract class to be used for classes that need to have trace feature
+ ///
+ public abstract class TraceableBase
+ {
+ ///
+ /// Returns the first implementation of trace in the catalog that has highest priority
+ ///
+ public abstract ITrace Trace
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Write a trace event message to the underlying trace source.
+ ///
+ public bool TraceEvent(TraceEventType eventType, TraceId traceId, string format, params object[] args)
+ {
+ return TraceEvent(eventType, (int)traceId, format, args);
+ }
+
+ ///
+ /// Write a trace event message to the underlying trace source.
+ ///
+ public bool TraceEvent(TraceEventType eventType, int traceId, string format, params object[] args)
+ {
+ return SafeTrace(eventType, traceId, format, args);
+ }
+
+ ///
+ /// Write a formatted trace event message to the underlying trace source and issue a Debug.Fail() call
+ /// if condition is false.
+ ///
+ public bool AssertTraceEvent(bool condition, TraceEventType eventType, TraceId traceId, string message)
+ {
+ return AssertTraceEvent(condition, eventType, (int)traceId, message);
+ }
+
+ ///
+ /// Write a formatted trace event message to the underlying trace source and issue a Debug.Fail() call
+ /// if condition is false.
+ ///
+ public bool AssertTraceEvent(bool condition, TraceEventType eventType, int traceId, string message)
+ {
+ if (!condition)
+ {
+ return DebugTraceEvent(eventType, traceId, message);
+ }
+ return false;
+ }
+
+ ///
+ /// Write a trace event with a message and exception details to the underlying trace source and issue a
+ /// Debug.Fail() call if the condition is false.
+ ///
+ ///
+ /// Note: often the fact that an exception has been thrown by itself is enough to determine that the message
+ /// should be logged. If so please use DebugTraceException() instead. This method is for if the exception should
+ /// only be logged if some additional condition is also false.
+ ///
+ /// Must be false for Debug or Trace event to be issued.
+ /// Event type that specifies the verbosity level of the trace.
+ /// The category of the caller's product feature.
+ /// The exception to be logged.
+ /// Message to be traced along with the event.
+ /// True if event was successfully written or the condition was true
+ public bool AssertTraceException(bool condition, TraceEventType eventType, TraceId traceId, Exception exception, string message)
+ {
+ return AssertTraceException(condition, eventType, (int) traceId, exception, message);
+ }
+
+ ///
+ /// Write a trace event with a message and exception details to the underlying trace source and issue a
+ /// Debug.Fail() call if the condition is false.
+ ///
+ ///
+ /// Note: often the fact that an exception has been thrown by itself is enough to determine that the message
+ /// should be logged. If so please use DebugTraceException() instead. This method is for if the exception should
+ /// only be logged if some additional condition is also false.
+ ///
+ /// Must be false for Debug or Trace event to be issued.
+ /// Event type that specifies the verbosity level of the trace.
+ /// The category of the caller's product feature.
+ /// The exception to be logged.
+ /// Message to be traced along with the event.
+ /// True if event was successfully written or the condition was true
+ public bool AssertTraceException2(bool condition, TraceEventType eventType, TraceId traceId, Exception exception, string message,
+ [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "",
+ [CallerMemberName] string memberName = "")
+ {
+ if (!condition)
+ {
+ return DebugTraceException2(eventType, (int) traceId, exception, message, lineNumber, fileName, memberName);
+ }
+ return true;
+ }
+
+ ///
+ /// Write a trace event with a message and exception details to the underlying trace source and issue a
+ /// Debug.Fail() call if the condition is false.
+ ///
+ ///
+ /// Note: often the fact that an exception has been thrown by itself is enough to determine that the message
+ /// should be logged. If so please use DebugTraceException() instead. This method is for if the exception should
+ /// only be logged if some additional condition is also false.
+ ///
+ /// Must be false for Debug or Trace event to be issued.
+ /// Event type that specifies the verbosity level of the trace.
+ /// The category of the caller's product feature.
+ /// The exception to be logged.
+ /// Message to be traced along with the event.
+ /// True if event was successfully written or the condition was true
+ public bool AssertTraceException(bool condition, TraceEventType eventType, int traceId, Exception exception, string message)
+ {
+ if (!condition)
+ {
+ return DebugTraceException(eventType, traceId, exception, message);
+ }
+ return true;
+ }
+
+ ///
+ /// Write a trace event with a message and exception details to the underlying trace source and issue a
+ /// Debug.Fail() call with the same message.
+ ///
+ /// Event type that specifies the verbosity level of the trace.
+ /// The category of the caller's product feature.
+ /// The exception to be logged.
+ /// Message to be traced along with the event.
+ /// True if event was successfully written
+ public bool DebugTraceException(TraceEventType eventType, int traceId, Exception exception, string message)
+ {
+ // Avoiding breaking change by not overloading this method. Passing default values to TraceException so this isn't
+ // reported as the callsite for majority of exceptions
+ bool success = TraceException(eventType, traceId, exception, message, 0, string.Empty, string.Empty);
+ Debug.Fail(message);
+ return success;
+ }
+
+ public bool DebugTraceException2(TraceEventType eventType, int traceId, Exception exception, string message,
+ [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "",
+ [CallerMemberName] string memberName = "")
+ {
+ bool success = TraceException(eventType, traceId, exception, message, lineNumber, fileName, memberName);
+ Debug.Fail(message);
+ return success;
+ }
+
+ ///
+ /// Write a trace event with a message and exception details to the underlying trace source.
+ ///
+ public bool TraceException(TraceEventType eventType, TraceId traceId, Exception exception, string message,
+ [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "",
+ [CallerMemberName] string memberName = "")
+ {
+ return TraceException(eventType, (int)traceId, exception, message, lineNumber, fileName, memberName);
+ }
+
+ ///
+ /// Write a trace event with a message and exception details to the underlying trace source.
+ ///
+ public bool TraceException(TraceEventType eventType, int traceId, Exception exception, string message,
+ [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "",
+ [CallerMemberName] string memberName = "")
+ {
+ return SafeTraceException(eventType, traceId, exception, message, lineNumber, fileName, memberName);
+ }
+
+ ///
+ /// Write a formatted trace event message to the underlying trace source and issue a Debug.Fail() with
+ /// the same message.
+ ///
+ /// Event type that specifies the verbosity level of the trace.
+ /// The category of the caller's product feature.
+ /// Message to be output via Debug.Fail() and traced along with the event.
+ /// True if event was successfully written
+ public bool DebugTraceEvent(TraceEventType eventType, int traceId, string message)
+ {
+ bool success = TraceEvent(eventType, traceId, message);
+ Debug.Fail(message);
+ return success;
+ }
+
+ ///
+ /// Verifies ITrace instance is not null before tracing
+ ///
+ private bool SafeTrace(TraceEventType eventType, int traceId, string format, params object[] args)
+ {
+ if (Trace != null)
+ {
+ return Trace.TraceEvent(eventType, traceId, format, args);
+ }
+ return false;
+ }
+
+ ///
+ /// Verifies ITrace instance is not null before tracing the exception
+ ///
+ private bool SafeTraceException(TraceEventType eventType, int traceId, Exception exception, string message,
+ [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "",
+ [CallerMemberName] string memberName = "")
+ {
+ if (Trace != null)
+ {
+ return Trace.TraceException(eventType, traceId, exception, message, lineNumber, fileName, memberName);
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallErrorParser.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallErrorParser.cs
new file mode 100644
index 00000000..44af4eae
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallErrorParser.cs
@@ -0,0 +1,102 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Data.SqlClient;
+using System.Net;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule
+{
+ internal interface IFirewallErrorParser
+ {
+ ///
+ /// Parses given error message and error code to see if it's firewall rule error
+ /// and finds the blocked ip address
+ ///
+ FirewallParserResponse ParseErrorMessage(string errorMessage, int errorCode);
+
+ ///
+ /// Parses given error message and error code to see if it's firewall rule error
+ /// and finds the blocked ip address
+ ///
+ FirewallParserResponse ParseException(SqlException sqlException);
+ }
+
+ ///
+ /// Parses an error to check for firewall rule error. Will include the blocked ip address if firewall rule error is detected
+ ///
+ public class FirewallErrorParser : IFirewallErrorParser
+ {
+ ///
+ /// Parses given error message and error code to see if it's firewall rule error
+ /// and finds the blocked ip address
+ ///
+ public FirewallParserResponse ParseException(SqlException sqlException)
+ {
+ CommonUtil.CheckForNull(sqlException, "sqlException");
+ return ParseErrorMessage(sqlException.Message, sqlException.Number);
+ }
+
+ ///
+ /// Parses given error message and error code to see if it's firewall rule error
+ /// and finds the blocked ip address
+ ///
+ public FirewallParserResponse ParseErrorMessage(string errorMessage, int errorCode)
+ {
+ CommonUtil.CheckForNull(errorMessage, "errorMessage");
+
+ FirewallParserResponse response = new FirewallParserResponse();
+ if (IsSqlAzureFirewallBlocked(errorCode))
+ {
+ // Connection failed due to blocked client IP
+ IPAddress clientIp;
+ if (TryParseClientIp(errorMessage, out clientIp))
+ {
+ response = new FirewallParserResponse(true, clientIp);
+ }
+ }
+ return response;
+ }
+
+ ///
+ /// Parses the given message to find the blocked ip address
+ ///
+ private static bool TryParseClientIp(string message, out IPAddress clientIp)
+ {
+ clientIp = null;
+ try
+ {
+ Regex regex =
+ new Regex(
+ @"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b",
+ RegexOptions.IgnoreCase);
+ Match match = regex.Match(message);
+
+ if (match.Success)
+ {
+ string clientIpValue = match.Value;
+ return IPAddress.TryParse(clientIpValue, out clientIp);
+ }
+
+ return false;
+ }
+ catch (Exception)
+ {
+ //TODO: trace?
+ return false;
+ }
+ }
+
+ ///
+ /// Returns true if given error code is firewall rule blocked error code
+ ///
+ private bool IsSqlAzureFirewallBlocked(int errorCode)
+ {
+ return errorCode == SqlAzureFirewallBlockedErrorNumber;
+ }
+
+ private const int SqlAzureFirewallBlockedErrorNumber = 40615; // http://msdn.microsoft.com/en-us/library/windowsazure/ff394106.aspx
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallParserResponse.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallParserResponse.cs
new file mode 100644
index 00000000..724f7f39
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallParserResponse.cs
@@ -0,0 +1,41 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Net;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule
+{
+ ///
+ /// The response that's created by firewall rule parser
+ ///
+ public class FirewallParserResponse
+ {
+ public FirewallParserResponse(bool firewallRuleErrorDetected, IPAddress blockedIpAddress)
+ {
+ FirewallRuleErrorDetected = firewallRuleErrorDetected;
+ BlockedIpAddress = blockedIpAddress;
+ }
+
+ public FirewallParserResponse()
+ {
+ FirewallRuleErrorDetected = false;
+ }
+
+ ///
+ /// Returns true if firewall rule is detected, otherwise returns false.
+ ///
+ public bool FirewallRuleErrorDetected
+ {
+ get; private set;
+ }
+
+ ///
+ /// Returns the blocked ip address if firewall rule is detected
+ ///
+ public IPAddress BlockedIpAddress
+ {
+ get; private set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleException.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleException.cs
new file mode 100644
index 00000000..772854b5
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleException.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Net;
+using System.Runtime.Serialization;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule
+{
+ ///
+ /// Exception used by firewall service to indicate when firewall rule operation fails
+ ///
+ public class FirewallRuleException : ServiceExceptionBase
+ {
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class.
+ ///
+ public FirewallRuleException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ public FirewallRuleException(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The Http error code.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public FirewallRuleException(string message, HttpStatusCode httpStatusCode, Exception innerException = null)
+ : base(message, httpStatusCode, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The Http error code.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public FirewallRuleException(string message, int httpStatusCode, Exception innerException = null)
+ : base(message, httpStatusCode, innerException)
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message
+ /// and a reference to the inner exception that is the cause of this exception.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public FirewallRuleException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with serialized data.
+ ///
+ /// The SerializationInfo that holds the serialized object data about the exception being thrown.
+ /// The StreamingContext that contains contextual information about the source or destination.
+ public FirewallRuleException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleRequest.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleRequest.cs
new file mode 100644
index 00000000..f0a46325
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleRequest.cs
@@ -0,0 +1,40 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Globalization;
+using System.Net;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule
+{
+ ///
+ /// Includes all the information needed to create a firewall rule
+ ///
+ public class FirewallRuleRequest
+ {
+ ///
+ /// Start IP address
+ ///
+ public IPAddress StartIpAddress { get; set; }
+
+ ///
+ /// End IP address
+ ///
+ public IPAddress EndIpAddress { get; set; }
+
+ ///
+ /// Firewall rule name
+ ///
+ public string FirewallRuleName
+ {
+ get
+ {
+ DateTime now = DateTime.UtcNow;
+
+ return string.Format(CultureInfo.InvariantCulture, "ClientIPAddress_{0}",
+ now.ToString("yyyy-MM-dd_hh:mm:ss", CultureInfo.CurrentCulture));
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResource.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResource.cs
new file mode 100644
index 00000000..6681385e
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResource.cs
@@ -0,0 +1,36 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule
+{
+ ///
+ /// Includes azure resource and subscription needed to create firewall rule
+ ///
+ public class FirewallRuleResource
+ {
+ ///
+ /// Azure resource
+ ///
+ public IAzureSqlServerResource AzureResource { get; set; }
+
+ ///
+ /// Azure Subscription
+ ///
+ public IAzureUserAccountSubscriptionContext SubscriptionContext { get; set; }
+
+
+ ///
+ /// Returns true if the resource and subscription are not null
+ ///
+ public bool IsValid
+ {
+ get
+ {
+ return AzureResource != null && SubscriptionContext != null;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResponse.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResponse.cs
new file mode 100644
index 00000000..c90a2461
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResponse.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule
+{
+ ///
+ /// The response that's created when the firewall rule creation request is complete
+ ///
+ public class FirewallRuleResponse
+ {
+ ///
+ /// End IP address
+ ///
+ public string EndIpAddress
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Start IP address
+ ///
+ public string StartIpAddress
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Indicates whether the firewall rule created successfully or not
+ ///
+ public bool Created { get; set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleService.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleService.cs
new file mode 100644
index 00000000..9acf18a6
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleService.cs
@@ -0,0 +1,257 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule
+{
+
+ public interface IFirewallRuleService
+ {
+ ///
+ /// Creates firewall rule for given server name and IP address range. Throws exception if operation fails
+ ///
+ Task CreateFirewallRuleAsync(string serverName, string startIpAddressValue, string endIpAddressValue);
+
+
+ ///
+ /// Creates firewall rule for given server name and IP address range. Throws exception if operation fails
+ ///
+ Task CreateFirewallRuleAsync(string serverName, IPAddress startIpAddress, IPAddress endIpAddress);
+
+
+ ///
+ /// Sets and gets azure resource manager instance. can be used by unit tests
+ ///
+ IAzureResourceManager ResourceManager
+ {
+ get; set;
+ }
+
+ ///
+ /// Sets and gets authentication manager instance. can be used by unit tests
+ ///
+ IAzureAuthenticationManager AuthenticationManager
+ {
+ get; set;
+ }
+ }
+
+ ///
+ /// Service to be used by the controls to create firewall rule
+ ///
+ public class FirewallRuleService : IFirewallRuleService
+ {
+ ///
+ /// Creates firewall rule for given server name and IP address range. Throws exception if operation fails
+ ///
+ public async Task CreateFirewallRuleAsync(string serverName, string startIpAddressValue, string endIpAddressValue)
+ {
+ IPAddress startIpAddress = ConvertToIpAddress(startIpAddressValue);
+ IPAddress endIpAddress = ConvertToIpAddress(endIpAddressValue);
+ return await CreateFirewallRuleAsync(serverName, startIpAddress, endIpAddress);
+ }
+
+ ///
+ /// Creates firewall rule for given server name and IP address range. Throws exception if operation fails
+ ///
+ public async Task CreateFirewallRuleAsync(string serverName, IPAddress startIpAddress, IPAddress endIpAddress)
+ {
+ try
+ {
+ FirewallRuleResponse firewallRuleResponse = new FirewallRuleResponse() { Created = false };
+ CommonUtil.CheckStringForNullOrEmpty(serverName, "serverName");
+ CommonUtil.CheckForNull(startIpAddress, "startIpAddress");
+ CommonUtil.CheckForNull(endIpAddress, "endIpAddress");
+
+ IAzureAuthenticationManager authenticationManager = AuthenticationManager;
+
+ if (authenticationManager != null && !await authenticationManager.GetUserNeedsReauthenticationAsync())
+ {
+ FirewallRuleResource firewallRuleResource = await FindAzureResourceAsync(serverName);
+ firewallRuleResponse = await CreateFirewallRule(firewallRuleResource, startIpAddress, endIpAddress);
+ }
+ if (firewallRuleResponse == null || !firewallRuleResponse.Created)
+ {
+ throw new FirewallRuleException(SR.FirewallRuleCreationFailed);
+ }
+ return firewallRuleResponse;
+ }
+ catch (ServiceExceptionBase)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ throw new FirewallRuleException(SR.FirewallRuleCreationFailed, ex);
+ }
+ }
+
+ ///
+ /// Sets and gets azure resource manager instance. can be used by unit tests
+ ///
+ public IAzureResourceManager ResourceManager { get; set; }
+
+ ///
+ /// Sets and gets authentication manager instance. can be used by unit tests
+ ///
+ public IAzureAuthenticationManager AuthenticationManager { get; set; }
+
+ ///
+ /// Creates firewall rule for given subscription and IP address range
+ ///
+ private async Task CreateFirewallRule(FirewallRuleResource firewallRuleResource, IPAddress startIpAddress, IPAddress endIpAddress)
+ {
+ CommonUtil.CheckForNull(firewallRuleResource, "firewallRuleResource");
+
+ try
+ {
+ if (firewallRuleResource.IsValid)
+ {
+
+ FirewallRuleRequest request = new FirewallRuleRequest()
+ {
+ StartIpAddress = startIpAddress,
+ EndIpAddress = endIpAddress
+ };
+ using (IAzureResourceManagementSession session = await ResourceManager.CreateSessionAsync(firewallRuleResource.SubscriptionContext))
+ {
+ return await ResourceManager.CreateFirewallRuleAsync(
+ session,
+ firewallRuleResource.AzureResource,
+ request);
+ }
+ }
+ }
+ catch (ServiceExceptionBase)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ throw new FirewallRuleException(SR.FirewallRuleCreationFailed, ex);
+ }
+
+ return new FirewallRuleResponse()
+ {
+ Created = false
+ };
+ }
+
+ ///
+ /// Finds an azure resource for given server name under user's subscriptions
+ ///
+ private async Task FindAzureResourceAsync(string serverName)
+ {
+ try
+ {
+ IEnumerable subscriptions = await AuthenticationManager.GetSubscriptionsAsync();
+
+ if (subscriptions == null)
+ {
+ throw new FirewallRuleException(SR.FirewallRuleCreationFailed);
+ }
+
+ foreach (IAzureUserAccountSubscriptionContext subscription in subscriptions)
+ {
+ using (IAzureResourceManagementSession session = await ResourceManager.CreateSessionAsync(subscription))
+ {
+ IAzureSqlServerResource azureSqlServer = await FindAzureResourceForSubscriptionAsync(serverName, session);
+
+ if (azureSqlServer != null)
+ {
+ return new FirewallRuleResource()
+ {
+ SubscriptionContext = subscription,
+ AzureResource = azureSqlServer
+ };
+ }
+ }
+ }
+ var currentUser = await AuthenticationManager.GetCurrentAccountAsync();
+
+ throw new FirewallRuleException(string.Format(CultureInfo.CurrentCulture, SR.AzureServerNotFound, serverName, currentUser != null ? currentUser.UniqueId : string.Empty));
+ }
+ catch (ServiceExceptionBase)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ throw new FirewallRuleException(SR.FirewallRuleCreationFailed, ex);
+ }
+ }
+
+ ///
+ /// Throws a firewallRule exception based on give status code
+ ///
+ private void HandleError(ServiceExceptionBase exception, string serverName,
+ IAzureUserAccountSubscriptionContext subscription)
+ {
+ var accountName = subscription != null && subscription.UserAccount != null &&
+ subscription.UserAccount.DisplayInfo != null
+ ? subscription.UserAccount.DisplayInfo.UserName
+ : string.Empty;
+
+ switch (exception.HttpStatusCode)
+ {
+ case HttpStatusCode.NotFound:
+ throw new FirewallRuleException(string.Format(CultureInfo.CurrentCulture, SR.AzureServerNotFound, serverName, accountName), exception);
+ case HttpStatusCode.Forbidden:
+ throw new FirewallRuleException(string.Format(CultureInfo.CurrentCulture, SR.FirewallRuleAccessForbidden, accountName), exception);
+ default:
+ throw new FirewallRuleException(SR.FirewallRuleCreationFailed, exception.HttpStatusCode, exception);
+ }
+ }
+
+ ///
+ /// Finds Azure resource for the given subscription and server name
+ ///
+ private async Task FindAzureResourceForSubscriptionAsync(
+ string serverName,
+ IAzureResourceManagementSession session)
+ {
+ try
+ {
+ IEnumerable resources = await ResourceManager.GetSqlServerAzureResourcesAsync(session);
+
+ if (resources == null)
+ {
+ return null;
+ }
+ foreach (IAzureSqlServerResource resource in resources)
+ {
+ if (serverName.Equals(resource.FullyQualifiedDomainName, StringComparison.OrdinalIgnoreCase))
+ {
+ return resource;
+ }
+ }
+ }
+ catch (ServiceExceptionBase ex)
+ {
+ HandleError(ex, serverName, session.SubscriptionContext);
+ }
+ catch (Exception ex)
+ {
+ throw new FirewallRuleException(SR.FirewallRuleCreationFailed, ex);
+ }
+ return null;
+ }
+
+ private IPAddress ConvertToIpAddress(string ipAddressValue)
+ {
+ IPAddress ipAddress;
+ if (!IPAddress.TryParse(ipAddressValue, out ipAddress))
+ {
+ throw new FirewallRuleException(SR.InvalidIpAddress);
+ }
+ return ipAddress;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.cs
new file mode 100644
index 00000000..1533eed7
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.cs
@@ -0,0 +1,150 @@
+// WARNING:
+// This file was generated by the Microsoft DataWarehouse String Resource Tool 1.37.0.0
+// from information in sr.strings
+// DO NOT MODIFY THIS FILE'S CONTENTS, THEY WILL BE OVERWRITTEN
+//
+namespace Microsoft.SqlTools.ResourceProvider.Core
+{
+ using System;
+ using System.Reflection;
+ using System.Resources;
+ using System.Globalization;
+
+ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class SR
+ {
+ protected SR()
+ { }
+
+ public static CultureInfo Culture
+ {
+ get
+ {
+ return Keys.Culture;
+ }
+ set
+ {
+ Keys.Culture = value;
+ }
+ }
+
+
+ public static string AzureServerNotFound
+ {
+ get
+ {
+ return Keys.GetString(Keys.AzureServerNotFound);
+ }
+ }
+
+ public static string AzureSubscriptionFailedErrorMessage
+ {
+ get
+ {
+ return Keys.GetString(Keys.AzureSubscriptionFailedErrorMessage);
+ }
+ }
+
+ public static string DatabaseDiscoveryFailedErrorMessage
+ {
+ get
+ {
+ return Keys.GetString(Keys.DatabaseDiscoveryFailedErrorMessage);
+ }
+ }
+
+ public static string FirewallRuleAccessForbidden
+ {
+ get
+ {
+ return Keys.GetString(Keys.FirewallRuleAccessForbidden);
+ }
+ }
+
+ public static string FirewallRuleCreationFailed
+ {
+ get
+ {
+ return Keys.GetString(Keys.FirewallRuleCreationFailed);
+ }
+ }
+
+ public static string InvalidIpAddress
+ {
+ get
+ {
+ return Keys.GetString(Keys.InvalidIpAddress);
+ }
+ }
+
+ public static string InvalidServerTypeErrorMessage
+ {
+ get
+ {
+ return Keys.GetString(Keys.InvalidServerTypeErrorMessage);
+ }
+ }
+
+ public static string LoadingExportableFailedGeneralErrorMessage
+ {
+ get
+ {
+ return Keys.GetString(Keys.LoadingExportableFailedGeneralErrorMessage);
+ }
+ }
+
+ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class Keys
+ {
+ static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ResourceProvider.Core.Localization.SR", typeof(SR).GetTypeInfo().Assembly);
+
+ static CultureInfo _culture = null;
+
+
+ public const string AzureServerNotFound = "AzureServerNotFound";
+
+
+ public const string AzureSubscriptionFailedErrorMessage = "AzureSubscriptionFailedErrorMessage";
+
+
+ public const string DatabaseDiscoveryFailedErrorMessage = "DatabaseDiscoveryFailedErrorMessage";
+
+
+ public const string FirewallRuleAccessForbidden = "FirewallRuleAccessForbidden";
+
+
+ public const string FirewallRuleCreationFailed = "FirewallRuleCreationFailed";
+
+
+ public const string InvalidIpAddress = "InvalidIpAddress";
+
+
+ public const string InvalidServerTypeErrorMessage = "InvalidServerTypeErrorMessage";
+
+
+ public const string LoadingExportableFailedGeneralErrorMessage = "LoadingExportableFailedGeneralErrorMessage";
+
+
+ private Keys()
+ { }
+
+ public static CultureInfo Culture
+ {
+ get
+ {
+ return _culture;
+ }
+ set
+ {
+ _culture = value;
+ }
+ }
+
+ public static string GetString(string key)
+ {
+ return resourceManager.GetString(key, _culture);
+ }
+
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.resx b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.resx
new file mode 100644
index 00000000..865b7923
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.resx
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The server you specified {0} does not exist in any subscription in {1}. Either you have signed in with an incorrect account or your server was removed from subscription(s) in this account. Please check your account and try again.
+
+
+
+ An error occurred while getting Azure subscriptions
+
+
+
+ An error occurred while getting databases from servers of type {0} from {1}
+
+
+
+ {0} does not have permission to change the server firewall rule. Try again with a different account that is an Owner or Contributor of the Azure subscription or the server.
+
+
+
+ An error occurred while creating a new firewall rule.
+
+
+
+ Invalid IP address
+
+
+
+ Server Type is invalid.
+
+
+
+ A required dll cannot be loaded. Please repair your application.
+
+
+
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.strings b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.strings
new file mode 100644
index 00000000..ba1bed62
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.strings
@@ -0,0 +1,32 @@
+# String resource file
+#
+# When processed by the String Resource Tool, this file generates
+# both a .CS and a .RESX file with the same name as the file.
+# The .CS file contains a class which can be used to access these
+# string resources, including the ability to format in
+# parameters, which are identified with the .NET {x} format
+# (see String.Format help).
+#
+# Comments below assume the file name is SR.strings.
+#
+# Lines starting with a semicolon ";" are also treated as comments, but
+# in a future version they will be extracted and made available in LocStudio
+# Put your comments to localizers _before_ the string they apply to.
+#
+# SMO build specific comment
+# after generating the .resx file, run srgen on it and get the .resx file
+# please remember to also check that .resx in, along with the
+# .strings and .cs files
+
+[strings]
+
+############################################################################
+# Azure Core DLL
+AzureServerNotFound = The server you specified {0} does not exist in any subscription in {1}. Either you have signed in with an incorrect account or your server was removed from subscription(s) in this account. Please check your account and try again.
+AzureSubscriptionFailedErrorMessage = An error occurred while getting Azure subscriptions
+DatabaseDiscoveryFailedErrorMessage = An error occurred while getting databases from servers of type {0} from {1}
+FirewallRuleAccessForbidden = {0} does not have permission to change the server firewall rule. Try again with a different account that is an Owner or Contributor of the Azure subscription or the server.
+FirewallRuleCreationFailed = An error occurred while creating a new firewall rule.
+InvalidIpAddress = Invalid IP address
+InvalidServerTypeErrorMessage = Server Type is invalid.
+LoadingExportableFailedGeneralErrorMessage = A required dll cannot be loaded. Please repair your application.
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.xlf b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.xlf
new file mode 100644
index 00000000..3ab7cbb5
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.xlf
@@ -0,0 +1,47 @@
+
+
+
+
+
+ The server you specified {0} does not exist in any subscription in {1}. Either you have signed in with an incorrect account or your server was removed from subscription(s) in this account. Please check your account and try again.
+ The server you specified {0} does not exist in any subscription in {1}. Either you have signed in with an incorrect account or your server was removed from subscription(s) in this account. Please check your account and try again.
+
+
+
+ An error occurred while getting Azure subscriptions
+ An error occurred while getting Azure subscriptions
+
+
+
+ An error occurred while getting databases from servers of type {0} from {1}
+ An error occurred while getting databases from servers of type {0} from {1}
+
+
+
+ {0} does not have permission to change the server firewall rule. Try again with a different account that is an Owner or Contributor of the Azure subscription or the server.
+ {0} does not have permission to change the server firewall rule. Try again with a different account that is an Owner or Contributor of the Azure subscription or the server.
+
+
+
+ Invalid IP address
+ Invalid IP address
+
+
+
+ Server Type is invalid.
+ Server Type is invalid.
+
+
+
+ A required dll cannot be loaded. Please repair your application.
+ A required dll cannot be loaded. Please repair your application.
+
+
+
+ An error occurred while creating a new firewall rule.
+ An error occurred while creating a new firewall rule.
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Microsoft.SqlTools.ResourceProvider.Core.csproj b/src/Microsoft.SqlTools.ResourceProvider.Core/Microsoft.SqlTools.ResourceProvider.Core.csproj
new file mode 100644
index 00000000..b7b58a82
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Microsoft.SqlTools.ResourceProvider.Core.csproj
@@ -0,0 +1,26 @@
+
+
+ netstandard2.0
+ Microsoft.SqlTools.ResourceProvider.Core
+ Microsoft.SqlTools.ResourceProvider.Core
+ false
+
+ Library
+
+ Provides Resource Provider and control plane services for SqlTools applications.
+ � Microsoft Corporation. All rights reserved.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Properties/AssemblyInfo.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..03deec20
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Properties/AssemblyInfo.cs
@@ -0,0 +1,15 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: InternalsVisibleTo("Microsoft.SqlTools.ServiceLayer.UnitTests")]
+[assembly: InternalsVisibleTo("Microsoft.SqlTools.ServiceLayer.IntegrationTests")]
+[assembly: InternalsVisibleTo("Microsoft.SqlTools.ServiceLayer.Test.Common")]
+
+// Allowing internals visible access to Moq library to help testing
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceFailedException.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceFailedException.cs
new file mode 100644
index 00000000..0a4882ba
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceFailedException.cs
@@ -0,0 +1,81 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Net;
+using System.Runtime.Serialization;
+using Microsoft.SqlTools.ResourceProvider.Core;
+
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ ///
+ /// Exception to be raised if azure resource manager operation fails
+ ///
+ public class AzureResourceFailedException : ServiceExceptionBase
+ {
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class.
+ ///
+ public AzureResourceFailedException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ public AzureResourceFailedException(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The Http error code.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public AzureResourceFailedException(string message, HttpStatusCode httpStatusCode, Exception innerException = null)
+ : base(message, httpStatusCode, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The Http error code.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public AzureResourceFailedException(string message, int httpStatusCode, Exception innerException = null)
+ : base(message, httpStatusCode, innerException)
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with a specified error message
+ /// and a reference to the inner exception that is the cause of this exception.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public AzureResourceFailedException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the AuthenticationFailedException class with serialized data.
+ ///
+ /// The SerializationInfo that holds the serialized object data about the exception being thrown.
+ /// The StreamingContext that contains contextual information about the source or destination.
+ public AzureResourceFailedException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceManagementSession.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceManagementSession.cs
new file mode 100644
index 00000000..4d5a30c0
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceManagementSession.cs
@@ -0,0 +1,93 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Microsoft.Azure.Management.ResourceManager;
+using Microsoft.Azure.Management.Sql;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ ///
+ /// VS session used by .
+ /// Includes all the clients that the resource management needs to get ther resources
+ ///
+ public class AzureResourceManagementSession : IAzureResourceManagementSession
+ {
+ ///
+ /// Creates the new session for given clients
+ ///
+ /// Sql Management Client
+ /// Resource Management Client
+ /// Subscription Context
+ public AzureResourceManagementSession(SqlManagementClient sqlManagementClient,
+ ResourceManagementClient resourceManagementClient,
+ IAzureUserAccountSubscriptionContext subscriptionContext)
+ {
+ SqlManagementClient = sqlManagementClient;
+ ResourceManagementClient = resourceManagementClient;
+ SubscriptionContext = subscriptionContext;
+ }
+
+ ///
+ /// Disposes the session
+ ///
+ public void Dispose()
+ {
+ CloseSession();
+ }
+
+ ///
+ /// Closes the session by disposing the clients
+ ///
+ ///
+ public bool CloseSession()
+ {
+ try
+ {
+ if (ResourceManagementClient != null)
+ {
+ ResourceManagementClient.Dispose();
+ }
+
+ if (SqlManagementClient != null)
+ {
+ SqlManagementClient.Dispose();
+ }
+ return true;
+ }
+ catch (Exception)
+ {
+ //TODO: trace
+ return false;
+ }
+ }
+
+ ///
+ /// Subscription Context
+ ///
+ public IAzureUserAccountSubscriptionContext SubscriptionContext
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Resource Management Client
+ ///
+ public ResourceManagementClient ResourceManagementClient
+ {
+ get; set;
+ }
+
+ ///
+ /// Sql Management Client
+ ///
+ public SqlManagementClient SqlManagementClient
+ {
+ get; set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceManager.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceManager.cs
new file mode 100644
index 00000000..af7dbb0d
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceManager.cs
@@ -0,0 +1,258 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Azure.Management.ResourceManager;
+using Microsoft.Azure.Management.ResourceManager.Models;
+using Microsoft.Azure.Management.Sql;
+using Microsoft.Azure.Management.Sql.Models;
+using RestFirewallRule = Microsoft.Azure.Management.Sql.Models.FirewallRule;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+using Microsoft.SqlTools.ResourceProvider.Core.FirewallRule;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+using Microsoft.SqlTools.Utility;
+using Microsoft.Rest;
+using System.Globalization;
+using Microsoft.Rest.Azure;
+using Microsoft.SqlTools.ResourceProvider.Core;
+
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ ///
+ /// Default implementation for
+ /// Provides functionality to get azure resources by making Http request to the Azure REST API
+ ///
+ [Exportable(
+ ServerTypes.SqlServer,
+ Categories.Azure,
+ typeof(IAzureResourceManager),
+ "Microsoft.SqlServer.ConnectionServices.Azure.Impl.VsAzureResourceManager",
+ 1)
+ ]
+ public class AzureResourceManager : ExportableBase, IAzureResourceManager
+ {
+ private readonly Uri _resourceManagementUri = new Uri("https://management.azure.com/");
+
+ public AzureResourceManager()
+ {
+ // Duplicate the exportable attribute as at present we do not support filtering using extensiondescriptor.
+ // The attribute is preserved in order to simplify ability to backport into existing tools
+ Metadata = new ExportableMetadata(
+ ServerTypes.SqlServer,
+ Categories.Azure,
+ "Microsoft.SqlServer.ConnectionServices.Azure.Impl.VsAzureResourceManager");
+ }
+
+ public async Task CreateSessionAsync(IAzureUserAccountSubscriptionContext subscriptionContext)
+ {
+ CommonUtil.CheckForNull(subscriptionContext, "subscriptionContext");
+ try
+ {
+ ServiceClientCredentials credentials = await CreateCredentialsAsync(subscriptionContext);
+ SqlManagementClient sqlManagementClient = new SqlManagementClient(_resourceManagementUri, credentials);
+ ResourceManagementClient resourceManagementClient = new ResourceManagementClient(_resourceManagementUri, credentials);
+ return new AzureResourceManagementSession(sqlManagementClient, resourceManagementClient, subscriptionContext);
+ }
+ catch (Exception ex)
+ {
+ Logger.Write(LogLevel.Error, string.Format(CultureInfo.CurrentCulture, "Failed to get databases {0}", ex));
+ throw;
+ }
+ }
+
+ ///
+ /// Returns a list of azure databases given subscription resource group name and server name
+ ///
+ /// Subscription Context which includes credentials to use in the resource manager
+ /// Resource Group Name
+ /// Server name
+ /// The list of databases
+ public async Task> GetAzureDatabasesAsync(
+ IAzureResourceManagementSession azureResourceManagementSession,
+ string resourceGroupName,
+ string serverName)
+ {
+ CommonUtil.CheckForNull(azureResourceManagementSession, "azureResourceManagerSession");
+ try
+ {
+ AzureResourceManagementSession vsAzureResourceManagementSession = azureResourceManagementSession as AzureResourceManagementSession;
+
+ if (vsAzureResourceManagementSession != null)
+ {
+ try
+ {
+ IEnumerable databaseListResponse = await vsAzureResourceManagementSession.SqlManagementClient.Databases.ListByServerAsync(resourceGroupName, serverName);
+ return databaseListResponse.Select(
+ x => new AzureResourceWrapper(x) { ResourceGroupName = resourceGroupName });
+ }
+ catch(HttpOperationException ex)
+ {
+ throw new AzureResourceFailedException(SR.FailedToGetAzureDatabasesErrorMessage, ex.Response.StatusCode);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Write(LogLevel.Error, string.Format(CultureInfo.CurrentCulture, "Failed to get databases {0}", ex.Message));
+ throw;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Returns a list of azure servers given subscription
+ ///
+ /// Subscription Context which includes credentials to use in the resource manager
+ /// The list of Sql server resources
+ public async Task> GetSqlServerAzureResourcesAsync(
+ IAzureResourceManagementSession azureResourceManagementSession)
+ {
+ CommonUtil.CheckForNull(azureResourceManagementSession, "azureResourceManagerSession");
+ List sqlServers = new List();
+ try
+ {
+ AzureResourceManagementSession vsAzureResourceManagementSession = azureResourceManagementSession as AzureResourceManagementSession;
+ if(vsAzureResourceManagementSession != null)
+ {
+ IEnumerable resourceGroupNames = await GetResourceGroupsAsync(vsAzureResourceManagementSession);
+ if (resourceGroupNames != null)
+ {
+ foreach (ResourceGroup resourceGroupExtended in resourceGroupNames)
+ {
+ try
+ {
+ IServersOperations serverOperations = vsAzureResourceManagementSession.SqlManagementClient.Servers;
+ IPage servers = await serverOperations.ListByResourceGroupAsync(resourceGroupExtended.Name);
+ if (servers != null)
+ {
+ sqlServers.AddRange(servers.Select(x =>
+ new SqlAzureResource(x) { ResourceGroupName = resourceGroupExtended.Name }));
+ }
+ }
+ catch (HttpOperationException ex)
+ {
+ throw new AzureResourceFailedException(SR.FailedToGetAzureSqlServersErrorMessage, ex.Response.StatusCode);
+ }
+ }
+ }
+ }
+ }
+ catch(Exception ex)
+ {
+ TraceException(TraceEventType.Error, (int) TraceId.AzureResource, ex, "Failed to get servers");
+ throw;
+ }
+
+ return sqlServers;
+ }
+
+ public async Task CreateFirewallRuleAsync(
+ IAzureResourceManagementSession azureResourceManagementSession,
+ IAzureSqlServerResource azureSqlServer,
+ FirewallRuleRequest firewallRuleRequest)
+ {
+ CommonUtil.CheckForNull(azureResourceManagementSession, "azureResourceManagerSession");
+ CommonUtil.CheckForNull(firewallRuleRequest, "firewallRuleRequest");
+ CommonUtil.CheckForNull(azureSqlServer, "azureSqlServer");
+
+ try
+ {
+ AzureResourceManagementSession vsAzureResourceManagementSession = azureResourceManagementSession as AzureResourceManagementSession;
+
+ if (vsAzureResourceManagementSession != null)
+ {
+ try
+ {
+ var firewallRule = new RestFirewallRule()
+ {
+ EndIpAddress = firewallRuleRequest.EndIpAddress.ToString(),
+ StartIpAddress = firewallRuleRequest.StartIpAddress.ToString()
+ };
+ IFirewallRulesOperations firewallRuleOperations = vsAzureResourceManagementSession.SqlManagementClient.FirewallRules;
+ var firewallRuleResponse = await firewallRuleOperations.CreateOrUpdateAsync(
+ azureSqlServer.ResourceGroupName,
+ azureSqlServer.Name,
+ firewallRuleRequest.FirewallRuleName,
+ firewallRule);
+ return new FirewallRuleResponse()
+ {
+ StartIpAddress = firewallRuleResponse.StartIpAddress,
+ EndIpAddress = firewallRuleResponse.EndIpAddress,
+ Created = true
+ };
+ }
+ catch (HttpOperationException ex)
+ {
+ throw new AzureResourceFailedException(SR.FirewallRuleCreationFailed, ex.Response.StatusCode);
+ }
+ }
+ // else respond with failure case
+ return new FirewallRuleResponse()
+ {
+ Created = false
+ };
+ }
+ catch (Exception ex)
+ {
+ TraceException(TraceEventType.Error, (int) TraceId.AzureResource, ex, "Failed to get databases");
+ throw;
+ }
+ }
+
+ ///
+ /// Returns the azure resource groups for given subscription
+ ///
+ private async Task> GetResourceGroupsAsync(AzureResourceManagementSession vsAzureResourceManagementSession)
+ {
+ try
+ {
+ if (vsAzureResourceManagementSession != null)
+ {
+ try
+ {
+ IResourceGroupsOperations resourceGroupOperations = vsAzureResourceManagementSession.ResourceManagementClient.ResourceGroups;
+ IPage resourceGroupList = await resourceGroupOperations.ListAsync();
+ if (resourceGroupList != null)
+ {
+ return resourceGroupList.AsEnumerable();
+ }
+
+ }
+ catch (HttpOperationException ex)
+ {
+ throw new AzureResourceFailedException(SR.FailedToGetAzureResourceGroupsErrorMessage, ex.Response.StatusCode);
+ }
+ }
+
+ return Enumerable.Empty();
+ }
+ catch (Exception ex)
+ {
+ TraceException(TraceEventType.Error, (int)TraceId.AzureResource, ex, "Failed to get azure resource groups");
+ throw;
+ }
+ }
+
+
+ ///
+ /// Creates credential instance for given subscription
+ ///
+ private Task CreateCredentialsAsync(IAzureUserAccountSubscriptionContext subscriptionContext)
+ {
+ AzureUserAccountSubscriptionContext azureUserSubContext =
+ subscriptionContext as AzureUserAccountSubscriptionContext;
+
+ if (azureUserSubContext != null)
+ {
+ return Task.FromResult(azureUserSubContext.Credentials);
+ }
+ throw new NotSupportedException("This uses an unknown subscription type");
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceWrapper.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceWrapper.cs
new file mode 100644
index 00000000..6b0f5bdb
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceWrapper.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Microsoft.Azure.Management.Sql.Models;
+using Microsoft.SqlTools.ResourceProvider.Core;
+
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ ///
+ /// Implementation for using VS services.
+ /// Provides information about an Azure resource
+ ///
+ public class AzureResourceWrapper : IAzureResource
+ {
+ ///
+ /// Initializes the resource
+ ///
+ public AzureResourceWrapper(TrackedResource azureResource)
+ {
+ CommonUtil.CheckForNull(azureResource, nameof(azureResource));
+ AzureResource = azureResource;
+ }
+
+ ///
+ /// Resource name
+ ///
+ public string Name
+ {
+ get
+ {
+ return AzureResource != null ? AzureResource.Name : string.Empty;
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ ///
+ /// Resource type
+ ///
+ public string Type
+ {
+ get
+ {
+ return AzureResource != null ? AzureResource.Type : string.Empty;
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ ///
+ /// Resource id
+ ///
+ public string Id
+ {
+ get
+ {
+ return AzureResource != null ? AzureResource.Id : string.Empty;
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ ///
+ /// Resource Group Name
+ ///
+ public string ResourceGroupName { get; set; }
+
+ ///
+ /// Resource Location
+ ///
+ public string Location
+ {
+ get
+ {
+ return AzureResource != null ? AzureResource.Location : string.Empty;
+ }
+ set
+ {
+ if (AzureResource != null)
+ {
+ AzureResource.Location = value;
+ }
+ }
+ }
+
+ ///
+ /// The resource wrapped by this class
+ ///
+ protected TrackedResource AzureResource
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureSubscriptionContext.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureSubscriptionContext.cs
new file mode 100644
index 00000000..d2a9dc00
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureSubscriptionContext.cs
@@ -0,0 +1,56 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ ///
+ /// Implementation for using VS services
+ /// Contains information about an Azure subscription
+ ///
+ public class AzureSubscriptionContext : IAzureSubscriptionContext
+ {
+ private readonly IAzureSubscriptionIdentifier _azureSubscriptionIdentifier;
+
+ ///
+ /// Default constructor to initialize the subscription
+ ///
+ public AzureSubscriptionContext(IAzureSubscriptionIdentifier azureSubscriptionIdentifier)
+ {
+ _azureSubscriptionIdentifier = azureSubscriptionIdentifier;
+ }
+
+ ///
+ /// Returns true if given subscription equals this class
+ ///
+ public bool Equals(IAzureSubscriptionContext other)
+ {
+ return (other == null && Subscription == null) || (other != null && other.Subscription.Equals(Subscription));
+ }
+
+ ///
+ /// Returns the wraper for the subscription identifier
+ ///
+ public IAzureSubscriptionIdentifier Subscription
+ {
+ get
+ {
+ return _azureSubscriptionIdentifier;
+ }
+ }
+
+ ///
+ /// Returns subscription name
+ ///
+ public string SubscriptionName
+ {
+ get
+ {
+ return _azureSubscriptionIdentifier != null ?
+ _azureSubscriptionIdentifier.SubscriptionId : string.Empty;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureSubscriptionIdentifier.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureSubscriptionIdentifier.cs
new file mode 100644
index 00000000..52b2b949
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureSubscriptionIdentifier.cs
@@ -0,0 +1,61 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ ///
+ /// Implementation for
+ /// Contains information about an Azure subscription identifier
+ ///
+ public class AzureSubscriptionIdentifier : IAzureSubscriptionIdentifier
+ {
+ ///
+ /// Default constructor to initialize the subscription identifier
+ ///
+ public AzureSubscriptionIdentifier(IAzureUserAccount userAccount, string subscriptionId, Uri serviceManagementEndpoint)
+ {
+ UserAccount = userAccount;
+ SubscriptionId = subscriptionId;
+ ServiceManagementEndpoint = serviceManagementEndpoint;
+ }
+
+ ///
+ /// Returns true if given subscription identifier equals this class
+ ///
+ public bool Equals(IAzureSubscriptionIdentifier other)
+ {
+ return other != null &&
+ CommonUtil.SameString(SubscriptionId, other.SubscriptionId) &&
+ CommonUtil.SameUri(ServiceManagementEndpoint, other.ServiceManagementEndpoint);
+ }
+
+ public IAzureUserAccount UserAccount
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Returns the endpoint url used by the identifier
+ ///
+ public Uri ServiceManagementEndpoint
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Subscription id
+ ///
+ public string SubscriptionId
+ {
+ get;
+ private set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccount.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccount.cs
new file mode 100644
index 00000000..075dd9ca
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccount.cs
@@ -0,0 +1,93 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ ///
+ /// Implementation for using VS services
+ /// Contains information about an Azure account
+ ///
+ public class AzureUserAccount : IAzureUserAccount
+ {
+ private string uniqueId;
+
+ ///
+ /// Default constructor to initializes user session
+ ///
+ public AzureUserAccount()
+ {
+ }
+
+ ///
+ /// Default constructor to initializes user session
+ ///
+ public AzureUserAccount(IAzureUserAccount azureUserAccount)
+ {
+ CopyFrom(azureUserAccount);
+ }
+
+ private void CopyFrom(IAzureUserAccount azureUserAccount)
+ {
+ this.DisplayInfo = new AzureUserAccountDisplayInfo(azureUserAccount.DisplayInfo);
+ this.NeedsReauthentication = azureUserAccount.NeedsReauthentication;
+ this.TenantId = azureUserAccount.TenantId;
+ this.UniqueId = azureUserAccount.UniqueId;
+ }
+ ///
+ /// Returns true if given user account equals this class
+ ///
+ public bool Equals(IAzureUserAccount other)
+ {
+ return other != null &&
+ CommonUtil.SameString(other.UniqueId, UniqueId) &&
+ CommonUtil.SameString(other.TenantId, TenantId);
+ }
+
+ ///
+ /// Unique Id
+ ///
+ public string UniqueId
+ {
+ get
+ {
+ return uniqueId == null ? string.Empty : uniqueId;
+ }
+ set
+ {
+ this.uniqueId = value;
+ }
+ }
+
+ ///
+ /// Returns true if user needs reauthentication
+ ///
+ public bool NeedsReauthentication
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// User display info
+ ///
+ public IAzureUserAccountDisplayInfo DisplayInfo
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Tenant Id
+ ///
+ public string TenantId
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccountDisplayInfo.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccountDisplayInfo.cs
new file mode 100644
index 00000000..ac4d40e2
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccountDisplayInfo.cs
@@ -0,0 +1,110 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ ///
+ /// Implementation for using VS services
+ /// Contains information about an Azure account display info
+ ///
+ public class AzureUserAccountDisplayInfo : IAzureUserAccountDisplayInfo
+ {
+ private string userName;
+ private string accountDisplayName;
+
+ ///
+ /// Creating the instance using
+ ///
+ public AzureUserAccountDisplayInfo(IAzureUserAccountDisplayInfo azureUserAccountDisplayInfo)
+ {
+ CopyFrom(azureUserAccountDisplayInfo);
+ }
+
+ ///
+ /// Creating empty instance
+ ///
+ public AzureUserAccountDisplayInfo()
+ {
+ }
+
+ private void CopyFrom(IAzureUserAccountDisplayInfo azureUserAccountDisplayInfo)
+ {
+ this.AccountDisplayName = azureUserAccountDisplayInfo.AccountDisplayName;
+ this.AccountLogo = azureUserAccountDisplayInfo.AccountLogo;
+ this.ProviderDisplayName = azureUserAccountDisplayInfo.ProviderDisplayName;
+ this.ProviderLogo = azureUserAccountDisplayInfo.ProviderLogo;
+ this.UserName = azureUserAccountDisplayInfo.UserName;
+ }
+
+ ///
+ /// Returns true if given user account equals this class
+ ///
+ public bool Equals(IAzureUserAccountDisplayInfo other)
+ {
+ return other != null &&
+ ((other.AccountDisplayName == null && AccountDisplayName == null ) || (other.AccountDisplayName != null && other.AccountDisplayName.Equals(AccountDisplayName))) &&
+ ((other.UserName == null && UserName == null ) || (other.UserName != null && other.UserName.Equals(UserName)));
+ }
+
+ ///
+ /// Account display name
+ ///
+ public string AccountDisplayName
+ {
+ get
+ {
+ return accountDisplayName != null ? accountDisplayName : string.Empty;
+ }
+ set
+ {
+ accountDisplayName = value;
+ }
+ }
+
+ ///
+ /// Account lego
+ ///
+ public byte[] AccountLogo
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Provider display name
+ ///
+ public string ProviderDisplayName
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Provider lego
+ ///
+ public byte[] ProviderLogo
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// User name
+ ///
+ public string UserName
+ {
+ get
+ {
+ return userName != null ? userName : string.Empty;
+ }
+ set
+ {
+ userName = value;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccountSubscriptionContext.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccountSubscriptionContext.cs
new file mode 100644
index 00000000..6c03c946
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccountSubscriptionContext.cs
@@ -0,0 +1,92 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Microsoft.Rest;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ ///
+ /// Implementation for using built-in services
+ /// Contains information about an Azure account subscription
+ ///
+ public class AzureUserAccountSubscriptionContext : IAzureUserAccountSubscriptionContext
+ {
+
+ ///
+ /// Default constructor to initializes user account and subscription
+ ///
+ public AzureUserAccountSubscriptionContext(AzureSubscriptionIdentifier subscription, ServiceClientCredentials credentials)
+ {
+ CommonUtil.CheckForNull(subscription, nameof(subscription));
+ CommonUtil.CheckForNull(credentials, nameof(credentials));
+ Subscription = subscription;
+ Credentials = credentials;
+ }
+
+ ///
+ /// Creates a subscription context for connecting with a known access token. This creates a object for use
+ /// in a request
+ ///
+ public static AzureUserAccountSubscriptionContext CreateStringTokenContext(AzureSubscriptionIdentifier subscription, string accessToken)
+ {
+ CommonUtil.CheckForNull(subscription, nameof(subscription));
+ CommonUtil.CheckStringForNullOrEmpty(accessToken, nameof(accessToken));
+ TokenCredentials credentials = new TokenCredentials(accessToken);
+ return new AzureUserAccountSubscriptionContext(subscription, credentials);
+ }
+
+ public bool Equals(IAzureSubscriptionContext other)
+ {
+ return other != null && other.Equals(this);
+ }
+
+ ///
+ /// Returns the wraper for the subscription identifier
+ ///
+ public IAzureSubscriptionIdentifier Subscription
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Subscription name
+ ///
+ public string SubscriptionName
+ {
+ get { return Subscription != null ? Subscription.SubscriptionId : string.Empty; }
+ }
+
+ ///
+ ///
+ ///
+ public bool Equals(IAzureUserAccountSubscriptionContext other)
+ {
+ return other != null &&
+ CommonUtil.SameSubscriptionIdentifier(Subscription, other.Subscription) &&
+ CommonUtil.SameUserAccount(UserAccount, other.UserAccount);
+ }
+
+ ///
+ /// User Account
+ ///
+ public IAzureUserAccount UserAccount
+ {
+ get
+ {
+ return Subscription != null ?
+ new AzureUserAccount(Subscription.UserAccount) : null;
+ }
+ }
+
+ public ServiceClientCredentials Credentials
+ {
+ get;
+ private set;
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.cs
new file mode 100644
index 00000000..b052d9e7
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.cs
@@ -0,0 +1,106 @@
+// WARNING:
+// This file was generated by the Microsoft DataWarehouse String Resource Tool 1.37.0.0
+// from information in sr.strings
+// DO NOT MODIFY THIS FILE'S CONTENTS, THEY WILL BE OVERWRITTEN
+//
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ using System;
+ using System.Reflection;
+ using System.Resources;
+ using System.Globalization;
+
+ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class SR
+ {
+ protected SR()
+ { }
+
+ public static CultureInfo Culture
+ {
+ get
+ {
+ return Keys.Culture;
+ }
+ set
+ {
+ Keys.Culture = value;
+ }
+ }
+
+
+ public static string FailedToGetAzureDatabasesErrorMessage
+ {
+ get
+ {
+ return Keys.GetString(Keys.FailedToGetAzureDatabasesErrorMessage);
+ }
+ }
+
+ public static string FailedToGetAzureResourceGroupsErrorMessage
+ {
+ get
+ {
+ return Keys.GetString(Keys.FailedToGetAzureResourceGroupsErrorMessage);
+ }
+ }
+
+ public static string FailedToGetAzureSqlServersErrorMessage
+ {
+ get
+ {
+ return Keys.GetString(Keys.FailedToGetAzureSqlServersErrorMessage);
+ }
+ }
+
+ public static string FirewallRuleCreationFailed
+ {
+ get
+ {
+ return Keys.GetString(Keys.FirewallRuleCreationFailed);
+ }
+ }
+
+ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class Keys
+ {
+ static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ResourceProvider.DefaultImpl.Localization.SR", typeof(SR).GetTypeInfo().Assembly);
+
+ static CultureInfo _culture = null;
+
+
+ public const string FailedToGetAzureDatabasesErrorMessage = "FailedToGetAzureDatabasesErrorMessage";
+
+
+ public const string FailedToGetAzureResourceGroupsErrorMessage = "FailedToGetAzureResourceGroupsErrorMessage";
+
+
+ public const string FailedToGetAzureSqlServersErrorMessage = "FailedToGetAzureSqlServersErrorMessage";
+
+
+ public const string FirewallRuleCreationFailed = "FirewallRuleCreationFailed";
+
+
+ private Keys()
+ { }
+
+ public static CultureInfo Culture
+ {
+ get
+ {
+ return _culture;
+ }
+ set
+ {
+ _culture = value;
+ }
+ }
+
+ public static string GetString(string key)
+ {
+ return resourceManager.GetString(key, _culture);
+ }
+
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.resx b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.resx
new file mode 100644
index 00000000..d44a779c
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.resx
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ An error occurred while getting Azure databases
+
+
+
+ An error occurred while getting Azure resource groups
+
+
+
+ An error occurred while getting Azure Sql Servers
+
+
+
+ An error occurred while creating a new firewall rule.
+
+
+
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.strings b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.strings
new file mode 100644
index 00000000..3e87c835
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.strings
@@ -0,0 +1,28 @@
+# String resource file
+#
+# When processed by the String Resource Tool, this file generates
+# both a .CS and a .RESX file with the same name as the file.
+# The .CS file contains a class which can be used to access these
+# string resources, including the ability to format in
+# parameters, which are identified with the .NET {x} format
+# (see String.Format help).
+#
+# Comments below assume the file name is SR.strings.
+#
+# Lines starting with a semicolon ";" are also treated as comments, but
+# in a future version they will be extracted and made available in LocStudio
+# Put your comments to localizers _before_ the string they apply to.
+#
+# SMO build specific comment
+# after generating the .resx file, run srgen on it and get the .resx file
+# please remember to also check that .resx in, along with the
+# .strings and .cs files
+
+[strings]
+
+############################################################################
+# Azure Core DLL
+FailedToGetAzureDatabasesErrorMessage = An error occurred while getting Azure databases
+FailedToGetAzureResourceGroupsErrorMessage = An error occurred while getting Azure resource groups
+FailedToGetAzureSqlServersErrorMessage = An error occurred while getting Azure Sql Servers
+FirewallRuleCreationFailed = An error occurred while creating a new firewall rule.
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.xlf b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.xlf
new file mode 100644
index 00000000..0c54fb77
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.xlf
@@ -0,0 +1,27 @@
+
+
+
+
+
+ An error occurred while getting Azure databases
+ An error occurred while getting Azure databases
+
+
+
+ An error occurred while getting Azure resource groups
+ An error occurred while getting Azure resource groups
+
+
+
+ An error occurred while getting Azure Sql Servers
+ An error occurred while getting Azure Sql Servers
+
+
+
+ An error occurred while creating a new firewall rule.
+ An error occurred while creating a new firewall rule.
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj
new file mode 100644
index 00000000..0c4a8845
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj
@@ -0,0 +1,31 @@
+
+
+ netstandard2.0
+ Microsoft.SqlTools.ResourceProvider.DefaultImpl
+ Microsoft.SqlTools.ResourceProvider.DefaultImpl
+ false
+
+ Library
+
+ Provides the default for SqlTools applications.
+ � Microsoft Corporation. All rights reserved.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/SqlAzureResource.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/SqlAzureResource.cs
new file mode 100644
index 00000000..47929de1
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/SqlAzureResource.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Models = Microsoft.Azure.Management.Sql.Models;
+
+namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl
+{
+ ///
+ /// Implementation for using VS services
+ /// Provides information about an Azure Sql Server resource
+ ///
+ public class SqlAzureResource : AzureResourceWrapper, IAzureSqlServerResource
+ {
+ private readonly Models.Server _azureSqlServerResource;
+
+ ///
+ /// Initializes the resource
+ ///
+ public SqlAzureResource(Models.Server azureResource) : base(azureResource)
+ {
+ CommonUtil.CheckForNull(azureResource, nameof(azureResource));
+ _azureSqlServerResource = azureResource;
+ }
+
+ ///
+ /// Fully qualified domain name
+ ///
+ public string FullyQualifiedDomainName
+ {
+ get
+ {
+ return _azureSqlServerResource != null ? _azureSqlServerResource.FullyQualifiedDomainName : string.Empty;
+ }
+ }
+
+ ///
+ /// Administrator User
+ ///
+ public string AdministratorLogin
+ {
+ get
+ {
+ return _azureSqlServerResource != null ? _azureSqlServerResource.AdministratorLogin : string.Empty;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.cs b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.cs
new file mode 100644
index 00000000..8db98dd4
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.cs
@@ -0,0 +1,62 @@
+// WARNING:
+// This file was generated by the Microsoft DataWarehouse String Resource Tool 1.37.0.0
+// from information in sr.strings
+// DO NOT MODIFY THIS FILE'S CONTENTS, THEY WILL BE OVERWRITTEN
+//
+namespace Microsoft.SqlTools.ResourceProvider
+{
+ using System;
+ using System.Reflection;
+ using System.Resources;
+ using System.Globalization;
+
+ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class SR
+ {
+ protected SR()
+ { }
+
+ public static CultureInfo Culture
+ {
+ get
+ {
+ return Keys.Culture;
+ }
+ set
+ {
+ Keys.Culture = value;
+ }
+ }
+
+
+ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class Keys
+ {
+ static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ResourceProvider.Localization.SR", typeof(SR).GetTypeInfo().Assembly);
+
+ static CultureInfo _culture = null;
+
+
+ private Keys()
+ { }
+
+ public static CultureInfo Culture
+ {
+ get
+ {
+ return _culture;
+ }
+ set
+ {
+ _culture = value;
+ }
+ }
+
+ public static string GetString(string key)
+ {
+ return resourceManager.GetString(key, _culture);
+ }
+
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.resx b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.resx
new file mode 100644
index 00000000..09ac8ab9
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
diff --git a/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.strings b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.strings
new file mode 100644
index 00000000..aca2b7d0
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.strings
@@ -0,0 +1,24 @@
+# String resource file
+#
+# When processed by the String Resource Tool, this file generates
+# both a .CS and a .RESX file with the same name as the file.
+# The .CS file contains a class which can be used to access these
+# string resources, including the ability to format in
+# parameters, which are identified with the .NET {x} format
+# (see String.Format help).
+#
+# Comments below assume the file name is SR.strings.
+#
+# Lines starting with a semicolon ";" are also treated as comments, but
+# in a future version they will be extracted and made available in LocStudio
+# Put your comments to localizers _before_ the string they apply to.
+#
+# SMO build specific comment
+# after generating the .resx file, run srgen on it and get the .resx file
+# please remember to also check that .resx in, along with the
+# .strings and .cs files
+
+[strings]
+
+############################################################################
+# Azure Service DLL
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.xlf b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.xlf
new file mode 100644
index 00000000..c5319993
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider/Localization/sr.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ResourceProvider/Microsoft.SqlTools.ResourceProvider.csproj b/src/Microsoft.SqlTools.ResourceProvider/Microsoft.SqlTools.ResourceProvider.csproj
new file mode 100644
index 00000000..d1e9e31e
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider/Microsoft.SqlTools.ResourceProvider.csproj
@@ -0,0 +1,28 @@
+
+
+ netcoreapp2.0
+ SqlToolsResourceProviderService
+ SqlToolsResourceProviderService
+ Exe
+ Microsoft
+ Sql Tools Service for Resource Provider services
+ Provides Resource Provider and control plane support.
+ � Microsoft Corporation. All rights reserved.
+ true
+ true
+ portable
+ false
+ win7-x64;win7-x86;ubuntu.14.04-x64;ubuntu.16.04-x64;centos.7-x64;rhel.7.2-x64;debian.8-x64;fedora.23-x64;opensuse.13.2-x64;osx.10.11-x64
+
+
+ TRACE;DEBUG;NETCOREAPP1_0;NETCOREAPP2_0
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ResourceProvider/Program.cs b/src/Microsoft.SqlTools.ResourceProvider/Program.cs
new file mode 100644
index 00000000..aa2d2d42
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider/Program.cs
@@ -0,0 +1,63 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using Microsoft.SqlTools.Hosting.Utility;
+using Microsoft.SqlTools.ServiceLayer.SqlContext;
+using Microsoft.SqlTools.Utility;
+
+namespace Microsoft.SqlTools.ResourceProvider
+{
+ ///
+ /// Main application class for Credentials Service Host executable
+ ///
+ internal class Program
+ {
+ private const string ServiceName = "SqlToolsAzure.exe";
+
+ ///
+ /// Main entry point into the Credentials Service Host
+ ///
+ internal static void Main(string[] args)
+ {
+ try
+ {
+ // read command-line arguments
+ CommandOptions commandOptions = new CommandOptions(args, ServiceName);
+ if (commandOptions.ShouldExit)
+ {
+ return;
+ }
+
+ string logFilePath = "sqltoolsazure";
+ if (!string.IsNullOrWhiteSpace(commandOptions.LoggingDirectory))
+ {
+ logFilePath = Path.Combine(commandOptions.LoggingDirectory, logFilePath);
+ }
+
+ // turn on Verbose logging during early development
+ // we need to switch to Normal when preparing for public preview
+ Logger.Initialize(logFilePath: logFilePath, minimumLogLevel: LogLevel.Verbose, isEnabled: commandOptions.EnableLogging);
+ Logger.Write(LogLevel.Normal, "Starting SqlTools Azure Provider");
+
+ // set up the host details and profile paths
+ var hostDetails = new HostDetails(
+ name: "SqlTools Azure Provider",
+ profileId: "Microsoft.SqlTools.ResourceProvider",
+ version: new Version(1, 0));
+
+ SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails);
+ UtilityServiceHost serviceHost = ResourceProviderHostLoader.CreateAndStartServiceHost(sqlToolsContext);
+
+ serviceHost.WaitForExit();
+ }
+ catch (Exception e)
+ {
+ Logger.Write(LogLevel.Error, string.Format("An unhandled exception occurred: {0}", e));
+ Environment.Exit(1);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ResourceProvider/ResourceProviderHostLoader.cs b/src/Microsoft.SqlTools.ResourceProvider/ResourceProviderHostLoader.cs
new file mode 100644
index 00000000..24fc22ea
--- /dev/null
+++ b/src/Microsoft.SqlTools.ResourceProvider/ResourceProviderHostLoader.cs
@@ -0,0 +1,82 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.SqlTools.Extensibility;
+using Microsoft.SqlTools.Hosting;
+using Microsoft.SqlTools.Hosting.Protocol;
+using Microsoft.SqlTools.ServiceLayer.SqlContext;
+using Microsoft.SqlTools.Utility;
+
+namespace Microsoft.SqlTools.ResourceProvider
+{
+ ///
+ /// Provides support for starting up a service host. This is a common responsibility
+ /// for both the main service program and test driver that interacts with it
+ ///
+ public static class ResourceProviderHostLoader
+ {
+ private static object lockObject = new object();
+ private static bool isLoaded;
+
+ internal static UtilityServiceHost CreateAndStartServiceHost(SqlToolsContext sqlToolsContext)
+ {
+ UtilityServiceHost serviceHost = UtilityServiceHost.Instance;
+ lock (lockObject)
+ {
+ if (!isLoaded)
+ {
+ // Grab the instance of the service host
+ serviceHost.Initialize();
+
+ InitializeRequestHandlersAndServices(serviceHost, sqlToolsContext);
+
+ // Start the service only after all request handlers are setup. This is vital
+ // as otherwise the Initialize event can be lost - it's processed and discarded before the handler
+ // is hooked up to receive the message
+ serviceHost.Start().Wait();
+ isLoaded = true;
+ }
+ }
+ return serviceHost;
+ }
+
+ private static void InitializeRequestHandlersAndServices(UtilityServiceHost serviceHost, SqlToolsContext sqlToolsContext)
+ {
+ // Load extension provider, which currently finds all exports in current DLL. Can be changed to find based
+ // on directory or assembly list quite easily in the future
+ ExtensionServiceProvider serviceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider();
+ serviceProvider.RegisterSingleService(sqlToolsContext);
+ serviceProvider.RegisterSingleService(serviceHost);
+
+ // CredentialService.Instance.InitializeService(serviceHost);
+ // serviceProvider.RegisterSingleService(CredentialService.Instance);
+
+ InitializeHostedServices(serviceProvider, serviceHost);
+
+ serviceHost.InitializeRequestHandlers();
+ }
+
+ ///
+ /// Internal to support testing. Initializes instances in the service,
+ /// and registers them for their preferred service type
+ ///
+ internal static void InitializeHostedServices(RegisteredServiceProvider provider, IProtocolEndpoint host)
+ {
+ // Pre-register all services before initializing. This ensures that if one service wishes to reference
+ // another one during initialization, it will be able to safely do so
+ foreach (IHostedService service in provider.GetServices())
+ {
+ provider.RegisterSingleService(service.ServiceType, service);
+ }
+
+ foreach (IHostedService service in provider.GetServices())
+ {
+ // Initialize all hosted services, and register them in the service provider for their requested
+ // service type. This ensures that when searching for the ConnectionService you can get it without
+ // searching for an IHostedService of type ConnectionService
+ service.InitializeService(host);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
index 9178e6cb..14e38096 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
+++ b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
@@ -30,6 +30,7 @@
+
diff --git a/test/CodeCoverage/codecoverage.bat b/test/CodeCoverage/codecoverage.bat
index c0415805..c44db427 100644
--- a/test/CodeCoverage/codecoverage.bat
+++ b/test/CodeCoverage/codecoverage.bat
@@ -12,12 +12,18 @@ REM backup current CSPROJ files
COPY /Y %REPOROOT%\src\Microsoft.SqlTools.Credentials\Microsoft.SqlTools.Credentials.csproj %REPOROOT%\src\Microsoft.SqlTools.Credentials\Microsoft.SqlTools.Credentials.csproj.BAK
COPY /Y %REPOROOT%\src\Microsoft.SqlTools.Hosting\Microsoft.SqlTools.Hosting.csproj %REPOROOT%\src\Microsoft.SqlTools.Hosting\Microsoft.SqlTools.Hosting.csproj.BAK
COPY /Y %REPOROOT%\src\Microsoft.SqlTools.ServiceLayer\Microsoft.SqlTools.ServiceLayer.csproj %REPOROOT%\src\Microsoft.SqlTools.ServiceLayer\Microsoft.SqlTools.ServiceLayer.csproj.BAK
+COPY /Y %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider\Microsoft.SqlTools.ResourceProvider.csproj %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider\Microsoft.SqlTools.ResourceProvider.csproj.BAK
+COPY /Y %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.Core\Microsoft.SqlTools.ResourceProvider.Core.csproj %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.Core\Microsoft.SqlTools.ResourceProvider.Core.csproj.BAK
+COPY /Y %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.DefaultImpl\Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.DefaultImpl\Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj.BAK
REM switch PDB type to Full since that is required by OpenCover for now
REM we should remove this step on OpenCover supports portable PDB
cscript /nologo ReplaceText.vbs %REPOROOT%\src\Microsoft.SqlTools.Credentials\Microsoft.SqlTools.Credentials.csproj portable full
cscript /nologo ReplaceText.vbs %REPOROOT%\src\Microsoft.SqlTools.Hosting\Microsoft.SqlTools.Hosting.csproj portable full
cscript /nologo ReplaceText.vbs %REPOROOT%\src\Microsoft.SqlTools.ServiceLayer\Microsoft.SqlTools.ServiceLayer.csproj portable full
+cscript /nologo ReplaceText.vbs %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider\Microsoft.SqlTools.ResourceProvider.csproj portable full
+cscript /nologo ReplaceText.vbs %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.Core\Microsoft.SqlTools.ResourceProvider.Core.csproj portable full
+cscript /nologo ReplaceText.vbs %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.DefaultImpl\Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj portable full
REM rebuild the SqlToolsService project
dotnet restore %REPOROOT%\src\Microsoft.SqlTools.Credentials\Microsoft.SqlTools.Credentials.csproj
@@ -26,6 +32,12 @@ dotnet restore %REPOROOT%\src\Microsoft.SqlTools.Hosting\Microsoft.SqlTools.Host
dotnet build %REPOROOT%\src\Microsoft.SqlTools.Hosting\Microsoft.SqlTools.Hosting.csproj %DOTNETCONFIG%
dotnet restore %REPOROOT%\src\Microsoft.SqlTools.ServiceLayer\Microsoft.SqlTools.ServiceLayer.csproj
dotnet build %REPOROOT%\src\Microsoft.SqlTools.ServiceLayer\Microsoft.SqlTools.ServiceLayer.csproj %DOTNETCONFIG% -r win7-x64
+dotnet restore %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider\Microsoft.SqlTools.ResourceProvider.csproj
+dotnet build %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider\Microsoft.SqlTools.ResourceProvider.csproj %DOTNETCONFIG% -r win7-x64
+dotnet restore %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.Core\Microsoft.SqlTools.ResourceProvider.Core.csproj
+dotnet build %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.Core\Microsoft.SqlTools.ResourceProvider.Core.csproj %DOTNETCONFIG% -r win7-x64
+dotnet restore %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.DefaultImpl\Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj
+dotnet build %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.DefaultImpl\Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj %DOTNETCONFIG% -r win7-x64
REM run the tests through OpenCover and generate a report
dotnet restore %REPOROOT%\test\Microsoft.SqlTools.ServiceLayer.TestDriver\Microsoft.SqlTools.ServiceLayer.TestDriver.csproj
@@ -66,5 +78,11 @@ COPY /Y %REPOROOT%\src\Microsoft.SqlTools.Credentials\Microsoft.SqlTools.Credent
DEL %REPOROOT%\src\Microsoft.SqlTools.Credentials\Microsoft.SqlTools.Credentials.csproj.BAK
COPY /Y %REPOROOT%\src\Microsoft.SqlTools.Hosting\Microsoft.SqlTools.Hosting.csproj.BAK %REPOROOT%\src\Microsoft.SqlTools.Hosting\Microsoft.SqlTools.Hosting.csproj
DEL %REPOROOT%\src\Microsoft.SqlTools.Hosting\Microsoft.SqlTools.Hosting.csproj.BAK
+COPY /Y %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider\Microsoft.SqlTools.ResourceProvider.csproj.BAK %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider\Microsoft.SqlTools.ResourceProvider.csproj
+DEL %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider\Microsoft.SqlTools.ResourceProvider.csproj.BAK
+COPY /Y %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.Core\Microsoft.SqlTools.ResourceProvider.Core.csproj.BAK %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.Core\Microsoft.SqlTools.ResourceProvider.Core.csproj
+DEL %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.Core\Microsoft.SqlTools.ResourceProvider.Core.csproj.BAK
+COPY /Y %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.DefaultImpl\Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj.BAK %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.DefaultImpl\Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj
+DEL %REPOROOT%\src\Microsoft.SqlTools.ResourceProvider.DefaultImpl\Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj.BAK
EXIT
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj
index d7c35110..b20c3156 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj
@@ -16,19 +16,21 @@
-
- ../../bin/ref/Newtonsoft.Json.dll
-
-
- ../../bin/ref/Moq.dll
-
-
- ../../bin/ref/Castle.Core.dll
-
-
-
-
-
+
+ ../../bin/ref/Newtonsoft.Json.dll
+
+
+ ../../bin/ref/Moq.dll
+
+
+ ../../bin/ref/Castle.Core.dll
+
+
+
+
+
+
+
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureDatabaseDiscoveryProviderTest.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureDatabaseDiscoveryProviderTest.cs
new file mode 100644
index 00000000..3baef9c4
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureDatabaseDiscoveryProviderTest.cs
@@ -0,0 +1,204 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Fakes;
+using Xunit;
+
+namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure
+{
+ ///
+ /// Tests for AzureDatabaseDiscoveryProvider to verify getting azure databases using azure resource manager
+ ///
+ public class AzureDatabaseDiscoveryProviderTest
+ {
+ [Fact]
+ public async Task GetShouldReturnDatabasesSuccessfully()
+ {
+ string databaseName1 = "server/db1";
+ string databaseName2 = "db2";
+ string databaseName3 = "server/";
+ string databaseName4 = "/db4";
+ List databasesForSubscription = new List()
+ {
+ databaseName1,
+ databaseName2
+ };
+
+ Dictionary> subscriptionToDatabaseMap = new Dictionary>();
+ subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), databasesForSubscription);
+ subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), new List()
+ {
+ databaseName3,
+ databaseName4,
+ });
+
+ AzureDatabaseDiscoveryProvider databaseDiscoveryProvider = FakeDataFactory.CreateAzureDatabaseDiscoveryProvider(subscriptionToDatabaseMap);
+ ServiceResponse response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
+ List list = response.Data.ToList();
+ Assert.NotNull(list);
+ Assert.True(list.Any(x => x.Name == "db1" && x.ServerInstanceInfo.Name == "server"));
+ Assert.False(list.Any(x => x.Name == "db2" && x.ServerInstanceInfo.Name == ""));
+ Assert.True(list.Any(x => x.Name == "" && x.ServerInstanceInfo.Name == "server"));
+ Assert.False(list.Any(x => x.Name == "db4" && x.ServerInstanceInfo.Name == ""));
+ Assert.True(list.Count() == 2);
+ }
+
+ [Fact]
+ public async Task GetShouldReturnDatabasesEvenIfFailsForOneServer()
+ {
+ string databaseName1 = "server1/db1";
+ string databaseName2 = "server1/db2";
+ string databaseName3 = "error/db3";
+ string databaseName4 = "server2/db4";
+ List databasesForSubscription = new List()
+ {
+ databaseName1,
+ databaseName2
+ };
+
+ Dictionary> subscriptionToDatabaseMap = new Dictionary>();
+ subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), databasesForSubscription);
+ subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), new List()
+ {
+ databaseName3,
+ databaseName4,
+ });
+
+ AzureDatabaseDiscoveryProvider databaseDiscoveryProvider = FakeDataFactory.CreateAzureDatabaseDiscoveryProvider(subscriptionToDatabaseMap);
+ ServiceResponse response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
+ List list = response.Data.ToList();
+ Assert.NotNull(list);
+ Assert.True(list.Any(x => x.Name == "db1" && x.ServerInstanceInfo.Name == "server1"));
+ Assert.True(list.Any(x => x.Name == "db2" && x.ServerInstanceInfo.Name == "server1"));
+ Assert.True(list.Any(x => x.Name == "db4" && x.ServerInstanceInfo.Name == "server2"));
+ Assert.False(list.Any(x => x.Name == "db3" && x.ServerInstanceInfo.Name == "error"));
+ Assert.True(list.Count() == 3);
+ Assert.NotNull(response.Errors);
+ Assert.True(response.Errors.Count() == 1);
+ }
+
+ [Fact]
+ public async Task GetShouldReturnDatabasesFromCacheIfGetCalledTwice()
+ {
+ Dictionary> subscriptionToDatabaseMap = CreateSubscriptonMap(2);
+ AddDatabases(subscriptionToDatabaseMap, 3);
+
+ AzureDatabaseDiscoveryProvider databaseDiscoveryProvider = FakeDataFactory.CreateAzureDatabaseDiscoveryProvider(subscriptionToDatabaseMap);
+ ServiceResponse response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
+ List list = response.Data.ToList();
+ ValidateResult(subscriptionToDatabaseMap, list);
+
+ Dictionary> subscriptionToDatabaseMap2 = CopySubscriptonMap(subscriptionToDatabaseMap);
+ AddDatabases(subscriptionToDatabaseMap2, 5);
+ AzureTestContext testContext = new AzureTestContext(subscriptionToDatabaseMap2);
+ databaseDiscoveryProvider.AccountManager = testContext.AzureAccountManager;
+ databaseDiscoveryProvider.AzureResourceManager = testContext.AzureResourceManager;
+ response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
+ list = response.Data.ToList();
+ //the provider should get the databases from cache for the list should match the first created data
+ ValidateResult(subscriptionToDatabaseMap, list);
+ }
+
+ [Fact]
+ public async Task GetShouldReturnDatabasesFromServiceIfGetCalledTwiceButRefreshed()
+ {
+ Dictionary> subscriptionToDatabaseMap = CreateSubscriptonMap(2);
+ AddDatabases(subscriptionToDatabaseMap, 3);
+
+ AzureDatabaseDiscoveryProvider databaseDiscoveryProvider = FakeDataFactory.CreateAzureDatabaseDiscoveryProvider(subscriptionToDatabaseMap);
+ ServiceResponse response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
+ List list = response.Data.ToList();
+ ValidateResult(subscriptionToDatabaseMap, list);
+
+ Dictionary> subscriptionToDatabaseMap2 = CopySubscriptonMap(subscriptionToDatabaseMap);
+ AddDatabases(subscriptionToDatabaseMap2, 5);
+ AzureTestContext testContext = new AzureTestContext(subscriptionToDatabaseMap2);
+ databaseDiscoveryProvider.AccountManager = testContext.AzureAccountManager;
+ databaseDiscoveryProvider.AzureResourceManager = testContext.AzureResourceManager;
+ await databaseDiscoveryProvider.ClearCacheAsync();
+ response = await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
+ list = response.Data.ToList();
+ //the provider should get the databases from cache for the list should match the first created data
+ ValidateResult(subscriptionToDatabaseMap2, list);
+ }
+
+ private void ValidateResult(Dictionary> subscriptionToDatabaseMap, List result)
+ {
+ Assert.NotNull(result);
+ int numberOfDatabases = 0;
+ foreach (KeyValuePair> item in subscriptionToDatabaseMap)
+ {
+ numberOfDatabases += item.Value.Count;
+ foreach (string databaseFullName in item.Value)
+ {
+ string serverName = AzureTestContext.GetServerName(databaseFullName);
+ string databaseName = databaseFullName.Replace(serverName + @"/", "");
+ Assert.True(result.Any(x => x.Name == databaseName && x.ServerInstanceInfo.Name == serverName));
+ }
+ }
+ Assert.True(result.Count() == numberOfDatabases);
+ }
+
+ private void AddDatabases(Dictionary> subscriptionToDatabaseMap, int numberOfDatabases)
+ {
+ foreach (string key in subscriptionToDatabaseMap.Keys.ToList())
+ {
+ List databases = CreateDatabases(numberOfDatabases);
+ subscriptionToDatabaseMap[key] = databases;
+ }
+ }
+
+ private List CreateDatabases(int numberOfDatabases)
+ {
+ List databases = new List();
+ for (int j = 0; j < numberOfDatabases; j++)
+ {
+ databases.Add(string.Format(CultureInfo.InvariantCulture, @"{0}/{1}", Guid.NewGuid().ToString(), Guid.NewGuid().ToString()));
+ }
+ return databases;
+ }
+
+ private Dictionary> CreateSubscriptonMap(int numberOfSubscriptions)
+ {
+ Dictionary> subscriptionToDatabaseMap = new Dictionary>();
+ for (int i = 0; i < numberOfSubscriptions; i++)
+ {
+ string id = Guid.NewGuid().ToString();
+ subscriptionToDatabaseMap.Add(id, null);
+ }
+ return subscriptionToDatabaseMap;
+ }
+
+ private Dictionary> CopySubscriptonMap(Dictionary> subscriptionToDatabaseMap)
+ {
+ Dictionary> subscriptionToDatabaseMapCopy = new Dictionary>();
+ foreach (KeyValuePair> pair in subscriptionToDatabaseMap)
+ {
+ subscriptionToDatabaseMapCopy.Add(pair.Key, null);
+ }
+ return subscriptionToDatabaseMapCopy;
+ }
+
+ [Fact]
+ public async Task GetShouldReturnEmptyGivenNotSubscriptionFound()
+ {
+ Dictionary> subscriptionToDatabaseMap = new Dictionary>();
+
+ AzureDatabaseDiscoveryProvider databaseDiscoveryProvider =
+ FakeDataFactory.CreateAzureDatabaseDiscoveryProvider(subscriptionToDatabaseMap);
+ ServiceResponse response =
+ await databaseDiscoveryProvider.GetDatabaseInstancesAsync(serverName: null, cancellationToken: new CancellationToken());
+ Assert.NotNull(response);
+ Assert.NotNull(response.Data);
+ Assert.False(response.Data.Any());
+ }
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureSqlServerDiscoveryProviderTest.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureSqlServerDiscoveryProviderTest.cs
new file mode 100644
index 00000000..820041b4
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureSqlServerDiscoveryProviderTest.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Fakes;
+using Xunit;
+
+namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure
+{
+ ///
+ /// Tests for AzureServerDiscoveryProvider to verify getting azure servers using azure resource manager
+ ///
+ public class AzureSqlServerDiscoveryProviderTest
+ {
+ [Fact]
+ public async Task GetShouldReturnServersSuccessfully()
+ {
+ string serverName = "server";
+ List serversForSubscription = new List()
+ {
+ Guid.NewGuid().ToString(),
+ serverName
+ };
+
+ Dictionary> subscriptionToDatabaseMap = new Dictionary>();
+ subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), serversForSubscription);
+ subscriptionToDatabaseMap.Add(Guid.NewGuid().ToString(), new List()
+ {
+ Guid.NewGuid().ToString(),
+ Guid.NewGuid().ToString(),
+ });
+
+ AzureSqlServerDiscoveryProvider discoveryProvider =
+ FakeDataFactory.CreateAzureServerDiscoveryProvider(subscriptionToDatabaseMap);
+ ServiceResponse response = await discoveryProvider.GetServerInstancesAsync();
+ IEnumerable servers = response.Data;
+ Assert.NotNull(servers);
+ Assert.True(servers.Any(x => x.Name == serverName));
+ Assert.True(servers.Count() == 4);
+ }
+
+ [Fact]
+ public async Task GetShouldReturnEmptyGivenNotSubscriptionFound()
+ {
+ Dictionary> subscriptionToDatabaseMap = new Dictionary>();
+
+ AzureSqlServerDiscoveryProvider discoveryProvider =
+ FakeDataFactory.CreateAzureServerDiscoveryProvider(subscriptionToDatabaseMap);
+ ServiceResponse response = await discoveryProvider.GetServerInstancesAsync();
+ IEnumerable servers = response.Data;
+ Assert.NotNull(servers);
+ Assert.False(servers.Any());
+ }
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureSubscriptionContextTest.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureSubscriptionContextTest.cs
new file mode 100644
index 00000000..12dc138d
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureSubscriptionContextTest.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Microsoft.SqlTools.ResourceProvider.DefaultImpl;
+using Xunit;
+
+namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure
+{
+ ///
+ /// Tests for AzureSubscriptionContextWrapper to verify the wrapper on azure subscription class
+ ///
+ public class AzureSubscriptionContextTest
+ {
+ [Fact]
+ public void SubscriptionNameShouldReturnNullGivenNullSubscription()
+ {
+ AzureSubscriptionContext subscriptionContext = new AzureSubscriptionContext(null);
+ Assert.True(subscriptionContext.SubscriptionName == String.Empty);
+ Assert.True(subscriptionContext.Subscription == null);
+ }
+
+ [Fact]
+ public void SubscriptionNameShouldReturnCorrectValueGivenValidSubscription()
+ {
+ string name = Guid.NewGuid().ToString();
+
+ AzureSubscriptionContext subscriptionContext = new AzureSubscriptionContext(new AzureSubscriptionIdentifier(null, name, null));
+ Assert.True(subscriptionContext.SubscriptionName == name);
+ Assert.True(subscriptionContext.Subscription != null);
+ }
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureTestContext.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureTestContext.cs
new file mode 100644
index 00000000..91eea47e
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureTestContext.cs
@@ -0,0 +1,121 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Azure.Management.Sql.Models;
+using Microsoft.Rest;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+using Microsoft.SqlTools.ResourceProvider.DefaultImpl;
+using Moq;
+
+namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure
+{
+ ///
+ /// A container to create test data and mock classes to test azure services and providers
+ ///
+ internal class AzureTestContext
+ {
+ public AzureTestContext(Dictionary> subscriptionToDatabaseMap)
+ {
+ AzureAccountManagerMock = new Mock();
+ List accountSubscriptions = new List
+ ();
+ AzureResourceManagerMock = new Mock();
+
+ foreach (string subscriptionName in subscriptionToDatabaseMap.Keys)
+ {
+ var azureAccount = new AzureUserAccount();
+ AzureSubscriptionIdentifier subId = new AzureSubscriptionIdentifier(azureAccount, subscriptionName, null);
+ var subscription = new AzureUserAccountSubscriptionContext(subId, new TokenCredentials("dummy"));
+ accountSubscriptions.Add(subscription);
+
+ var sessionMock = new Mock();
+ IAzureResourceManagementSession session = sessionMock.Object;
+ sessionMock.Setup(x => x.SubscriptionContext).Returns(subscription);
+ AzureResourceManagerMock.Setup(x => x.CreateSessionAsync(subscription)).Returns(Task.FromResult(session));
+ MockServersAndDatabases(subscriptionToDatabaseMap[subscriptionName], session);
+ }
+ AzureAccountManagerMock.Setup(x => x.GetSelectedSubscriptionsAsync()).Returns
+ (Task.FromResult(accountSubscriptions as IEnumerable));
+ }
+
+ private void MockServersAndDatabases(List resourceNames, IAzureResourceManagementSession session)
+ {
+ IEnumerable azureResources = resourceNames.Select(
+ x => new AzureResourceWrapper(new TrackedResource(Guid.NewGuid().ToString(), "id", x, "type")) { ResourceGroupName = Guid.NewGuid().ToString()}
+ ).ToList();
+
+ List servers = new List();
+ foreach (var azureResourceWrapper in azureResources.ToList())
+ {
+ var serverName = GetServerName(azureResourceWrapper.Name);
+ if (string.IsNullOrEmpty(serverName) || servers.Any(x => x.Name == serverName))
+ {
+ continue;
+ }
+
+ var databases = azureResources.Where(x => x.Name.StartsWith(serverName + "/"));
+ if (serverName.Equals("error", StringComparison.OrdinalIgnoreCase))
+ {
+ AzureResourceManagerMock.Setup(x => x.GetAzureDatabasesAsync(session, azureResourceWrapper.ResourceGroupName, serverName))
+ .Throws(new ApplicationException(serverName));
+ }
+ else
+ {
+ AzureResourceManagerMock.Setup(x => x.GetAzureDatabasesAsync(session, azureResourceWrapper.ResourceGroupName, serverName))
+ .Returns(Task.FromResult(databases));
+ }
+
+ Server azureSqlServer = new Server(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), serverName, null, null, null, null, null, null, null, null, fullyQualifiedDomainName: serverName + ".database.windows.net");
+ servers.Add(new SqlAzureResource(azureSqlServer)
+ {
+ ResourceGroupName = azureResourceWrapper.ResourceGroupName
+ });
+ }
+ AzureResourceManagerMock.Setup(x => x.GetSqlServerAzureResourcesAsync(session))
+ .Returns(Task.FromResult(servers as IEnumerable));
+ }
+
+ internal static string GetServerName(string name)
+ {
+ string azureResourceName = name;
+ int separatorIndex = azureResourceName.IndexOf("/", StringComparison.OrdinalIgnoreCase);
+ if (separatorIndex >= 0)
+ {
+ return azureResourceName.Substring(0, separatorIndex);
+ }
+ else
+ {
+ return azureResourceName;
+ }
+ }
+
+ public Mock AzureAccountManagerMock
+ {
+ get;
+ set;
+ }
+
+ public IAzureAuthenticationManager AzureAccountManager
+ {
+ get { return AzureAccountManagerMock.Object; }
+ }
+
+ public IAzureResourceManager AzureResourceManager
+ {
+ get { return AzureResourceManagerMock.Object; }
+ }
+
+ public Mock AzureResourceManagerMock
+ {
+ get;
+ set;
+ }
+
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/FakeAzureServerDiscoveryProvider.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/FakeAzureServerDiscoveryProvider.cs
new file mode 100644
index 00000000..fce524e0
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/FakeAzureServerDiscoveryProvider.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+
+namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure
+{
+ ///
+ /// A fake server discovery class
+ ///
+ [Exportable(ServerTypes.SqlServer, Categories.Azure
+ , typeof(IServerDiscoveryProvider),
+ "Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure.FakeAzureServerDiscoveryProvider", 1)]
+ public class FakeAzureServerDiscoveryProvider : ExportableBase, IServerDiscoveryProvider, ISecureService
+ {
+ public FakeAzureServerDiscoveryProvider()
+ {
+ Metadata = new ExportableMetadata(ServerTypes.SqlServer, Categories.Azure,
+ "Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure.FakeAzureServerDiscoveryProvider", 1);
+ }
+ public Task> GetServerInstancesAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAccountManager AccountManager
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// This should always return null otherwise there's going to be a infinite loop
+ ///
+ public IServerDiscoveryProvider ServerDiscoveryProvider
+ {
+ get
+ {
+ return GetService();
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/DependencyManagerTest.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/DependencyManagerTest.cs
new file mode 100644
index 00000000..e7f71304
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/DependencyManagerTest.cs
@@ -0,0 +1,295 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+// TODO Ideally would reenable these but using ExtensionServiceProvider
+
+//using System;
+//using System.Collections.Generic;
+//using System.Linq;
+//using System.Threading.Tasks;
+//using Microsoft.SqlTools.ResourceProvider.Core;
+//using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+//using Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Fakes;
+//using Moq;
+//using Xunit;
+
+//namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider
+//{
+// ///
+// /// Tests for DependencyManager to verify the services and providers can be created given different types of catalogs
+// ///
+// public class DependencyManagerTest
+// {
+// private ExtensionProperties _serviceProperties;
+// private DependencyManager _dependencyManager;
+// private IList> _providers;
+
+// private readonly List _localSqlServers = new List()
+// {
+// new ServerInstanceInfo(),
+// new ServerInstanceInfo(),
+// };
+
+// public DependencyManagerTest()
+// {
+// var provider1 = new Mock();
+// var provider2 = new Mock();
+// provider1.Setup(x => x.GetServerInstancesAsync()).Returns(Task.FromResult(new ServiceResponse(_localSqlServers.AsEnumerable())));
+// _providers = new List>()
+// {
+// new Lazy(() => provider1.Object,
+// new ExportableAttribute("SqlServer", "Local", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString())),
+// new Lazy(() => provider2.Object,
+// new ExportableAttribute("SqlServer", "Network", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString())),
+// };
+
+// _serviceProperties = FakeDataFactory.CreateServiceProperties(_providers);
+// _dependencyManager = new DependencyManager(_serviceProperties);
+// }
+
+// [Fact]
+// public void GetShouldReturnProvidersFromTheCatalog()
+// {
+// IEnumerable> providers =
+// _dependencyManager.GetServiceDescriptors();
+// Assert.NotNull(providers);
+// }
+
+// [Fact]
+// public void GetShouldReturnEmptyListGivenInvalidCategory()
+// {
+// Assert.False(_dependencyManager.GetServiceDescriptors(new ServerDefinition(null, "invalid category")).Any());
+// }
+
+// [Fact]
+// public void GetShouldReturnEmptyListGivenInvalidServerType()
+// {
+// Assert.False(_dependencyManager.GetServiceDescriptors(new ServerDefinition("invalid server type", null)).Any());
+// }
+
+// [Fact]
+// public void GetShouldReturnAllProvidersGivenNoParameter()
+// {
+// IEnumerable> providers =
+// _dependencyManager.GetServiceDescriptors();
+// Assert.NotNull(providers);
+// Assert.True(providers.Count() == _providers.Count());
+// }
+
+// [Fact]
+// public void GetShouldReturnProvidersGivenServerType()
+// {
+// var serverType = "sqlServer";
+// IEnumerable> providers =
+// _dependencyManager.GetServiceDescriptors(new ServerDefinition(serverType, null));
+// Assert.NotNull(providers);
+// Assert.True(providers.Any());
+// Assert.True(providers.Count() == _providers.Count(x => x.Metadata.ServerType.Equals(serverType, StringComparison.OrdinalIgnoreCase)));
+// }
+
+// [Fact]
+// public void GetShouldReturnProvidersGivenCategory()
+// {
+// IEnumerable> providers =
+// _dependencyManager.GetServiceDescriptors(new ServerDefinition(null, "local"));
+// Assert.NotNull(providers);
+// Assert.True(providers.Count() == 1);
+// }
+
+// [Fact]
+// public void GetShouldReturnProviderForEmptyCategoryGivenEmptyCategory()
+// {
+// // Given choice of 2 providers, one with empty category and other with specified one
+
+// IServerDiscoveryProvider provider1 = new Mock();
+// IServerDiscoveryProvider provider2 = new Mock();
+// provider1.Setup(x => x.GetServerInstancesAsync()).Returns(Task.FromResult(new ServiceResponse(_localSqlServers.AsEnumerable())));
+// var providers = new List>()
+// {
+// new Lazy(() => provider1,
+// new ExportableAttribute("SqlServer", "Azure", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString())),
+// new Lazy(() => provider2,
+// new ExportableAttribute("SqlServer", "", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString())),
+// };
+
+// var serviceProperties = FakeDataFactory.CreateServiceProperties(providers);
+// var dependencyManager = new DependencyManager(serviceProperties);
+
+// // When getting the correct descriptor
+
+// IEnumerable> foundProviders =
+// dependencyManager.GetServiceDescriptors(new ServerDefinition("SqlServer", ""));
+
+// // Then expect only the provider with the empty categorty to be returned
+// Assert.NotNull(foundProviders);
+// Assert.True(foundProviders.Count() == 1);
+// }
+
+// [Fact]
+// public void GetShouldReturnProviderGivenServerTypeAndLocationWithValidProvider()
+// {
+// IEnumerable> providers =
+// _dependencyManager.GetServiceDescriptors(new ServerDefinition("SqlServer", "local"));
+// Assert.NotNull(providers);
+// Assert.True(providers.Count() == 1);
+// }
+
+// [Fact]
+
+// public void GetShouldReturnTheServiceWithTheHighestPriorityIdMultipleFound()
+// {
+// IServerDiscoveryProvider expectedProvider = new Mock();
+// List> providers = new List>()
+// {
+// new Lazy(() => new Mock(),
+// new ExportableAttribute("SqlServer", "Local", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString())),
+// new Lazy(() => new Mock(),
+// new ExportableAttribute("SqlServer", "Network", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString(), 1)),
+// new Lazy(() => expectedProvider,
+// new ExportableAttribute("SqlServer", "Network", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString(), 2))
+// };
+
+// ExtensionProperties serviceProperties = FakeDataFactory.CreateServiceProperties(providers);
+// DependencyManager dependencyManager = new DependencyManager(serviceProperties);
+
+// IEnumerable> descriptors =
+// dependencyManager.GetServiceDescriptors();
+// Assert.NotNull(descriptors);
+
+// ExportableDescriptor descriptor = descriptors.FindMatchedDescriptor(new ServerDefinition("SqlServer", "network"));
+// Assert.NotNull(descriptor);
+// Assert.True(descriptor.Exportable == expectedProvider);
+// }
+
+// [Fact]
+// public void GetShouldReturnTheServiceEvenIfTheServerTypeNotSet()
+// {
+// IServerDiscoveryProvider expectedProvider = new Mock();
+// List> providers = new List>()
+// {
+// new Lazy(() => new Mock(),
+// new ExportableAttribute("SqlServer", "", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString())),
+// new Lazy(() => new Mock(),
+// new ExportableAttribute("SqlServer", "Network", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString(), 1)),
+// new Lazy(() => expectedProvider,
+// new ExportableAttribute("", "Network", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString(), 2))
+// };
+
+// ExtensionProperties serviceProperties = FakeDataFactory.CreateServiceProperties(providers);
+// DependencyManager dependencyManager = new DependencyManager(serviceProperties);
+
+// IEnumerable> descriptors =
+// dependencyManager.GetServiceDescriptors();
+// Assert.NotNull(descriptors);
+
+// ExportableDescriptor descriptor = descriptors.FindMatchedDescriptor(new ServerDefinition("", "network"));
+// Assert.NotNull(descriptor);
+// Assert.True(descriptor.Exportable == expectedProvider);
+// }
+
+// [Fact]
+// public void GetShouldReturnTheServiceThatMatchedExactlyIfServerTypeSpecified()
+// {
+// IServerDiscoveryProvider expectedProvider = new Mock();
+// List> providers = new List>()
+// {
+// new Lazy(() => new Mock(),
+// new ExportableAttribute("SqlServer", "", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString())),
+// new Lazy(() => expectedProvider,
+// new ExportableAttribute("SqlServer", "Network", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString(), 1)),
+// new Lazy(() => new Mock(),
+// new ExportableAttribute("", "Network", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString(), 2))
+// };
+
+// ExtensionProperties serviceProperties = FakeDataFactory.CreateServiceProperties(providers);
+// DependencyManager dependencyManager = new DependencyManager(serviceProperties);
+
+// IEnumerable> descriptors =
+// dependencyManager.GetServiceDescriptors();
+// Assert.NotNull(descriptors);
+
+// ExportableDescriptor descriptor = descriptors.FindMatchedDescriptor(new ServerDefinition("SqlServer", "network"));
+// Assert.NotNull(descriptor);
+// Assert.True(descriptor.Exportable == expectedProvider);
+// }
+
+// [Fact]
+// public void GetShouldReturnTheServiceThatMatchedExactlyIfCategorySpecified()
+// {
+// IServerDiscoveryProvider expectedProvider = new Mock();
+// List> providers = new List>()
+// {
+// new Lazy(() => new Mock(),
+// new ExportableAttribute("SqlServer", "", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString())),
+// new Lazy(() => expectedProvider,
+// new ExportableAttribute("SqlServer", "Network", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString(), 1)),
+// new Lazy(() => new Mock(),
+// new ExportableAttribute("", "Network", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString(), 2))
+// };
+
+// ExtensionProperties serviceProperties = FakeDataFactory.CreateServiceProperties(providers);
+// DependencyManager dependencyManager = new DependencyManager(serviceProperties);
+
+// IEnumerable> descriptors =
+// dependencyManager.GetServiceDescriptors();
+// Assert.NotNull(descriptors);
+
+// ExportableDescriptor descriptor = descriptors.FindMatchedDescriptor(new ServerDefinition("SqlServer", "network"));
+// Assert.NotNull(descriptor);
+// Assert.True(descriptor.Exportable == expectedProvider);
+// }
+
+// [Fact]
+
+// public void GetShouldReturnTheServiceEvenIfTheCategoryNotSet()
+// {
+// IServerDiscoveryProvider expectedProvider = new Mock();
+// List> providers = new List>()
+// {
+// new Lazy(() => new Mock(),
+// new ExportableAttribute("SqlServer", "Local", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString())),
+// new Lazy(() => new Mock(),
+// new ExportableAttribute("SqlServer", "Network", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString(), 1)),
+// new Lazy(() => expectedProvider,
+// new ExportableAttribute("SqlServer", "", typeof(IServerDiscoveryProvider), Guid.NewGuid().ToString(), 2))
+// };
+
+// ExtensionProperties serviceProperties = FakeDataFactory.CreateServiceProperties(providers);
+// DependencyManager dependencyManager = new DependencyManager(serviceProperties);
+
+// IEnumerable> descriptors =
+// dependencyManager.GetServiceDescriptors();
+// Assert.NotNull(descriptors);
+
+// ExportableDescriptor descriptor = descriptors.FindMatchedDescriptor(new ServerDefinition("SqlServer", ""));
+// Assert.NotNull(descriptor);
+// Assert.True(descriptor.Exportable == expectedProvider);
+// }
+
+// [Fact]
+// public void GetShouldReturnProvidersGivenServerTypeAndMoreThanOneLocation()
+// {
+// var serverType = "sqlServer";
+// IEnumerable> providers =
+// _dependencyManager.GetServiceDescriptors(new ServerDefinition(serverType, null));
+// Assert.NotNull(providers);
+// Assert.True(providers.Count() == _providers.Count(x => x.Metadata.ServerType.Equals(serverType, StringComparison.OrdinalIgnoreCase)));
+// }
+
+// [Fact]
+// public async Task ProviderCreatedByFactoryShouldReturnServersSuccessfully()
+// {
+// List expectedServers = _localSqlServers;
+// IEnumerable> providers =
+// _dependencyManager.GetServiceDescriptors(new ServerDefinition("SqlServer",
+// "local"));
+// ExportableDescriptor provider = providers.First();
+// Assert.NotNull(provider);
+// ServiceResponse result = await provider.Exportable.GetServerInstancesAsync();
+// var servers = result.Data;
+// Assert.NotNull(servers);
+// Assert.Equal(expectedServers, servers);
+// }
+// }
+//}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/ExceptionUtilTest.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/ExceptionUtilTest.cs
new file mode 100644
index 00000000..f0896e31
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/ExceptionUtilTest.cs
@@ -0,0 +1,72 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Data.Common;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Moq;
+using Xunit;
+
+namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider
+{
+ ///
+ /// Tests for ExceptionUtil to verify the helper and extension methods
+ ///
+ public class ExceptionUtilTest
+ {
+ [Fact]
+ public void IsSqlExceptionShouldReturnFalseGivenNullException()
+ {
+ Exception exception = null;
+ bool expected = false;
+ bool actual = exception.IsDbException();
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void IsSqlExceptionShouldReturnFalseGivenNonSqlException()
+ {
+ Exception exception = new ApplicationException();
+ bool expected = false;
+ bool actual = exception.IsDbException();
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void IsSqlExceptionShouldReturnFalseGivenNonSqlExceptionWithInternalException()
+ {
+ Exception exception = new ApplicationException("Exception message", new ServiceFailedException());
+ bool expected = false;
+ bool actual = exception.IsDbException();
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void IsSqlExceptionShouldReturnTrueGivenSqlException()
+ {
+ Exception exception = CreateDbException();
+ Assert.NotNull(exception);
+
+ bool expected = true;
+ bool actual = exception.IsDbException();
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void IsSqlExceptionShouldReturnTrueGivenExceptionWithInnerSqlException()
+ {
+ Exception exception = new ApplicationException("", CreateDbException());
+ Assert.NotNull(exception);
+
+ bool expected = true;
+ bool actual = exception.IsDbException();
+ Assert.Equal(expected, actual);
+ }
+
+ private Exception CreateDbException()
+ {
+ return new Mock().Object;
+ }
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Fakes/FakeAccountManager.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Fakes/FakeAccountManager.cs
new file mode 100644
index 00000000..702033c9
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Fakes/FakeAccountManager.cs
@@ -0,0 +1,83 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.Extensibility;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+
+namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Fakes
+{
+ [Exportable(ServerTypes.SqlServer, Categories.Azure,
+ typeof(IAccountManager),
+ "Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Fakes.FakeAccountManager", 1)]
+ public class FakeAccountManager : IAccountManager
+ {
+ public FakeAccountManager(IExportableMetadata metadata)
+ {
+ Metadata = metadata;
+ }
+
+ public ITrace Trace { get; set; }
+ public Task GetUserNeedsReauthenticationAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task AuthenticateAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public bool IsCachExpired { get; private set; }
+ public bool SessionIsCached { get; private set; }
+ public void ResetSession()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task AddUserAccountAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task SetCurrentAccountAsync(object account)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task SetCurrentAccountFromLoginDialogAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetCurrentAccountAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public bool HasLoginDialog { get; }
+
+ public event EventHandler CurrentAccountChanged;
+
+ public IUserAccount SetCurrentAccount(object account)
+ {
+ if (CurrentAccountChanged != null)
+ {
+ CurrentAccountChanged(this, null);
+ }
+ return null;
+ }
+
+ public void SetServiceProvider(IMultiServiceProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IExportableMetadata Metadata { get; set; }
+ public ExportableStatus Status { get; }
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Fakes/FakeAccountManager2.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Fakes/FakeAccountManager2.cs
new file mode 100644
index 00000000..e652f984
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Fakes/FakeAccountManager2.cs
@@ -0,0 +1,81 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.Extensibility;
+using Microsoft.SqlTools.ResourceProvider.Core.Authentication;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+
+namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Fakes
+{
+ [Exportable("SqlServer", "Network",
+ typeof(IAccountManager), "Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Fakes.FakeAccountManager2", 2)]
+ public class FakeAccountManager2 : IAccountManager
+ {
+ public FakeAccountManager2(IExportableMetadata metadata)
+ {
+ Metadata = metadata;
+ }
+
+ public ITrace Trace { get; set; }
+ public Task GetUserNeedsReauthenticationAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task AuthenticateAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public bool IsCachExpired { get; private set; }
+ public bool SessionIsCached { get; private set; }
+ public void ResetSession()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task AddUserAccountAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task SetCurrentAccountAsync(object account)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task SetCurrentAccountFromLoginDialogAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetCurrentAccountAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public bool HasLoginDialog { get; }
+
+ public event EventHandler CurrentAccountChanged;
+
+ public IUserAccount SetCurrentAccount(object account)
+ {
+ if (CurrentAccountChanged != null)
+ {
+ CurrentAccountChanged(this, null);
+ }
+ return null;
+ }
+
+ public void SetServiceProvider(IMultiServiceProvider provider)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IExportableMetadata Metadata { get; set; }
+ public ExportableStatus Status { get; }
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Fakes/FakeDataFactory.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Fakes/FakeDataFactory.cs
new file mode 100644
index 00000000..2c89f579
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Fakes/FakeDataFactory.cs
@@ -0,0 +1,193 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.SqlTools.ResourceProvider.Core;
+using Microsoft.SqlTools.ResourceProvider.Core.Extensibility;
+using Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure;
+using Moq;
+
+namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Fakes
+{
+ internal static class FakeDataFactory
+ {
+ //public static ExtensionProperties CreateServiceProperties(IList> exports)
+ //{
+ // FakeExportProvider fakeProvider = new FakeExportProvider(f => ((FakeInstanceExportDefinition)f).Instance);
+ // foreach (var export in exports)
+ // {
+ // var metadata = new Dictionary()
+ // {
+ // {"ServerType", export.Metadata.ServerType},
+ // {"Category", export.Metadata.Category},
+ // {"Id", export.Metadata.Id },
+ // {"Priority", export.Metadata.Priority}
+ // };
+
+ // var definition = new FakeInstanceExportDefinition(typeof(IServerDiscoveryProvider), export.Value, metadata);
+ // fakeProvider.AddExportDefinitions(definition);
+ // }
+ // var trace = new Mock();
+ // trace.Setup(x => x.TraceEvent(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny