TemplateGenerator.cs
using System;
using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Management; using System.Management.Automation; using APIManagementTemplate.Models; using Newtonsoft.Json.Linq; using Microsoft.IdentityModel.Clients.ActiveDirectory; using System.Windows.Forms; using System.Net; using System.IO; using System.Xml.Linq; using System.Text.RegularExpressions; namespace APIManagementTemplate { public class TemplateGenerator { private string subscriptionId; private string resourceGroup; private string servicename; private string apiFilters; private bool exportPIManagementInstance; private bool exportGroups; private bool exportProducts; private bool parametrizePropertiesOnly; private bool replaceSetBackendServiceBaseUrlAsProperty; private bool fixedServiceNameParameter; private string apiVersion; IResourceCollector resourceCollector; public TemplateGenerator(string servicename, string subscriptionId, string resourceGroup, string apiFilters, bool exportGroups, bool exportProducts, bool exportPIManagementInstance, bool parametrizePropertiesOnly, IResourceCollector resourceCollector, bool replaceSetBackendServiceBaseUrlAsProperty = false, bool fixedServiceNameParameter = false, string apiVersion= null) { this.servicename = servicename; this.subscriptionId = subscriptionId; this.resourceGroup = resourceGroup; this.apiFilters = apiFilters; this.exportGroups = exportGroups; this.exportProducts = exportProducts; this.exportPIManagementInstance = exportPIManagementInstance; this.parametrizePropertiesOnly = parametrizePropertiesOnly; this.replaceSetBackendServiceBaseUrlAsProperty = replaceSetBackendServiceBaseUrlAsProperty; this.resourceCollector = resourceCollector; this.fixedServiceNameParameter = fixedServiceNameParameter; this.apiVersion = apiVersion; } private string GetAPIMResourceIDString() { return $"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.ApiManagement/service/{servicename}"; } public async Task<JObject> GenerateTemplate() { DeploymentTemplate template = new DeploymentTemplate(this.parametrizePropertiesOnly, this.fixedServiceNameParameter); if (exportPIManagementInstance) { var apim = await resourceCollector.GetResource(GetAPIMResourceIDString()); var apimTemplateResource = template.AddAPIManagementInstance(apim); var policies = await resourceCollector.GetResource(GetAPIMResourceIDString() + "/policies"); foreach (JObject policy in (policies == null ? new JArray() : policies.Value<JArray>("value"))) { var policyTemplateResource = template.CreatePolicy(policy); apimTemplateResource.Value<JArray>("resources").Add(policyTemplateResource); } var loggers = await resourceCollector.GetResource(GetAPIMResourceIDString() + "/loggers"); foreach (JObject logger in (loggers == null ? new JArray() : loggers.Value<JArray>("value"))) { HandleProperties(logger.Value<string>("name"), "Logger", logger["properties"].ToString()); var loggerTemplateResource = template.CreateLogger(logger, false); apimTemplateResource.Value<JArray>("resources").Add(loggerTemplateResource); } } var apis = await resourceCollector.GetResource(GetAPIMResourceIDString() + "/apis", (string.IsNullOrEmpty(apiFilters) ? "" : $"$filter={apiFilters}")); if (apis != null) { foreach (JObject apiObject in (!string.IsNullOrEmpty(apiVersion) ? apis.Value<JArray>("value").Where(aa => aa["properties"].Value<string>("apiVersion") == this.apiVersion) : apis.Value<JArray>("value"))) { var id = apiObject.Value<string>("id"); var apiInstance = await resourceCollector.GetResource(id); var apiTemplateResource = template.AddApi(apiInstance); //Api version set string apiversionsetid = apiTemplateResource["properties"].Value<string>("apiVersionSetId"); if (!string.IsNullOrEmpty(apiversionsetid)) { AzureResourceId aiapiversion = new AzureResourceId(apiInstance["properties"].Value<string>("apiVersionSetId")); var versionsetResource = template.AddVersionSet(await resourceCollector.GetResource(apiversionsetid)); if (versionsetResource != null) { string resourceid = $"[resourceId('Microsoft.ApiManagement/service/api-version-sets',{versionsetResource.GetResourceId()})]"; apiTemplateResource["properties"]["apiVersionSetId"] = resourceid; apiTemplateResource.Value<JArray>("dependsOn").Add(resourceid); } } string openidProviderId = GetOpenIdProviderId(apiTemplateResource); if (!String.IsNullOrWhiteSpace(openidProviderId)) { if (this.openidConnectProviders == null) { openidConnectProviders = new List<JObject>(); var providers = await resourceCollector.GetResource(GetAPIMResourceIDString() + "/openidConnectProviders"); foreach (JObject openidConnectProvider in (providers == null ? new JArray() : providers.Value<JArray>("value"))) { openidConnectProviders.Add(openidConnectProvider); } } var openIdProvider = this.openidConnectProviders.FirstOrDefault(x => x.Value<string>("name") == openidProviderId); template.CreateOpenIDConnectProvider(openIdProvider, true); } var operations = await resourceCollector.GetResource(id + "/operations"); foreach (JObject operation in (operations == null ? new JArray() : operations.Value<JArray>("value"))) { var opId = operation.Value<string>("id"); var operationInstance = await resourceCollector.GetResource(opId); var operationTemplateResource = template.CreateOperation(operationInstance); apiTemplateResource.Value<JArray>("resources").Add(operationTemplateResource); var operationPolicies = await resourceCollector.GetResource(opId + "/policies"); foreach (JObject policy in (operationPolicies == null ? new JArray() : operationPolicies.Value<JArray>("value"))) { var pol = template.CreatePolicy(policy); //add properties this.PolicyHandleProperties(pol, apiTemplateResource.Value<string>("name"), (operationInstance.Value<string>("name").StartsWith("api-") ? operationInstance.Value<string>("name").Substring(4, (operationInstance.Value<string>("name").LastIndexOf("-" + operationInstance["properties"].Value<string>("method").ToLower())) - 4) : operationInstance.Value<string>("name"))); var operationSuffix = apiInstance.Value<string>("name") + "_" + operationInstance.Value<string>("name"); //Handle Azure Resources if (!this.PolicyHandeAzureResources(pol, apiTemplateResource.Value<string>("name"), template)) { PolicyHandeBackendUrl(pol, operationSuffix, template); } var backendid = TemplateHelper.GetBackendIdFromnPolicy(policy["properties"].Value<string>("policyContent")); if (!string.IsNullOrEmpty(backendid)) { JObject backendInstance = await HandleBackend(template, operationSuffix, backendid); if (apiTemplateResource.Value<JArray>("dependsOn") == null) apiTemplateResource["dependsOn"] = new JArray(); //add dependeOn apiTemplateResource.Value<JArray>("dependsOn").Add($"[resourceId('Microsoft.ApiManagement/service/backends', parameters('{GetServiceName(servicename)}'), '{backendInstance.Value<string>("name")}')]"); } await AddCertificate(policy, template); operationTemplateResource.Value<JArray>("resources").Add(pol); //handle nextlink? } //handle nextlink? } var apiPolicies = await resourceCollector.GetResource(id + "/policies"); foreach (JObject policy in (apiPolicies == null ? new JArray() : apiPolicies.Value<JArray>("value"))) { //Handle SOAP Backend var backendid = TemplateHelper.GetBackendIdFromnPolicy(policy["properties"].Value<string>("policyContent")); await AddCertificate(policy, template); PolicyHandeBackendUrl(policy, apiInstance.Value<string>("name"), template); var policyTemplateResource = template.CreatePolicy(policy); this.PolicyHandleProperties(policy, apiTemplateResource.Value<string>("name"), null); apiTemplateResource.Value<JArray>("resources").Add(policyTemplateResource); if (!string.IsNullOrEmpty(backendid)) { JObject backendInstance = await HandleBackend(template, apiObject.Value<string>("name"), backendid); if (apiTemplateResource.Value<JArray>("dependsOn") == null) apiTemplateResource["dependsOn"] = new JArray(); //add dependeOn apiTemplateResource.Value<JArray>("dependsOn").Add($"[resourceId('Microsoft.ApiManagement/service/backends', parameters('{GetServiceName(servicename)}'), '{backendInstance.Value<string>("name")}')]"); } //handle nextlink? } //schemas var apiSchemas = await resourceCollector.GetResource(id + "/schemas"); foreach (JObject schema in (apiSchemas == null ? new JArray() : apiSchemas.Value<JArray>("value"))) { var schemaTemplate = template.CreateAPISchema(schema); apiTemplateResource.Value<JArray>("resources").Add(JObject.FromObject(schemaTemplate)); } //handle nextlink? } } // Export all groups if we don't export the products. if (exportGroups && !exportProducts) { var groups = await resourceCollector.GetResource(GetAPIMResourceIDString() + "/groups"); foreach (JObject groupObject in (groups == null ? new JArray() : groups.Value<JArray>("value"))) { //cannot edit och create built in groups if (groupObject["properties"].Value<bool>("builtIn") == false) template.AddGroup(groupObject); } } if (exportProducts) { var products = await resourceCollector.GetResource(GetAPIMResourceIDString() + "/products"); foreach (JObject productObject in (products == null ? new JArray() : products.Value<JArray>("value"))) { var id = productObject.Value<string>("id"); var productInstance = await resourceCollector.GetResource(id); var productApis = await resourceCollector.GetResource(id + "/apis", (string.IsNullOrEmpty(apiFilters) ? "" : $"$filter={apiFilters}")); // Skip product if not related to an API in the filter. if (productApis != null && productApis.Value<JArray>("value").Count > 0) { var productTemplateResource = template.AddProduct(productObject); foreach (JObject productApi in (productApis == null ? new JArray() : productApis.Value<JArray>("value"))) { var productProperties = productApi["properties"]; if (productProperties["apiVersionSetId"] != null) { var apiVersionSetId = new AzureResourceId(productProperties["apiVersionSetId"].ToString()).ValueAfter("api-version-sets"); productProperties["apiVersionSetId"] = $"[resourceId('Microsoft.ApiManagement/service/api-version-sets', parameters('{GetServiceName(servicename)}'), '{apiVersionSetId}')]"; } productTemplateResource.Value<JArray>("resources").Add(template.AddProductSubObject(productApi)); } var groups = await resourceCollector.GetResource(id + "/groups"); foreach (JObject group in (groups == null ? new JArray() : groups.Value<JArray>("value"))) { if (group["properties"].Value<bool>("builtIn") == false) { // Add group resource var groupObject = await resourceCollector.GetResource(GetAPIMResourceIDString() + "/groups/" + group.Value<string>("name")); template.AddGroup(groupObject); productTemplateResource.Value<JArray>("resources").Add(template.AddProductSubObject(group)); productTemplateResource.Value<JArray>("dependsOn").Add($"[resourceId('Microsoft.ApiManagement/service/groups', parameters('{GetServiceName(servicename)}'), '{group.Value<string>("name")}')]"); } } var policies = await resourceCollector.GetResource(id + "/policies"); foreach (JObject policy in (policies == null ? new JArray() : policies.Value<JArray>("value"))) { productTemplateResource.Value<JArray>("resources").Add(template.AddProductSubObject(policy)); } } } } var properties = await resourceCollector.GetResource(GetAPIMResourceIDString() + "/properties"); foreach (JObject propertyObject in (properties == null ? new JArray() : properties.Value<JArray>("value"))) { var id = propertyObject.Value<string>("id"); var name = propertyObject["properties"].Value<string>("displayName"); var identifiedProperty = this.identifiedProperties.Where(idp => name.EndsWith(idp.name)).FirstOrDefault(); if (identifiedProperty == null) { identifiedProperty = identifiedProperties.FirstOrDefault(idp => name == $"{idp.name}-key" && idp.type == Property.PropertyType.Function); } if (identifiedProperty != null) { if (identifiedProperty.type == Property.PropertyType.LogicApp) { propertyObject["properties"]["value"] = $"[concat('sv=',{identifiedProperty.extraInfo}.queries.sv,'&sig=',{identifiedProperty.extraInfo}.queries.sig)]"; } else if (identifiedProperty.type == Property.PropertyType.LogicAppRevisionGa) { propertyObject["properties"]["value"] = $"[{identifiedProperty.extraInfo}.queries.sig]"; } else if (identifiedProperty.type == Property.PropertyType.Function) { // "replacewithfunctionoperationname" propertyObject["properties"]["value"] = $"[{identifiedProperty.extraInfo.Replace("replacewithfunctionoperationname", $"{identifiedProperty.operationName}")}]"; } var propertyTemplate = template.AddProperty(propertyObject); if (!parametrizePropertiesOnly) { string resourceid = $"[resourceId('Microsoft.ApiManagement/service/properties',{propertyTemplate.GetResourceId()})]"; foreach (var apiName in identifiedProperty.apis) { var apiTemplate = template.resources.Where(rr => rr.Value<string>("name") == apiName).FirstOrDefault(); if(apiTemplate != null) apiTemplate.Value<JArray>("dependsOn").Add(resourceid); } } } } return JObject.FromObject(template); } private string GetServiceName(string serviceName) { var template = new DeploymentTemplate(this.parametrizePropertiesOnly, fixedServiceNameParameter); return template.GetServiceName(serviceName); } private async Task AddCertificate(JObject policy, DeploymentTemplate template) { var certificateThumbprint = TemplateHelper.GetCertificateThumbPrintIdFromPolicy(policy["properties"].Value<string>("policyContent")); if (!string.IsNullOrEmpty(certificateThumbprint)) { var certificates = await resourceCollector.GetResource(GetAPIMResourceIDString() + "/certificates"); if (certificates != null) { var certificate = certificates.Value<JArray>("value").FirstOrDefault(x => x?["properties"]?.Value<string>("thumbprint") == certificateThumbprint); if (certificate != null) template.CreateCertificate(JObject.FromObject(certificate), true); } } } private string GetOpenIdProviderId(JObject apiTemplateResource) { if (apiTemplateResource == null || !apiTemplateResource["properties"].HasValues || !apiTemplateResource["properties"]["authenticationSettings"].HasValues || !apiTemplateResource["properties"]["authenticationSettings"]["openid"].HasValues || apiTemplateResource["properties"]["authenticationSettings"]["openid"]["openidProviderId"] == null ) return string.Empty; JToken openIdProviderId = apiTemplateResource?["properties"]["authenticationSettings"]["openid"]["openidProviderId"]; return openIdProviderId.Value<string>(); } private async Task<JObject> HandleBackend(DeploymentTemplate template, string startname, string backendid) { var backendInstance = await resourceCollector.GetResource(GetAPIMResourceIDString() + "/backends/" + backendid); JObject azureResource = null; if (backendInstance["properties"]["resourceId"] != null) { string version = "2018-02-01"; if(backendInstance["properties"].Value<string>("resourceId").Contains("Microsoft.Logic")) { version = "2017-07-01"; } azureResource = await resourceCollector.GetResource(backendInstance["properties"].Value<string>("resourceId"), "", version); } var property = template.AddBackend(backendInstance, azureResource); if (property != null) { if (property.type == Property.PropertyType.LogicApp) { var idp = this.identifiedProperties.Where(pp => pp.name.StartsWith(startname) && pp.name.Contains("-invoke")).FirstOrDefault(); if (idp != null) { idp.extraInfo = property.extraInfo; idp.type = Property.PropertyType.LogicAppRevisionGa; } } else if (property.type == Property.PropertyType.Function) { property.operationName = GetOperationName(startname); identifiedProperties.Add(property); foreach (var idp in this.identifiedProperties.Where(pp => pp.name.ToLower().StartsWith(property.name) && !pp.name.Contains("-invoke"))) { idp.extraInfo = property.extraInfo; idp.type = Property.PropertyType.Function; } } } return backendInstance; } private static string GetOperationName(string startname) { if (startname.IndexOf("_") >= 0) return startname.Split('_')[1].Replace("-", String.Empty); return startname; } public void PolicyHandleProperties(JObject policy, string apiname, string operationName) { var policyContent = policy["properties"].Value<string>("policyContent"); HandleProperties(apiname, operationName, policyContent); } private void HandleProperties(string apiname, string operationName, string content) { var match = Regex.Match(content, "{{(?<name>[-_.a-zA-Z0-9]*)}}"); while (match.Success) { string name = match.Groups["name"].Value; var idp = identifiedProperties.Where(pp => pp.name == name).FirstOrDefault(); if (idp == null) { this.identifiedProperties.Add(new Property() { name = name, type = Property.PropertyType.Standard, apis = new List<string>(new string[] { apiname }), operationName = operationName }); } else if (!idp.apis.Contains(apiname)) { idp.apis.Add(apiname); } match = match.NextMatch(); } } public List<Property> identifiedProperties = new List<Property>(); public List<JObject> openidConnectProviders = null; public bool PolicyHandeAzureResources(JObject policy, string apiname, DeploymentTemplate template) { var policyContent = policy["properties"].Value<string>("policyContent"); var policyXMLDoc = XDocument.Parse(policyContent); var commentMatch = Regex.Match(policyContent, "<!--[ ]*(?<json>{+.*\"azureResource.*)-->"); if (commentMatch.Success) { var json = commentMatch.Groups["json"].Value; JObject azureResourceObject = JObject.Parse(json).Value<JObject>("azureResource"); if (azureResourceObject != null) { string reourceType = azureResourceObject.Value<string>("type"); string id = azureResourceObject.Value<string>("id"); if (reourceType == "logicapp") { var logicAppNameMatch = Regex.Match(id, @"resourceGroups/(?<resourceGroupName>[\w-_d]*)/providers/Microsoft.Logic/workflows/(?<name>[\w-_d]*)/triggers/(?<triggerName>[\w-_d]*)"); string logicAppName = logicAppNameMatch.Groups["name"].Value; string logicApptriggerName = logicAppNameMatch.Groups["triggerName"].Value; string logicAppResourceGroup = logicAppNameMatch.Groups["resourceGroupName"].Value; string listCallbackUrl = $"listCallbackUrl(resourceId(parameters('{template.AddParameter($"logicApp_{logicAppName}_resourcegroup", "string", logicAppResourceGroup)}'),'Microsoft.Logic/workflows/triggers', parameters('{template.AddParameter($"logicApp_{logicAppName}_name", "string", logicAppName)}'),parameters('{template.AddParameter($"logicApp_{logicAppName}_trigger", "string", logicApptriggerName)}')), providers('Microsoft.Logic', 'workflows').apiVersions[0])"; //Set the Base URL var backendService = policyXMLDoc.Descendants().Where(dd => dd.Name == "set-backend-service" && dd.Attribute("id").Value == "apim-generated-policy").FirstOrDefault(); policy["properties"]["policyContent"] = CreatePolicyContentReplaceBaseUrl(backendService, policyContent, $"{listCallbackUrl}.basePath"); //Handle the sig property var rewriteElement = policyXMLDoc.Descendants().Where(dd => dd.Name == "rewrite-uri").LastOrDefault(); var rewritetemplate = rewriteElement.Attribute("template"); if (rewritetemplate != null) { var match = Regex.Match(rewritetemplate.Value, "{{(?<name>[-_.a-zA-Z0-9]*)}}"); if (match.Success) { string propname = match.Groups["name"].Value; this.identifiedProperties.Add(new Property() { type = Property.PropertyType.LogicApp, name = propname, extraInfo = listCallbackUrl, apis = new List<string>(new string[] { apiname }) }); } } /* <policies> <inbound> <rewrite-uri id="apim-generated-policy" template="?api-version=2016-06-01&sp=/triggers/request/run&{{orderrequest59a6b4783fb21a7984df42ae}}" /> <set-backend-service id="apim-generated-policy" base-url="https://prod-48.westeurope.logic.azure.com/workflows/bc406236bfff482a836ca4f6caabbb17/triggers/request/paths/invoke" /> <base /> <set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" /> </inbound> <outbound> <base /> </outbound> <backend> <base /> <!-- { "azureResource": { "type": "logicapp", "id": "/subscriptions/c107df29-a4af-4bc9-a733-f88f0eaa4296/resourceGroups/PreDemoTest/providers/Microsoft.Logic/workflows/INT001-GetOrderInfo/triggers/request" } } --> </backend> </policies> */ } else if (reourceType == "funcapp") { /* var logicAppNameMatch = Regex.Match(id, "resourceGroups/(?<resourceGroupName>.*)/providers/Microsoft.Logic/workflows/(?<name>.*)/triggers/(?<triggerName>.*)"); string functionAppName = logicAppNameMatch.Groups["name"].Value; string functionName = logicAppNameMatch.Groups["triggerName"].Value; string functionResourceGroup = logicAppNameMatch.Groups["resourceGroupName"].Value;*/ } } } return commentMatch.Success; } public void PolicyHandeBackendUrl(JObject policy, string apiname, DeploymentTemplate template) { var policyContent = policy["properties"].Value<string>("policyContent"); var policyXMLDoc = XDocument.Parse(policyContent); //find the last backend service and add as parameter var backendService = policyXMLDoc.Descendants().Where(dd => dd.Name == "set-backend-service").LastOrDefault(); if (backendService != null) { if (backendService.Attribute("base-url") != null && !backendService.Attribute("base-url").Value.Contains("{{")) { string baseUrl = backendService.Attribute("base-url").Value; var paramname = template.AddParameter($"api_{apiname}_backendurl", "string", baseUrl); if (replaceSetBackendServiceBaseUrlAsProperty) { policy["properties"]["policyContent"] = CreatePolicyContentReplaceBaseUrlWithProperty(backendService, policyContent, paramname); string id = GetIdFromPolicy(policy); AzureResourceId resourceId = new AzureResourceId(id); var lookFor = $"/service/{resourceId.ValueAfter("service")}"; var index = id.IndexOf(lookFor); var serviceId = id.Substring(0, index + lookFor.Length); var property = new { id = $"{serviceId}/properties/{paramname}", type = "Microsoft.ApiManagement/service/properties", name= paramname, properties = new { displayName = paramname, value = $"[parameters('{paramname}')]", secret = false } }; template.AddProperty(JObject.FromObject(property)); } else { policy["properties"]["policyContent"] = CreatePolicyContentReplaceBaseUrl(backendService, policyContent, $"parameters('{paramname}')"); } } } } private static string GetIdFromPolicy(JObject policy) { var id = policy.Value<string>("id"); if (id != null) return id; var comment = policy.Value<string>("comments"); return comment.Substring(comment.IndexOf("/subscriptions/", StringComparison.InvariantCulture)); } private string CreatePolicyContentReplaceBaseUrl(XElement backendService, string policyContent, string replaceText) { var baseUrl = backendService.Attribute("base-url"); if (baseUrl != null && policyContent.IndexOf(baseUrl.Value) > -1) { int index = policyContent.IndexOf(baseUrl.Value); return "[Concat('" + policyContent.Substring(0, index).Replace("'", "''") + "'," + replaceText + ",'" + policyContent.Substring(index + baseUrl.Value.Length).Replace("'", "''") + "')]"; } return policyContent; } private string CreatePolicyContentReplaceBaseUrlWithProperty(XElement backendService, string policyContent, string parameterName) { var baseUrl = backendService.Attribute("base-url"); if (baseUrl != null && policyContent.IndexOf(baseUrl.Value) > -1) { return policyContent.Replace(baseUrl.Value, $"{{{{{parameterName}}}}}"); } return policyContent; } } } |