mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -05:00
Backup/Restore Managed Instance (#1428)
* Enabled backup to and restore from URL * Created RPC, but when process tries to load Microsoft.Azure.Storage.Blob.dll, it crashes * Added create shared access token * Code refactor * Minor changes * Changed RPC path * Moved createSas RPC to the newly created BlobService, fixed PR comments * Added sas expiration date parameter to the RPC * Added copyright headers * Removed ConnectionInstance property from BlobService * Removed unhelpful comment * Removed unused using statements * Changed copy/paste comments * Disposable objects fix * Small formatting fix * Changed backup to/restore from url supported device types * Added backup to url integration test * Created restore integration test. Test are now getting azure blob params from env variables instead of file. * Culture invariant epiration date param, fixed comment, and typo * Updated headers * PR comments fix * Changed supported device type logic * string localization fix * String formatting fix * build failure fix * Typo * Updated supported restore device types
This commit is contained in:
committed by
GitHub
parent
35e1782a3f
commit
881c335cdf
81
src/Microsoft.SqlTools.ServiceLayer/AzureBlob/BlobService.cs
Normal file
81
src/Microsoft.SqlTools.ServiceLayer/AzureBlob/BlobService.cs
Normal file
@@ -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.Data.SqlClient;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.AzureBlob.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.AzureBlob
|
||||
{
|
||||
public class BlobService
|
||||
{
|
||||
private static readonly Lazy<BlobService> instance = new Lazy<BlobService>(() => new BlobService());
|
||||
|
||||
/// <summary>
|
||||
/// Default, parameterless constructor.
|
||||
/// </summary>
|
||||
internal BlobService()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton instance object
|
||||
/// </summary>
|
||||
public static BlobService Instance
|
||||
{
|
||||
get { return instance.Value; }
|
||||
}
|
||||
|
||||
public void InitializeService(IProtocolEndpoint serviceHost)
|
||||
{
|
||||
serviceHost.SetRequestHandler(CreateSasRequest.Type, HandleCreateSasRequest);
|
||||
}
|
||||
|
||||
internal async Task HandleCreateSasRequest(
|
||||
CreateSasParams optionsParams,
|
||||
RequestContext<CreateSasResponse> requestContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
ConnectionInfo connInfo;
|
||||
ConnectionService.Instance.TryFindConnection(
|
||||
optionsParams.OwnerUri,
|
||||
out connInfo);
|
||||
var response = new CreateSasResponse();
|
||||
|
||||
if (connInfo == null)
|
||||
{
|
||||
await requestContext.SendError(SR.ConnectionServiceListDbErrorNotConnected(optionsParams.OwnerUri));
|
||||
return;
|
||||
}
|
||||
if (connInfo.IsCloud)
|
||||
{
|
||||
await requestContext.SendError(SR.NotSupportedCloudCreateSas);
|
||||
return;
|
||||
}
|
||||
using (SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo, "AzureBlob"))
|
||||
{
|
||||
// Connection gets disconnected when backup is done
|
||||
ServerConnection serverConnection = new ServerConnection(sqlConn);
|
||||
Server sqlServer = new Server(serverConnection);
|
||||
|
||||
SharedAccessSignatureCreator sharedAccessSignatureCreator = new SharedAccessSignatureCreator(sqlServer);
|
||||
string sharedAccessSignature = sharedAccessSignatureCreator.CreateSqlSASCredential(optionsParams.StorageAccountName, optionsParams.BlobContainerKey, optionsParams.BlobContainerUri, optionsParams.ExpirationDate);
|
||||
response.SharedAccessSignature = sharedAccessSignature;
|
||||
await requestContext.SendResult(response);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await requestContext.SendError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// 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.ServiceLayer.AzureBlob.Contracts
|
||||
{
|
||||
public static class BlobSasResource
|
||||
{
|
||||
/*
|
||||
* Specify "c" if the shared resource is a blob container.
|
||||
* This grants access to the content and metadata of any blob in the container,
|
||||
* and to the list of blobs in the container.
|
||||
*/
|
||||
public const string BLOB_CONTAINER = "c";
|
||||
|
||||
/*
|
||||
* Specify "b" if the shared resource is a blob.
|
||||
* This grants access to the content and metadata of the blob.
|
||||
*/
|
||||
public const string BLOB = "b";
|
||||
|
||||
/*
|
||||
* Beginning in version 2018-11-09, specify "bs" if the shared resource is a blob snapshot.
|
||||
* This grants access to the content and metadata of the specific snapshot,
|
||||
* but not the corresponding root blob.
|
||||
*/
|
||||
public const string BLOB_SNAPSHOT = "bs";
|
||||
|
||||
/*
|
||||
* Beginning in version 2019-12-12, specify "bv" if the shared resource is a blob version.
|
||||
* This grants access to the content and metadata of the specific version,
|
||||
* but not the corresponding root blob.
|
||||
*/
|
||||
public const string BLOB_VERSION = "bv";
|
||||
}
|
||||
}
|
||||
@@ -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 Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.AzureBlob.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters passed for creating shared access signature
|
||||
/// </summary>
|
||||
public class CreateSasParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Connection URI
|
||||
/// </summary>
|
||||
public string OwnerUri { get; set; }
|
||||
/// <summary>
|
||||
/// Blob container URI
|
||||
/// </summary>
|
||||
public string BlobContainerUri { get; set; }
|
||||
/// <summary>
|
||||
/// Blob container key
|
||||
/// </summary>
|
||||
public string BlobContainerKey { get; set; }
|
||||
/// <summary>
|
||||
/// Storage account name
|
||||
/// </summary>
|
||||
public string StorageAccountName { get; set; }
|
||||
/// <summary>
|
||||
/// Shared access signature expiration date
|
||||
/// </summary>
|
||||
public string ExpirationDate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response class for creating shared access signature
|
||||
/// </summary>
|
||||
public class CreateSasResponse
|
||||
{
|
||||
public string SharedAccessSignature { get; set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request class for creating shared access signature
|
||||
/// </summary>
|
||||
public class CreateSasRequest
|
||||
{
|
||||
public static readonly
|
||||
RequestType<CreateSasParams, CreateSasResponse> Type =
|
||||
RequestType<CreateSasParams, CreateSasResponse>.Create("blob/createSas");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// 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.SqlServer.Management.Smo;
|
||||
using Azure.Storage.Blobs;
|
||||
using Azure.Storage;
|
||||
using Azure.Storage.Sas;
|
||||
using Microsoft.SqlTools.ServiceLayer.AzureBlob.Contracts;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.AzureBlob
|
||||
{
|
||||
class SharedAccessSignatureCreator
|
||||
{
|
||||
private Server sqlServer;
|
||||
|
||||
public SharedAccessSignatureCreator(Server sqlServer)
|
||||
{
|
||||
this.sqlServer = sqlServer;
|
||||
}
|
||||
|
||||
public string CreateSqlSASCredential(string accountName, string accountKey, string containerUri, string expirationDateString)
|
||||
{
|
||||
DateTimeOffset? expirationDate = null;
|
||||
if (!String.IsNullOrEmpty(expirationDateString))
|
||||
{
|
||||
expirationDate = DateTimeOffset.Parse(expirationDateString, CultureInfo.InvariantCulture);
|
||||
}
|
||||
var containerClient = new BlobContainerClient(new Uri(containerUri), new StorageSharedKeyCredential(accountName, accountKey));
|
||||
Uri secretStringUri = GetServiceSasUriForContainer(containerClient, null, expirationDate);
|
||||
string secretString = secretStringUri.ToString().Split('?')[1];
|
||||
string identity = "Shared Access Signature";
|
||||
WriteSASCredentialToSqlServer(containerUri, identity, secretString);
|
||||
return secretString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create sql sas credential with the given credential name
|
||||
/// </summary>
|
||||
/// <param name="credentialName">Name of sas credential, here is the same of the full container url.</param>
|
||||
/// <param name="identity">Identity for credential, here is fixed as "Shared Access Signature"</param>
|
||||
/// <param name="secretString">Secret of credential, which is sharedAccessSignatureForContainer </param>
|
||||
/// <returns> The newly created SAS credential</returns>
|
||||
public Credential WriteSASCredentialToSqlServer(string credentialName, string identity, string secretString)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Format of Sql SAS credential:
|
||||
// CREATE CREDENTIAL [https://<StorageAccountName>.blob.core.windows.net/<ContainerName>] WITH IDENTITY = N'Shared Access Signature',
|
||||
// SECRET = N'sv=2014-02-14&sr=c&sig=lxb2aXr%2Bi0Aeygg%2B0a4REZ%2BqsUxxxxxxsqUybg0tVzg%3D&st=2015-10-15T08%3A00%3A00Z&se=2015-11-15T08%3A00%3A00Z&sp=rwdl'
|
||||
//
|
||||
CredentialCollection credentials = sqlServer.Credentials;
|
||||
|
||||
Credential azureCredential = new Credential(sqlServer, credentialName);
|
||||
|
||||
// Container can have many SAS credentials coexisting, here we'll always drop existing one once customer choose to create new credential
|
||||
// since sql customer has no way to know its existency and even harder to retrive its secret string.
|
||||
if (credentials.Contains(credentialName))
|
||||
{
|
||||
Credential oldCredential = credentials[credentialName];
|
||||
oldCredential.Drop();
|
||||
}
|
||||
|
||||
azureCredential.Create(identity, secretString);
|
||||
return azureCredential;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new FailedOperationException(SR.WriteSASCredentialToSqlServerFailed, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Shared Access Policy for container
|
||||
/// Default Accesss permission is Write/List/Read/Delete
|
||||
/// </summary>
|
||||
/// <param name="container"></param>
|
||||
/// <param name="policyName"></param>
|
||||
/// <param name="selectedSaredAccessExpiryTime"></param>
|
||||
public Uri GetServiceSasUriForContainer(BlobContainerClient containerClient,
|
||||
string storedPolicyName = null,
|
||||
DateTimeOffset? expiringDate = null)
|
||||
{
|
||||
// Check whether this BlobContainerClient object has been authorized with Shared Key.
|
||||
if (containerClient.CanGenerateSasUri)
|
||||
{
|
||||
// Create a SAS token
|
||||
BlobSasBuilder sasBuilder = new BlobSasBuilder()
|
||||
{
|
||||
BlobContainerName = containerClient.Name,
|
||||
Resource = BlobSasResource.BLOB_CONTAINER
|
||||
};
|
||||
|
||||
if (storedPolicyName == null)
|
||||
{
|
||||
sasBuilder.ExpiresOn = (DateTimeOffset)(expiringDate == null ? DateTimeOffset.UtcNow.AddYears(1) : expiringDate);
|
||||
sasBuilder.SetPermissions(BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Delete);
|
||||
}
|
||||
else
|
||||
{
|
||||
sasBuilder.Identifier = storedPolicyName;
|
||||
}
|
||||
Uri sasUri = containerClient.GenerateSasUri(sasBuilder);
|
||||
|
||||
return sasUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FailedOperationException(SR.CreateSasForBlobContainerFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user