Skip to content
This repository was archived by the owner on Feb 23, 2024. It is now read-only.

Commit 854fdcc

Browse files
bablulawrenceRupengLiu
authored andcommitted
Added support to extract tags for operations, APIs and products. (#257)
* Added template class for Tags * Added logic for processing single tag * Added logic for extracting multiple tags * Changed code for tag - api operation association to reflect existing style * Added code for api and tags association. Fixed comments and console log for api and operation. * Added code for extracting product and tag associations to product template * Added extractor for tag and fixed empty displayName issue for tag associations * Added code for extracting tags for single api * Implemented product tag extraction for single api * Fixed bug in extracting same tags attached to multiple operations, apis and products
1 parent b996fe0 commit 854fdcc

File tree

7 files changed

+202
-1
lines changed

7 files changed

+202
-1
lines changed

src/APIM_ARMTemplate/apimtemplate/Commands/Extract.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public ExtractCommand()
7373
LoggerExtractor loggerExtractor = new LoggerExtractor();
7474
PolicyExtractor policyExtractor = new PolicyExtractor(fileWriter);
7575
PropertyExtractor propertyExtractor = new PropertyExtractor();
76+
TagExtractor tagExtractor = new TagExtractor();
7677
ProductExtractor productExtractor = new ProductExtractor(fileWriter);
7778
MasterTemplateExtractor masterTemplateExtractor = new MasterTemplateExtractor();
7879

@@ -84,7 +85,9 @@ public ExtractCommand()
8485
Template authorizationServerTemplate = await authorizationServerExtractor.GenerateAuthorizationServersARMTemplateAsync(sourceApim, resourceGroup, singleApiName, apiTemplateResources, policyXMLBaseUrl);
8586
Template loggerTemplate = await loggerExtractor.GenerateLoggerTemplateAsync(sourceApim, resourceGroup, singleApiName, apiTemplateResources, policyXMLBaseUrl);
8687
Template productTemplate = await productExtractor.GenerateProductsARMTemplateAsync(sourceApim, resourceGroup, singleApiName, apiTemplateResources, policyXMLBaseUrl, fileFolder);
88+
List<TemplateResource> productTemplateResources = productTemplate.resources.ToList();
8789
Template namedValueTemplate = await propertyExtractor.GenerateNamedValuesTemplateAsync(sourceApim, resourceGroup, singleApiName, apiTemplateResources, policyXMLBaseUrl);
90+
Template tagTemplate = await tagExtractor.GenerateTagsTemplateAsync(sourceApim, resourceGroup, singleApiName, apiTemplateResources, productTemplateResources, policyXMLBaseUrl);
8891
List<TemplateResource> namedValueResources = namedValueTemplate.resources.ToList();
8992
Template backendTemplate = await backendExtractor.GenerateBackendsARMTemplateAsync(sourceApim, resourceGroup, singleApiName, apiTemplateResources, namedValueResources, policyXMLBaseUrl);
9093

@@ -99,6 +102,7 @@ public ExtractCommand()
99102
fileWriter.WriteJSONToFile(backendTemplate, String.Concat(@fileFolder, fileNames.backends));
100103
fileWriter.WriteJSONToFile(loggerTemplate, String.Concat(@fileFolder, fileNames.loggers));
101104
fileWriter.WriteJSONToFile(namedValueTemplate, String.Concat(@fileFolder, fileNames.namedValues));
105+
fileWriter.WriteJSONToFile(tagTemplate, String.Concat(@fileFolder, fileNames.tags));
102106
fileWriter.WriteJSONToFile(productTemplate, String.Concat(@fileFolder, fileNames.products));
103107
fileWriter.WriteJSONToFile(globalServicePolicyTemplate, String.Concat(@fileFolder, fileNames.globalServicePolicy));
104108

src/APIM_ARMTemplate/apimtemplate/Common/Constants/ResourceTypeConstants.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ internal static class ResourceTypeConstants
88
public const string APIDiagnostic = "Microsoft.ApiManagement/service/apis/diagnostics";
99
public const string APIOperation = "Microsoft.ApiManagement/service/apis/operations";
1010
public const string APIOperationPolicy = "Microsoft.ApiManagement/service/apis/operations/policies";
11+
public const string APIOperationTag = "Microsoft.ApiManagement/service/apis/operations/tags";
12+
public const string APITag = "Microsoft.ApiManagement/service/apis/tags";
1113
public const string APIPolicy = "Microsoft.ApiManagement/service/apis/policies";
1214
public const string APIRelease = "Microsoft.ApiManagement/service/apis/releases";
1315
public const string APISchema = "Microsoft.ApiManagement/service/apis/schemas";
@@ -17,7 +19,9 @@ internal static class ResourceTypeConstants
1719
public const string Logger = "Microsoft.ApiManagement/service/loggers";
1820
public const string ProductAPI = "Microsoft.ApiManagement/service/products/apis";
1921
public const string Product = "Microsoft.ApiManagement/service/products";
22+
public const string ProductTag = "Microsoft.ApiManagement/service/products/tags";
2023
public const string ProductPolicy = "Microsoft.ApiManagement/service/products/policies";
2124
public const string Property = "Microsoft.ApiManagement/service/properties";
25+
public const string Tag = "Microsoft.ApiManagement/service/Tags";
2226
}
2327
}

