Skip to content

Commit 392aa58

Browse files
authored
Add support for cross tenant and cross subscription restore operations (#17582)
* Add logic to support cross server and cross tenant restore 1) Added support for cross restore operations by using optional param sourceResourceId. 2) Added support for cross tenant restore operations. 3) Reference the new 2.1.1-preview .NET sdk, as it has the newly added sourceResourceId param. * Add release notes to changelog.md * Add null check for parser, modify release notes
1 parent 8b6e600 commit 392aa58

File tree

6 files changed

+95
-30
lines changed

6 files changed

+95
-30
lines changed

src/Sql/Sql.Test/Sql.Test.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<PackageReference Include="Microsoft.Azure.KeyVault.WebKey" Version="3.0.1" />
2020
<PackageReference Include="Microsoft.Azure.Management.KeyVault" Version="4.0.0-preview.1" />
2121
<PackageReference Include="Microsoft.Azure.Management.OperationalInsights" Version="0.24.0-preview" />
22-
<PackageReference Include="Microsoft.Azure.Management.Sql" Version="2.1.0-preview" />
22+
<PackageReference Include="Microsoft.Azure.Management.Sql" Version="2.1.1-preview" />
2323
</ItemGroup>
2424

2525
<ItemGroup>

src/Sql/Sql/ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
- `Get-AzSqlInstanceLink`
3232
- `Remove-AzSqlInstanceLink`
3333
- `Set-AzSqlInstanceLink`
34+
* Added support for DataWarehouse cross tenant and cross subscription restore operations to `Restore-AzSqlDatabase` cmdlet
3435

3536
## Version 3.7.1
3637
* Deprecation of Get-AzSqlDatabaseTransparentDataEncryptionActivity cmdlet

src/Sql/Sql/Database Backup/Cmdlet/RestoreAzureRMSqlDatabase.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using Microsoft.WindowsAzure.Commands.Utilities.Common;
2121
using System;
2222
using System.Collections;
23+
using System.Collections.Generic;
2324
using System.Globalization;
2425
using System.Linq;
2526
using System.Management.Automation;
@@ -398,7 +399,45 @@ protected override AzureSqlDatabaseModel GetEntity()
398399
model.Edition = Edition;
399400
}
400401

401-
return ModelAdapter.RestoreDatabase(this.ResourceGroupName, restorePointInTime, ResourceId, model);
402+
/// get auth headers for cross-sub and cross-tenant restore operations
403+
string targetSubscriptionId = ModelAdapter.Context?.Subscription.Id;
404+
string sourceSubscriptionId = ParseSourceSubscriptionIdFromResourceId(ResourceId);
405+
Dictionary<string, List<string>> auxAuthHeader = null;
406+
if (!string.IsNullOrEmpty(ResourceId) && targetSubscriptionId!=sourceSubscriptionId)
407+
{
408+
List<string> resourceIds = new List<string>();
409+
resourceIds.Add(ResourceId);
410+
var auxHeaderDictionary = GetAuxilaryAuthHeaderFromResourceIds(resourceIds);
411+
if (auxHeaderDictionary != null && auxHeaderDictionary.Count > 0)
412+
{
413+
auxAuthHeader = new Dictionary<string, List<string>>(auxHeaderDictionary);
414+
}
415+
}
416+
417+
return ModelAdapter.RestoreDatabase(this.ResourceGroupName, restorePointInTime, ResourceId, model, sourceSubscriptionId, auxAuthHeader);
418+
}
419+
420+
/// <summary>
421+
/// Parse source subscription id from ResourceId
422+
/// </summary>
423+
/// <returns>Source Subscription Id</returns>
424+
private string ParseSourceSubscriptionIdFromResourceId(string resourceId)
425+
{
426+
if (string.IsNullOrEmpty(resourceId))
427+
{
428+
return null;
429+
}
430+
431+
string[] words = resourceId.Split('/');
432+
string sourceSubscriptionId = "";
433+
for (int i = 0; i < words.Length; i++)
434+
{
435+
if (words[i] == "subscriptions")
436+
{
437+
sourceSubscriptionId = words[i + 1];
438+
}
439+
}
440+
return sourceSubscriptionId;
402441
}
403442
}
404443
}

