AzureResourceManagerActivity.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. using Microsoft.Azure.Management.ResourceManager;
  2. using Microsoft.Azure.Management.ResourceManager.Models;
  3. using Microsoft.Azure.Management.Storage;
  4. using Microsoft.Azure.Management.Storage.Models;
  5. using Microsoft.Azure.Services.AppAuthentication;
  6. using Microsoft.Azure.WebJobs;
  7. using Microsoft.Azure.WebJobs.Extensions.DurableTask;
  8. using Microsoft.Extensions.Logging;
  9. using Microsoft.Rest;
  10. using Microsoft.WindowsAzure.Storage;
  11. using Microsoft.WindowsAzure.Storage.Auth;
  12. using Microsoft.WindowsAzure.Storage.Blob;
  13. using Newtonsoft.Json;
  14. using Newtonsoft.Json.Linq;
  15. using System;
  16. using System.Collections.Generic;
  17. using System.IO;
  18. using System.Linq;
  19. using System.Threading.Tasks;
  20. using static ADPControl.HttpSurface;
  21. namespace ADPControl
  22. {
  23. public static class AzureResourceManagerActivity
  24. {
  25. private const string ArmTemplateFileName = "template.json";
  26. private static string[] AllowedImportSubServerResourceTypes = new string[] {
  27. "Microsoft.Sql/servers/firewallRules",
  28. "Microsoft.Sql/servers/databases",
  29. "Microsoft.Sql/servers/elasticPools",
  30. //"Microsoft.Sql/servers/keys",
  31. //"Microsoft.Sql/servers/databases/transparentDataEncryption",
  32. "Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies",
  33. "Microsoft.Sql/servers/administrators"
  34. };
  35. // Deploy the ARM template
  36. [FunctionName(nameof(BeginDeployArmTemplateForImport))]
  37. public static async Task<string> BeginDeployArmTemplateForImport([ActivityTrigger] ImportRequest request, ILogger log)
  38. {
  39. var azureServiceTokenProvider = new AzureServiceTokenProvider();
  40. TokenCredentials tokenArmCredential = new TokenCredentials(await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"));
  41. ResourceManagementClient resourcesClient = new ResourceManagementClient(tokenArmCredential) { SubscriptionId = request.SubscriptionId.ToString() };
  42. StorageManagementClient storageMgmtClient = new StorageManagementClient(tokenArmCredential) { SubscriptionId = request.SubscriptionId.ToString() };
  43. // Get the storage account keys for a given account and resource group
  44. IList<StorageAccountKey> acctKeys = storageMgmtClient.StorageAccounts.ListKeys(request.ResourceGroupName, request.StorageAccountName).Keys;
  45. // Get a Storage account using account creds:
  46. StorageCredentials storageCred = new StorageCredentials(request.StorageAccountName, acctKeys.FirstOrDefault().Value);
  47. CloudStorageAccount linkedStorageAccount = new CloudStorageAccount(storageCred, true);
  48. CloudBlobContainer container = linkedStorageAccount
  49. .CreateCloudBlobClient()
  50. .GetContainerReference(request.ContainerName);
  51. CloudBlockBlob blob = container.GetBlockBlobReference(ArmTemplateFileName);
  52. string json = await blob.DownloadTextAsync();
  53. JObject originalTemplate = JObject.Parse(json);
  54. JObject importTemplate = UpdateArmTemplateForImport(originalTemplate, request);
  55. var deployParams = new Deployment
  56. {
  57. Properties = new DeploymentProperties
  58. {
  59. Mode = DeploymentMode.Incremental,
  60. Template = importTemplate
  61. }
  62. };
  63. string deploymentName = request.TargetSqlServerName + "_" + DateTime.UtcNow.ToFileTimeUtc();
  64. try
  65. {
  66. await resourcesClient.Deployments.BeginCreateOrUpdateAsync(request.TargetSqlServerResourceGroupName, deploymentName, deployParams);
  67. }
  68. catch (Exception ex)
  69. {
  70. log.LogError(ex.ToString());
  71. throw ex;
  72. }
  73. return deploymentName;
  74. }
  75. // Get the ARM deployment status
  76. [FunctionName(nameof(GetArmDeploymentForImport))]
  77. public static async Task<string> GetArmDeploymentForImport([ActivityTrigger] (Guid, string, string) input)
  78. {
  79. Guid subscriptionId = input.Item1;
  80. string resourceGroupName = input.Item2;
  81. string deploymentName = input.Item3;
  82. var azureServiceTokenProvider = new AzureServiceTokenProvider();
  83. TokenCredentials tokenArmCredential = new TokenCredentials(await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"));
  84. ResourceManagementClient resourcesClient = new ResourceManagementClient(tokenArmCredential) { SubscriptionId = subscriptionId.ToString() };
  85. DeploymentExtended result = await resourcesClient.Deployments.GetAsync(resourceGroupName, deploymentName);
  86. return result.Properties.ProvisioningState;
  87. }
  88. // Get the ARM template without the parameter of the resource name
  89. [FunctionName(nameof(GetArmTemplateForExportSkipParameterization))]
  90. public static async Task<dynamic> GetArmTemplateForExportSkipParameterization([ActivityTrigger] ExportRequest request, ILogger log)
  91. {
  92. log.LogInformation("GetArmTemplateForExportSkipParameterization: entering");
  93. var azureServiceTokenProvider = new AzureServiceTokenProvider();
  94. TokenCredentials tokenArmCredential = new TokenCredentials(await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"));
  95. if (tokenArmCredential != null)
  96. {
  97. log.LogInformation("GetArmTemplateForExportSkipParameterization: acquired access token");
  98. ResourceManagementClient resourcesClient = new ResourceManagementClient(tokenArmCredential) { SubscriptionId = request.SubscriptionId.ToString() };
  99. string sourceSqlServerResourceId = string.Format("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Sql/servers/{2}", request.SubscriptionId, request.SourceSqlServerResourceGroupName, request.SourceSqlServerName);
  100. ResourceGroupExportResult exportedTemplate = resourcesClient.ResourceGroups.ExportTemplate(request.SourceSqlServerResourceGroupName, new ExportTemplateRequest(new List<string> { sourceSqlServerResourceId }, "SkipResourceNameParameterization"));
  101. log.LogInformation("GetArmTemplateForExportSkipParameterization: server template exported. Size: {0} bytes", exportedTemplate.Template.ToString().Length);
  102. dynamic template = (dynamic)exportedTemplate.Template;
  103. // Filtering the list of databases
  104. dynamic databases = template.resources.SelectTokens("$.[?(@.type == 'Microsoft.Sql/servers/databases')]");
  105. int numberOfDatabases = 0;
  106. foreach (var db in databases)
  107. {
  108. numberOfDatabases++;
  109. }
  110. log.LogInformation("GetArmTemplateForExportSkipParameterization: exiting with database list. Databases count: {0}", numberOfDatabases);
  111. return databases;
  112. }
  113. log.LogInformation("GetArmTemplateForExportSkipParameterization: exiting with empty database list");
  114. return null;
  115. }
  116. // Get the ARM template without the parameter of the resource name
  117. [FunctionName(nameof(GetArmTemplateForImportSkipParameterization))]
  118. public static async Task<dynamic> GetArmTemplateForImportSkipParameterization([ActivityTrigger] ImportRequest request, ILogger log)
  119. {
  120. var azureServiceTokenProvider = new AzureServiceTokenProvider();
  121. TokenCredentials tokenArmCredential = new TokenCredentials(await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"));
  122. if (tokenArmCredential != null)
  123. {
  124. log.LogInformation("GetArmTemplateForImportSkipParameterization: acquired access token");
  125. ResourceManagementClient resourcesClient = new ResourceManagementClient(tokenArmCredential) { SubscriptionId = request.SubscriptionId.ToString() };
  126. string sourceSqlServerResourceId = string.Format("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Sql/servers/{2}", request.SubscriptionId, request.TargetSqlServerResourceGroupName, request.TargetSqlServerName);
  127. ResourceGroupExportResult exportedTemplate = resourcesClient.ResourceGroups.ExportTemplate(request.TargetSqlServerResourceGroupName, new ExportTemplateRequest(new List<string> { sourceSqlServerResourceId }, "SkipResourceNameParameterization"));
  128. log.LogInformation("GetArmTemplateForImportSkipParameterization: server template exported. Size: {0} bytes", exportedTemplate.Template.ToString().Length);
  129. dynamic template = (dynamic)exportedTemplate.Template;
  130. // Filtering the list of databases
  131. dynamic databases = template.resources.SelectTokens("$.[?(@.type == 'Microsoft.Sql/servers/databases')]");
  132. int numberOfDatabases = 0;
  133. foreach (var db in databases)
  134. {
  135. numberOfDatabases++;
  136. }
  137. log.LogInformation("GetArmTemplateForExportSkipParameterization: exiting with database list. Databases count: {0}", numberOfDatabases);
  138. return databases;
  139. }
  140. return null;
  141. }
  142. [FunctionName(nameof(GetArmTemplateForExport))]
  143. public static async Task<dynamic> GetArmTemplateForExport([ActivityTrigger] ExportRequest request)
  144. {
  145. var azureServiceTokenProvider = new AzureServiceTokenProvider();
  146. TokenCredentials tokenArmCredential = new TokenCredentials(await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"));
  147. ResourceManagementClient resourcesClient = new ResourceManagementClient(tokenArmCredential) { SubscriptionId = request.SubscriptionId.ToString() };
  148. string sourceSqlServerResourceId = string.Format("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Sql/servers/{2}", request.SubscriptionId, request.SourceSqlServerResourceGroupName, request.SourceSqlServerName);
  149. ResourceGroupExportResult exportedTemplate = resourcesClient.ResourceGroups.ExportTemplate(request.SourceSqlServerResourceGroupName, new ExportTemplateRequest(new List<string> { sourceSqlServerResourceId }, "IncludeParameterDefaultValue"));
  150. return exportedTemplate.Template;
  151. }
  152. private static JObject UpdateArmTemplateForImport(JObject originalTemplate, ImportRequest request)
  153. {
  154. string serverNameParameterName = null;
  155. // Go through every parameter to find the property name is like 'server_%_name'
  156. using (JsonTextReader reader = new JsonTextReader(new StringReader(originalTemplate["parameters"].ToString())))
  157. {
  158. while (reader.Read())
  159. {
  160. if (reader.TokenType.ToString().Equals("PropertyName")
  161. && reader.ValueType.ToString().Equals("System.String")
  162. && reader.Value.ToString().StartsWith("servers_")
  163. && reader.Value.ToString().EndsWith("_name"))
  164. {
  165. serverNameParameterName = reader.Value.ToString();
  166. break;
  167. }
  168. }
  169. }
  170. // 1. Replacing the default value to the target server name, appending to the new template
  171. originalTemplate["parameters"][serverNameParameterName]["defaultValue"] = request.TargetSqlServerName;
  172. JObject serverNameParameterValue = (JObject)originalTemplate["parameters"][serverNameParameterName];
  173. // 2. Cleanup all the parameters except the updated server name
  174. ((JObject)originalTemplate["parameters"]).RemoveAll();
  175. ((JObject)originalTemplate["parameters"]).Add(serverNameParameterName, serverNameParameterValue);
  176. // 3. Adjust the servers resource by adding password after the login
  177. JObject server = (JObject)originalTemplate["resources"]
  178. .SelectToken("$.[?(@.type == 'Microsoft.Sql/servers')]");
  179. server.Remove("identity");
  180. JObject serverProperties = (JObject)server["properties"];
  181. serverProperties.Property("administratorLogin")
  182. .AddAfterSelf(new JProperty("administratorLoginPassword", request.SqlAdminPassword));
  183. JArray newResources = new JArray();
  184. // 4. Getting the whitelisted resources and adding them to the new template later.
  185. foreach (string resourceType in AllowedImportSubServerResourceTypes)
  186. {
  187. List<JToken> resources = originalTemplate["resources"]
  188. .SelectTokens(string.Format("$.[?(@.type == '{0}')]", resourceType)).ToList();
  189. newResources.Add(resources);
  190. }
  191. // 5. Clean up all the resources excepted the new server and whitelisted resource type.
  192. ((JArray)originalTemplate["resources"]).Clear();
  193. ((JArray)originalTemplate["resources"]).Add(server);
  194. foreach (var resource in newResources)
  195. {
  196. ((JArray)originalTemplate["resources"]).Add(resource);
  197. }
  198. return originalTemplate;
  199. }
  200. }
  201. }