src/APIM_ARMTemplate/apimtemplate/Common/FileHandlers/FileNameGenerator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public FileNames GenerateFileNames(string apimServiceName)
1616
globalServicePolicy = $@"/{apimServiceName}-globalServicePolicy.template.json",
1717
loggers = $@"/{apimServiceName}-loggers.template.json",
1818
namedValues = $@"/{apimServiceName}-namedValues.template.json",
19+
tags = $@"/{apimServiceName}-tags.template.json",
1920
products = $@"/{apimServiceName}-products.template.json",
2021
parameters = $@"/{apimServiceName}-parameters.json",
2122
linkedMaster = $@"/{apimServiceName}-master.template.json"
@@ -57,6 +58,7 @@ public class FileNames
5758
public string globalServicePolicy { get; set; }
5859
public string loggers { get; set; }
5960
public string namedValues { get; set; }
61+
public string tags { get; set; }
6062
public string products { get; set; }
6163
public string parameters { get; set; }
6264
// linked property outputs 1 master template
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+

2+
namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common
3+
{
4+
5+
public class TagTemplateResource : APITemplateSubResource
6+
{
7+
public TagTemplateProperties properties { get; set; }
8+
}
9+
10+
public class TagTemplateProperties
11+
{
12+
public string displayName {get; set;}
13+
}
14+
}

src/APIM_ARMTemplate/apimtemplate/Extractor/EntityExtractors/APIExtractor.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ public async Task<string> GetOperationPolicyAsync(string ApiManagementName, stri
4747
return await CallApiManagementAsync(azToken, requestUrl);
4848
}
4949

50+
public async Task<string> GetOperationTagsAsync(string ApiManagementName, string ResourceGroupName, string ApiName, string OperationId)
51+
{
52+
(string azToken, string azSubId) = await auth.GetAccessToken();
53+
54+
string requestUrl = string.Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/apis/{4}/operations/{5}/tags?api-version={6}&format=rawxml",
55+
baseUrl, azSubId, ResourceGroupName, ApiManagementName, ApiName, OperationId, GlobalConstants.APIVersion);
56+
57+
return await CallApiManagementAsync(azToken, requestUrl);
58+
}
59+
5060
public async Task<string> GetAPIDetailsAsync(string ApiManagementName, string ResourceGroupName, string ApiName)
5161
{
5262
(string azToken, string azSubId) = await auth.GetAccessToken();
@@ -77,6 +87,15 @@ public async Task<string> GetAPIPolicyAsync(string ApiManagementName, string Res
7787
return await CallApiManagementAsync(azToken, requestUrl);
7888
}
7989

90+
public async Task<string> GetAPITagsAsync(string ApiManagementName, string ResourceGroupName, string ApiName)
91+
{
92+
(string azToken, string azSubId) = await auth.GetAccessToken();
93+
94+
string requestUrl = string.Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/apis/{4}/tags?api-version={5}",
95+
baseUrl, azSubId, ResourceGroupName, ApiManagementName, ApiName, GlobalConstants.APIVersion);
96+
97+
return await CallApiManagementAsync(azToken, requestUrl);
98+
}
8099
public async Task<string> GetAPIDiagnosticsAsync(string ApiManagementName, string ResourceGroupName, string ApiName)
81100
{
82101
(string azToken, string azSubId) = await auth.GetAccessToken();
@@ -238,6 +257,30 @@ public async Task<Template> GenerateAPIsARMTemplateAsync(string apimname, string
238257
templateResources.Add(operationPolicyResource);
239258
}
240259
catch (Exception) { }
260+
261+
262+
// add tags associated with the operation to template
263+
try
264+
{
265+
// pull tags associated with the operation
266+
string apiOperationTags = await GetOperationTagsAsync(apimname, resourceGroup, oApiName, operationName);
267+
JObject oApiOperationTags = JObject.Parse(apiOperationTags);
268+
269+
foreach (var tag in oApiOperationTags["value"])
270+
{
271+
string apiOperationTagName = ((JValue)tag["name"]).Value.ToString();
272+
Console.WriteLine(" - '{0}' Tag association found for {1} operation", apiOperationTagName, operationResourceName);
273+
274+
// convert operation tag association to template resource class
275+
TagTemplateResource operationTagResource = JsonConvert.DeserializeObject<TagTemplateResource>(tag.ToString());
276+
operationTagResource.name = $"[concat(parameters('ApimServiceName'), '/{oApiName}/{operationResourceName}/{apiOperationTagName}')]";
277+
operationTagResource.apiVersion = GlobalConstants.APIVersion;
278+
operationTagResource.scale = null;
279+
operationTagResource.dependsOn = new string[] { $"[resourceId('Microsoft.ApiManagement/service/apis/operations', parameters('ApimServiceName'), '{oApiName}', '{operationResourceName}')]" };
280+
templateResources.Add(operationTagResource);
281+
}
282+
}
283+
catch (Exception) { }
241284
}
242285
#endregion
243286