src/Sql/Sql/Database Backup/Services/AzureSqlDatabaseBackupAdapter.cs

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,12 @@
1313
// ----------------------------------------------------------------------------------
1414

1515
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
16-
using Microsoft.Azure.Commands.Common.Authentication.Models;
1716
using Microsoft.Azure.Commands.Sql.Backup.Model;
1817
using Microsoft.Azure.Commands.Sql.Database.Model;
19-
using Microsoft.Azure.Commands.Sql.Database.Services;
20-
using Microsoft.Azure.Commands.Sql.ElasticPool.Services;
2118
using Microsoft.Azure.Commands.Sql.Server.Adapter;
2219
using Microsoft.Azure.Management.Sql.LegacySdk.Models;
23-
using Microsoft.WindowsAzure.Commands.Utilities.Common;
2420
using System;
2521
using System.Collections.Generic;
26-
using System.Globalization;
2722
using System.Linq;
2823
using System.Management.Automation;
2924

@@ -586,8 +581,10 @@ internal AzureSqlDatabaseGeoBackupPolicyModel SetDatabaseGeoBackupPolicy(
586581
/// <param name="restorePointInTime">A point to time to restore to (for PITR and dropped DB restore)</param>
587582
/// <param name="resourceId">The resource ID of the DB to restore (live, geo backup, deleted database, long term retention backup, etc.)</param>
588583
/// <param name="model">An object modeling the database to create via restore</param>
584+
/// <param name="sourceSubscriptionId">Source Subscription Id</param>
585+
/// <param name="customHeaders">Custom headers</param>
589586
/// <returns>Restored database object</returns>
590-
internal AzureSqlDatabaseModel RestoreDatabase(string resourceGroup, DateTime restorePointInTime, string resourceId, AzureSqlDatabaseModel model)
587+
internal AzureSqlDatabaseModel RestoreDatabase(string resourceGroup, DateTime restorePointInTime, string resourceId, AzureSqlDatabaseModel model, string sourceSubscriptionId, Dictionary<string, List<string>> customHeaders = null)
591588
{
592589
// Construct the ARM resource Id of the pool
593590
string elasticPoolId = string.IsNullOrWhiteSpace(model.ElasticPoolName) ? null : AzureSqlDatabaseModel.PoolIdTemplate.FormatInvariant(
@@ -614,29 +611,46 @@ internal AzureSqlDatabaseModel RestoreDatabase(string resourceGroup, DateTime re
614611
RequestedBackupStorageRedundancy = model.RequestedBackupStorageRedundancy,
615612
ZoneRedundant = model.ZoneRedundant,
616613
};
617-
618-
if (model.CreateMode == Management.Sql.Models.CreateMode.Recovery)
619-
{
620-
dbModel.RecoverableDatabaseId = resourceId;
621-
}
622-
else if (model.CreateMode == Management.Sql.Models.CreateMode.Restore)
623-
{
624-
dbModel.RestorableDroppedDatabaseId = resourceId;
625-
}
626-
else if (model.CreateMode == Management.Sql.Models.CreateMode.PointInTimeRestore)
614+
615+
// check if restore operation is cross subscription or same subscription
616+
if (_subscription.Id != sourceSubscriptionId)
627617
{
628-
dbModel.SourceDatabaseId = resourceId;
629-
}
630-
else if (model.CreateMode == Management.Sql.Models.CreateMode.RestoreLongTermRetentionBackup)
631-
{
632-
dbModel.LongTermRetentionBackupResourceId = resourceId;
618+
// cross subscription path
619+
if (dbModel.CreateMode != Management.Sql.Models.CreateMode.Recovery
620+
&& dbModel.CreateMode != Management.Sql.Models.CreateMode.Restore
621+
&& dbModel.CreateMode != Management.Sql.Models.CreateMode.PointInTimeRestore
622+
&& dbModel.CreateMode != Management.Sql.Models.CreateMode.RestoreLongTermRetentionBackup)
623+
{
624+
throw new ArgumentException("Restore mode not supported");
625+
}
626+
dbModel.SourceResourceId = resourceId;
633627
}
634628
else
635629
{
636-
throw new ArgumentException("Restore mode not supported");
630+
// same subscription path
631+
if (model.CreateMode == Management.Sql.Models.CreateMode.Recovery)
632+
{
633+
dbModel.RecoverableDatabaseId = resourceId;
634+
}
635+
else if (model.CreateMode == Management.Sql.Models.CreateMode.Restore)
636+
{
637+
dbModel.RestorableDroppedDatabaseId = resourceId;
638+
}
639+
else if (model.CreateMode == Management.Sql.Models.CreateMode.PointInTimeRestore)
640+
{
641+
dbModel.SourceDatabaseId = resourceId;
642+
}
643+
else if (model.CreateMode == Management.Sql.Models.CreateMode.RestoreLongTermRetentionBackup)
644+
{
645+
dbModel.LongTermRetentionBackupResourceId = resourceId;
646+
}
647+
else
648+
{
649+
throw new ArgumentException("Restore mode not supported");
650+
}
637651
}
638652

639-
Management.Sql.Models.Database database = Communicator.RestoreDatabase(resourceGroup, model.ServerName, model.DatabaseName, dbModel);
653+
Management.Sql.Models.Database database = Communicator.RestoreDatabase(resourceGroup, model.ServerName, model.DatabaseName, dbModel, customHeaders);
640654

641655
return new AzureSqlDatabaseModel(resourceGroup, model.ServerName, database);
642656
}

src/Sql/Sql/Database Backup/Services/AzureSqlDatabaseBackupCommunicator.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
using Microsoft.Azure.Management.Sql.LegacySdk;
2020
using Microsoft.Azure.Management.Sql.LegacySdk.Models;
2121
using System.Collections.Generic;
22-
using Microsoft.Azure.Commands.Sql.Database.Model;
23-
using Microsoft.Azure.Management.Internal.Resources.Models;
2422

2523
namespace Microsoft.Azure.Commands.Sql.Backup.Services
2624
{
@@ -461,10 +459,23 @@ public Management.Sql.LegacySdk.Models.Database LegacyRestoreDatabase(string res
461459
/// <param name="serverName">The name of the Azure SQL Server</param>
462460
/// <param name="databaseName">The name of the Azure SQL database</param>
463461
/// <param name="model">Sql Database Model with required parameters</param>
462+
/// <param name="customHeaders">Custom headers</param>
464463
/// <returns>Restored database object</returns>
465-
public Management.Sql.Models.Database RestoreDatabase(string resourceGroupName, string serverName, string databaseName, Management.Sql.Models.Database model)
464+
public Management.Sql.Models.Database RestoreDatabase(string resourceGroupName, string serverName, string databaseName, Management.Sql.Models.Database model, Dictionary<string, List<string>> customHeaders = null)
466465
{
467-
return GetCurrentSqlClient().Databases.CreateOrUpdate(resourceGroupName, serverName, databaseName, model);
466+
if (customHeaders == null)
467+
{
468+
// Execute the create call without the custom headers.
469+
return GetCurrentSqlClient().Databases.CreateOrUpdate(resourceGroupName, serverName, databaseName, model);
470+
}
471+
else
472+
{
473+
// Execute the create call and pass the custom headers.
474+
using (var _result = GetCurrentSqlClient().Databases.CreateOrUpdateWithHttpMessagesAsync(resourceGroupName, serverName, databaseName, model, customHeaders).ConfigureAwait(false).GetAwaiter().GetResult())
475+
{
476+
return _result.Body;
477+
}
478+
}
468479
}
469480

470481
/// <summary>

src/Sql/Sql/Sql.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</ItemGroup>
2222

2323
<ItemGroup>
24-
<PackageReference Include="Microsoft.Azure.Management.Sql" Version="2.1.0-preview" />
24+
<PackageReference Include="Microsoft.Azure.Management.Sql" Version="2.1.1-preview" />
2525
<PackageReference Include="System.Security.Permissions" Version="4.5.0" />
2626
</ItemGroup>
2727
<ItemGroup>

0 commit comments

Comments
 (0)