@@ -269,6 +312,29 @@ public async Task<Template> GenerateAPIsARMTemplateAsync(string apimname, string
269312
catch (Exception) { }
270313
#endregion
271314

315+
// add tags associated with the api to template
316+
try
317+
{
318+
// pull tags associated with the api
319+
string apiTags = await GetAPITagsAsync(apimname, resourceGroup, apiName);
320+
JObject oApiTags = JObject.Parse(apiTags);
321+
322+
foreach (var tag in oApiTags["value"])
323+
{
324+
string apiTagName = ((JValue)tag["name"]).Value.ToString();
325+
Console.WriteLine("'{0}' Tag association found", apiTagName);
326+
327+
// convert associations between api and tags to template resource class
328+
TagTemplateResource apiTagResource = JsonConvert.DeserializeObject<TagTemplateResource>(tag.ToString());
329+
apiTagResource.name = $"[concat(parameters('ApimServiceName'), '/{oApiName}/{apiTagName}')]";
330+
apiTagResource.apiVersion = GlobalConstants.APIVersion;
331+
apiTagResource.scale = null;
332+
apiTagResource.dependsOn = new string[] { $"[resourceId('Microsoft.ApiManagement/service/apis', parameters('ApimServiceName'), '{oApiName}')]" };
333+
templateResources.Add(apiTagResource);
334+
}
335+
}
336+
catch (Exception) { }
337+
272338
// add product api associations to template
273339
#region API Products
274340
try

src/APIM_ARMTemplate/apimtemplate/Extractor/EntityExtractors/ProductExtractor.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ public async Task<string> GetProductPolicyAsync(string ApiManagementName, string
4747
return await CallApiManagementAsync(azToken, requestUrl);
4848
}
4949

50+
51+
public async Task<string> GetProductTagsAsync(string ApiManagementName, string ResourceGroupName, string ProductName)
52+
{
53+
(string azToken, string azSubId) = await auth.GetAccessToken();
54+
55+
string requestUrl = string.Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/products/{4}/tags?api-version={5}&format=rawxml",
56+
baseUrl, azSubId, ResourceGroupName, ApiManagementName, ProductName, GlobalConstants.APIVersion);
57+
58+
return await CallApiManagementAsync(azToken, requestUrl);
59+
}
60+
5061
public async Task<Template> GenerateProductsARMTemplateAsync(string apimname, string resourceGroup, string singleApiName, List<TemplateResource> apiTemplateResources, string policyXMLBaseUrl, string fileFolder)
5162
{
5263
Console.WriteLine("------------------------------------------");
@@ -109,7 +120,30 @@ public async Task<Template> GenerateProductsARMTemplateAsync(string apimname, st
109120
templateResources.Add(productPolicyResource);
110121
}
111122
catch (Exception) { }
112-
}
123+
124+
// add tags associated with the product to template
125+
try
126+
{
127+
// pull tags associated with the product
128+
string productTags = await GetProductTagsAsync(apimname, resourceGroup, productName);
129+
JObject oProductTags = JObject.Parse(productTags);
130+
131+
foreach (var tag in oProductTags["value"])
132+
{
133+
string productTagName = ((JValue)tag["name"]).Value.ToString();
134+
Console.WriteLine(" - '{0}' Tag association found for {1} product", productTagName, productName);
135+
136+
// convert associations between product and tags to template resource class
137+
TagTemplateResource productTagResource = JsonConvert.DeserializeObject<TagTemplateResource>(tag.ToString());
138+
productTagResource.name = $"[concat(parameters('ApimServiceName'), '/{productName}/{productTagName}')]";
139+
productTagResource.apiVersion = GlobalConstants.APIVersion;
140+
productTagResource.scale = null;
141+
productTagResource.dependsOn = new string[] { $"[resourceId('Microsoft.ApiManagement/service/products', parameters('ApimServiceName'), '{productName}')]" };
142+
templateResources.Add(productTagResource);
143+
}
144+
}
145+
catch (Exception) { }
146+
}
113147
}
114148

115149
armTemplate.resources = templateResources.ToArray();
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common;
3+
using System;
4+
using System.Collections.Generic;
5+
using Newtonsoft.Json;
6+
using Newtonsoft.Json.Linq;
7+
using System.Linq;
8+
9+
namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extract
10+
{
11+
public class TagExtractor: EntityExtractor
12+
{
13+
public async Task<string> GetTagsAsync(string ApiManagementName, string ResourceGroupName)
14+
{
15+
(string azToken, string azSubId) = await auth.GetAccessToken();
16+
17+
string requestUrl = string.Format("{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/Tags?api-version={4}",
18+
baseUrl, azSubId, ResourceGroupName, ApiManagementName, GlobalConstants.APIVersion);
19+
20+
return await CallApiManagementAsync(azToken, requestUrl);
21+
}
22+
23+
public async Task<Template> GenerateTagsTemplateAsync(string apimname, string resourceGroup, string singleApiName, List<TemplateResource> apiTemplateResources, List<TemplateResource> productTemplateResources, string policyXMLBaseUrl)
24+
{
25+
Console.WriteLine("------------------------------------------");
26+
Console.WriteLine("Extracting tags from service");
27+
Template armTemplate = GenerateEmptyTemplateWithParameters(policyXMLBaseUrl);
28+
29+
// isolate tag and api operation associations in the case of a single api extraction
30+
var apiOperationTagResources = apiTemplateResources.Where(resource => resource.type == ResourceTypeConstants.APIOperationTag);
31+
32+
// isolate tag and api associations in the case of a single api extraction
33+
var apiTagResources = apiTemplateResources.Where(resource => resource.type == ResourceTypeConstants.APITag);
34+
35+
// isolate product api associations in the case of a single api extraction
36+
var productAPIResources = apiTemplateResources.Where(resource => resource.type == ResourceTypeConstants.ProductAPI);
37+
38+
// isolate tag and product associations in the case of a single api extraction
39+
var productTagResources = productTemplateResources.Where(resource => resource.type == ResourceTypeConstants.ProductTag);
40+
41+
List<TemplateResource> templateResources = new List<TemplateResource>();
42+
43+
// pull all named values (Tags) for service
44+
string Tags = await GetTagsAsync(apimname, resourceGroup);
45+
JObject oTags = JObject.Parse(Tags);
46+
47+
foreach (var extractedTag in oTags["value"])
48+
{
49+
string TagName = ((JValue)extractedTag["name"]).Value.ToString();
50+
51+
// convert returned named value to template resource class
52+
TagTemplateResource TagTemplateResource = JsonConvert.DeserializeObject<TagTemplateResource>(extractedTag.ToString());
53+
TagTemplateResource.name = $"[concat(parameters('ApimServiceName'), '/{TagName}')]";
54+
TagTemplateResource.type = ResourceTypeConstants.Tag;
55+
TagTemplateResource.apiVersion = GlobalConstants.APIVersion;
56+
TagTemplateResource.scale = null;
57+
58+
// only extract the tag if this is a full extraction,
59+
// or in the case of a single api, if it is found in tags associated with the api operations
60+
// or if it is found in tags associated with the api
61+
// or if it is found in tags associated with the products associated with the api
62+
if (singleApiName == null
63+
|| apiOperationTagResources.FirstOrDefault(t => t.name.Contains($"/{TagName}")) != null
64+
|| apiTagResources.FirstOrDefault(t => t.name.Contains($"/{TagName}")) != null
65+
|| (productAPIResources.FirstOrDefault(t => t.name.Contains($"/{singleApiName}")) != null
66+
&& productTagResources.FirstOrDefault(t => t.name.Contains($"/{TagName}")) != null))
67+
{
68+
Console.WriteLine("'{0}' Tag found", TagName);
69+
templateResources.Add(TagTemplateResource);
70+
}
71+
}
72+
73+
armTemplate.resources = templateResources.ToArray();
74+
return armTemplate;
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)