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

Commit 3b04442

Browse files
f-alizadaFarhad Alizada
and
Farhad Alizada
authored
Restructure the API template generation, ordering revisions depends on property (#782)
* Add release template, when revisions generated for single api * Add revision to api name, if not exist. Introduce ApisResource id naming helper. * Add local isCurrent property to api template * Add api releasese file with release resource for each api * Replace explicit dependson parameter for apis in all api related extractors * Add tests for release extractor and api data processor * Add apiRelease parameters file Co-authored-by: Farhad Alizada <[email protected]>
1 parent 3822fcf commit 3b04442

32 files changed

+744
-96
lines changed

src/ArmTemplates/Commands/Executors/ExtractorExecutor.cs

+77-7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.FileHandlers;
1212
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Abstractions;
1313
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.ApiManagementService;
14+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.ApiReleases;
1415
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Apis;
1516
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.ApiVersionSet;
1617
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.AuthorizationServer;
@@ -70,7 +71,8 @@ public class ExtractorExecutor
7071
readonly ISchemaExtractor schemaExtractor;
7172
readonly IOpenIdConnectProviderExtractor openIdConnectProviderExtractor;
7273
readonly IPolicyFragmentsExtractor policyFragmentsExtractor;
73-
74+
readonly IApiReleaseExtractor apiReleaseExtractor;
75+
7476
public ExtractorExecutor(
7577
ILogger<ExtractorExecutor> logger,
7678
IApisClient apisClient,
@@ -95,7 +97,8 @@ public ExtractorExecutor(
9597
IApiManagementServiceExtractor apiManagementServiceExtractor,
9698
ISchemaExtractor schemaExtractor,
9799
IOpenIdConnectProviderExtractor openIdConnectProviderExtractor,
98-
IPolicyFragmentsExtractor policyFragmentsExtractor)
100+
IPolicyFragmentsExtractor policyFragmentsExtractor,
101+
IApiReleaseExtractor apiReleaseExtractor)
99102
{
100103
this.logger = logger;
101104
this.apisClient = apisClient;
@@ -121,6 +124,7 @@ public ExtractorExecutor(
121124
this.schemaExtractor = schemaExtractor;
122125
this.openIdConnectProviderExtractor = openIdConnectProviderExtractor;
123126
this.policyFragmentsExtractor = policyFragmentsExtractor;
127+
this.apiReleaseExtractor = apiReleaseExtractor;
124128
}
125129

126130
/// <summary>
@@ -151,7 +155,8 @@ public static ExtractorExecutor BuildExtractorExecutor(
151155
IApiManagementServiceExtractor apiManagementServiceExtractor = null,
152156
ISchemaExtractor schemaExtractor = null,
153157
IOpenIdConnectProviderExtractor openIdConnectProviderExtractor = null,
154-
IPolicyFragmentsExtractor policyFragmentsExtractor = null)
158+
IPolicyFragmentsExtractor policyFragmentsExtractor = null,
159+
IApiReleaseExtractor apiReleaseExtractor = null)
155160
=> new ExtractorExecutor(
156161
logger,
157162
apisClient,
@@ -176,7 +181,8 @@ public static ExtractorExecutor BuildExtractorExecutor(
176181
apiManagementServiceExtractor,
177182
schemaExtractor,
178183
openIdConnectProviderExtractor,
179-
policyFragmentsExtractor);
184+
policyFragmentsExtractor,
185+
apiReleaseExtractor);
180186

181187
public void SetExtractorParameters(ExtractorParameters extractorParameters)
182188
{
@@ -478,7 +484,8 @@ public async Task GenerateResourceParametersFiles(
478484
Template<IdentityProviderResources> identityProviderTemplate = null,
479485
Template<SchemaTemplateResources> schemaTemplate = null,
480486
Template<OpenIdConnectProviderResources> openIdConnectProviderTemplate = null,
481-
Template<PolicyFragmentsResources> policyFragmentsTemplate = null)
487+
Template<PolicyFragmentsResources> policyFragmentsTemplate = null,
488+
Template<ApiReleaseTemplateResources> apiReleaseTemplate = null)
482489
{
483490
this.RenameExistingParametersDirectory(baseFilesGenerationDirectory);
484491

@@ -498,6 +505,7 @@ public async Task GenerateResourceParametersFiles(
498505
await this.GenerateResourceParametersFile(baseFilesGenerationDirectory, this.extractorParameters.FileNames.SchemaParameters, schemaTemplate, mainParametersTemplate);
499506
await this.GenerateResourceParametersFile(baseFilesGenerationDirectory, this.extractorParameters.FileNames.OpenIdConnectProvidersParameters, openIdConnectProviderTemplate, mainParametersTemplate);
500507
await this.GenerateResourceParametersFile(baseFilesGenerationDirectory, this.extractorParameters.FileNames.PolicyFragmentsParameters, policyFragmentsTemplate, mainParametersTemplate);
508+
await this.GenerateResourceParametersFile(baseFilesGenerationDirectory, this.extractorParameters.FileNames.ApiReleaseParameters, apiReleaseTemplate, mainParametersTemplate);
501509
}
502510

503511
public async Task GenerateResourceParametersFile<TTemplateResource>(string baseFilesGenerationDirectory, string fileName, Template<TTemplateResource> resourceTemplate, Template mainParametersTemplate) where TTemplateResource : ITemplateResources, new()
@@ -901,6 +909,57 @@ await FileWriter.SaveAsJsonAsync(
901909
}
902910

903911
/// <summary>
912+
/// Generates api release template in the desired folder
913+
/// </summary>
914+
/// <param name="baseFilesGenerationDirectory">name of base folder where to save output files</param>
915+
/// <returns>generated api release template</returns>
916+
public async Task<Template<ApiReleaseTemplateResources>> GenerateApiReleaseTemplateAsync(string apiName, string baseFilesGenerationDirectory)
917+
{
918+
this.logger.LogInformation("Started generation of api release template...");
919+
920+
var apiReleaseTemplate = this.apiReleaseExtractor.GenerateSingleApiReleaseTemplate(apiName, this.extractorParameters);
921+
922+
if (apiReleaseTemplate?.HasResources() == true)
923+
{
924+
await FileWriter.SaveAsJsonAsync(
925+
apiReleaseTemplate,
926+
directory: baseFilesGenerationDirectory,
927+
fileName: this.extractorParameters.FileNames.ApiRelease);
928+
}
929+
930+
this.logger.LogInformation("Finished generation of apiRelease template...");
931+
return apiReleaseTemplate;
932+
}
933+
934+
/// <summary>
935+
/// Generates api release template in the desired folder
936+
/// </summary>
937+
/// <param name="baseFilesGenerationDirectory">name of base folder where to save output files</param>
938+
/// <returns>generated api release template</returns>
939+
public async Task<Template<ApiReleaseTemplateResources>> GenerateApiReleasesTemplateAsync(string baseFilesGenerationDirectory)
940+
{
941+
if (!string.IsNullOrEmpty(this.extractorParameters.SingleApiName) || !this.extractorParameters.MultipleApiNames.IsNullOrEmpty())
942+
{
943+
this.logger.LogInformation("Skip generation of api releases template: not all apis are extracted");
944+
return null;
945+
}
946+
947+
this.logger.LogInformation("Started generation of api releases template...");
948+
949+
var apiReleaseTemplate = await this.apiReleaseExtractor.GenerateCurrentApiReleaseTemplate(this.extractorParameters);
950+
951+
if (apiReleaseTemplate?.HasResources() == true)
952+
{
953+
await FileWriter.SaveAsJsonAsync(
954+
apiReleaseTemplate,
955+
directory: baseFilesGenerationDirectory,
956+
fileName: this.extractorParameters.FileNames.ApiRelease);
957+
}
958+
959+
this.logger.LogInformation("Finished generation of apiReleases template...");
960+
return apiReleaseTemplate;
961+
}
962+
904963
/// Generates policy fragments templates in the desired folder
905964
/// </summary>
906965
/// <param name="baseFilesGenerationDirectory">name of base folder where to save output files</param>
@@ -1034,14 +1093,17 @@ async Task GenerateSingleAPIWithRevisionsTemplates()
10341093
this.logger.LogInformation("Extracting singleAPI {0} with revisions", this.extractorParameters.SingleApiName);
10351094

10361095
string currentRevision = null;
1096+
bool generateSingleApiReleaseTemplate;
10371097
List<string> revList = new List<string>();
10381098

10391099
await foreach (var apiRevision in this.apiRevisionExtractor.GetApiRevisionsAsync(this.extractorParameters.SingleApiName, this.extractorParameters))
10401100
{
1101+
generateSingleApiReleaseTemplate = false;
10411102
var apiRevisionName = apiRevision.ApiId.Split("/")[2];
10421103
if (apiRevision.IsCurrent)
10431104
{
10441105
currentRevision = apiRevisionName;
1106+
generateSingleApiReleaseTemplate = true;
10451107
}
10461108

10471109
// creating a folder for this api revision
@@ -1050,6 +1112,11 @@ async Task GenerateSingleAPIWithRevisionsTemplates()
10501112
revList.Add(apiRevisionName);
10511113

10521114
await this.GenerateTemplates(revFileFolder, singleApiName: apiRevisionName);
1115+
1116+
if (generateSingleApiReleaseTemplate)
1117+
{
1118+
await this.GenerateApiReleaseTemplateAsync(apiRevisionName, revFileFolder);
1119+
}
10531120
}
10541121

10551122
if (currentRevision is null)
@@ -1088,9 +1155,9 @@ async Task GenerateTemplates(
10881155
}
10891156

10901157
var apisToExtract = await this.GetApiNamesToExtract(singleApiName, multipleApiNames);
1091-
10921158
// generate different templates using extractors and write to output
10931159
apiTemplate = apiTemplate ?? await this.GenerateApiTemplateAsync(singleApiName, multipleApiNames, baseFilesGenerationDirectory);
1160+
10941161
var globalServicePolicyTemplate = await this.GeneratePolicyTemplateAsync(baseFilesGenerationDirectory);
10951162
var productApiTemplate = await this.GenerateProductApisTemplateAsync(singleApiName, multipleApiNames, baseFilesGenerationDirectory);
10961163
var productTemplate = await this.GenerateProductsTemplateAsync(singleApiName, baseFilesGenerationDirectory, apiTemplate.TypedResources.ApiProducts);
@@ -1106,9 +1173,11 @@ async Task GenerateTemplates(
11061173
var openIdConnectProviderTemplate = await this.GenerateOpenIdConnectProviderTemplateAsync(baseFilesGenerationDirectory);
11071174
var schemasTempate = await this.GenerateSchemasTemplateAsync(baseFilesGenerationDirectory);
11081175
var policyFragmentTemplate = await this.GeneratePolicyFragmentsTemplateAsync(apiTemplate.TypedResources.GetAllPolicies(), baseFilesGenerationDirectory);
1176+
var apiReleasesTemplate = await this.GenerateApiReleasesTemplateAsync(baseFilesGenerationDirectory);
11091177
await this.GenerateGatewayTemplateAsync(singleApiName, baseFilesGenerationDirectory);
11101178
await this.GenerateGatewayApiTemplateAsync(singleApiName, multipleApiNames, baseFilesGenerationDirectory);
11111179
await this.GenerateApiManagementServiceTemplate(baseFilesGenerationDirectory);
1180+
11121181
var parametersTemplate = await this.GenerateParametersTemplateAsync(apisToExtract, loggerTemplate.TypedResources, backendTemplate.TypedResources, namedValueTemplate.TypedResources, identityProviderTemplate.TypedResources, openIdConnectProviderTemplate.TypedResources, baseFilesGenerationDirectory);
11131182

11141183
await this.GenerateResourceParametersFiles(
@@ -1129,7 +1198,8 @@ await this.GenerateResourceParametersFiles(
11291198
identityProviderTemplate: identityProviderTemplate,
11301199
openIdConnectProviderTemplate: openIdConnectProviderTemplate,
11311200
schemaTemplate: schemasTempate,
1132-
policyFragmentsTemplate: policyFragmentTemplate);
1201+
policyFragmentsTemplate: policyFragmentTemplate,
1202+
apiReleaseTemplate: apiReleasesTemplate);
11331203

11341204
await this.GenerateMasterTemplateAsync(
11351205
baseFilesGenerationDirectory,

src/ArmTemplates/Common/API/Clients/Abstractions/IApisClient.cs

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public interface IApisClient
1616

1717
Task<List<ApiTemplateResource>> GetAllAsync(ExtractorParameters extractorParameters);
1818

19+
Task<List<ApiTemplateResource>> GetAllCurrentAsync(ExtractorParameters extractorParameters);
20+
1921
Task<List<ApiTemplateResource>> GetAllLinkedToProductAsync(string productName, ExtractorParameters extractorParameters);
2022

2123
Task<List<ApiTemplateResource>> GetAllLinkedToGatewayAsync(string gatewayName, ExtractorParameters extractorParameters);

src/ArmTemplates/Common/API/Clients/Apis/ApisClient.cs

+35-5
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,23 @@
1010
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Constants;
1111
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Templates.Apis;
1212
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Models;
13+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Extractor.Utilities.DataProcessors.Absctraction;
1314

1415
namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.API.Clients.Apis
1516
{
1617
public class ApisClient : ApiClientBase, IApisClient
1718
{
1819
const string GetSingleApiRequest = "{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/apis/{4}?api-version={5}";
1920
const string GetAllApisRequest = "{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/apis?api-version={4}";
21+
const string GetAllCurrentApisRequest = "{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/apis?api-version={4}&$filter=isCurrent";
2022
const string GetAllApisLinkedToProductRequest = "{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/products/{4}/apis?api-version={5}";
2123
const string GetApisLinkedToGatewayRequest = "{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.ApiManagement/service/{3}/gateways/{4}/apis?api-version={5}";
2224

23-
public ApisClient(IHttpClientFactory httpClientFactory) : base(httpClientFactory)
25+
readonly IApiDataProcessor apiDataProcessor;
26+
27+
public ApisClient(IHttpClientFactory httpClientFactory, IApiDataProcessor apiDataProcessor) : base(httpClientFactory)
2428
{
29+
this.apiDataProcessor = apiDataProcessor;
2530
}
2631

2732
public async Task<ApiTemplateResource> GetSingleAsync(string apiName, ExtractorParameters extractorParameters)
@@ -30,7 +35,10 @@ public async Task<ApiTemplateResource> GetSingleAsync(string apiName, ExtractorP
3035
string requestUrl = string.Format(GetSingleApiRequest,
3136
this.BaseUrl, azSubId, extractorParameters.ResourceGroup, extractorParameters.SourceApimName, apiName, GlobalConstants.ApiVersion);
3237

33-
return await this.GetResponseAsync<ApiTemplateResource>(azToken, requestUrl);
38+
var api = await this.GetResponseAsync<ApiTemplateResource>(azToken, requestUrl);
39+
this.apiDataProcessor.ProcessSingleData(api);
40+
41+
return api;
3442
}
3543

3644
public async Task<List<ApiTemplateResource>> GetAllAsync(ExtractorParameters extractorParameters)
@@ -40,7 +48,23 @@ public async Task<List<ApiTemplateResource>> GetAllAsync(ExtractorParameters ext
4048
string requestUrl = string.Format(GetAllApisRequest,
4149
this.BaseUrl, azSubId, extractorParameters.ResourceGroup, extractorParameters.SourceApimName, GlobalConstants.ApiVersion);
4250

43-
return await this.GetPagedResponseAsync<ApiTemplateResource>(azToken, requestUrl);
51+
var apis = await this.GetPagedResponseAsync<ApiTemplateResource>(azToken, requestUrl);
52+
this.apiDataProcessor.ProcessData(apis);
53+
54+
return apis;
55+
}
56+
57+
public async Task<List<ApiTemplateResource>> GetAllCurrentAsync(ExtractorParameters extractorParameters)
58+
{
59+
var (azToken, azSubId) = await this.Auth.GetAccessToken();
60+
61+
string requestUrl = string.Format(GetAllCurrentApisRequest,
62+
this.BaseUrl, azSubId, extractorParameters.ResourceGroup, extractorParameters.SourceApimName, GlobalConstants.ApiVersion);
63+
64+
var apis = await this.GetPagedResponseAsync<ApiTemplateResource>(azToken, requestUrl);
65+
this.apiDataProcessor.ProcessData(apis);
66+
67+
return apis;
4468
}
4569

4670
public async Task<List<ApiTemplateResource>> GetAllLinkedToProductAsync(string productName, ExtractorParameters extractorParameters)
@@ -49,7 +73,10 @@ public async Task<List<ApiTemplateResource>> GetAllLinkedToProductAsync(string p
4973
string requestUrl = string.Format(GetAllApisLinkedToProductRequest,
5074
this.BaseUrl, azSubId, extractorParameters.ResourceGroup, extractorParameters.SourceApimName, productName, GlobalConstants.ApiVersion);
5175

52-
return await this.GetPagedResponseAsync<ApiTemplateResource>(azToken, requestUrl);
76+
var apis = await this.GetPagedResponseAsync<ApiTemplateResource>(azToken, requestUrl);
77+
this.apiDataProcessor.ProcessData(apis);
78+
79+
return apis;
5380
}
5481

5582
public async Task<List<ApiTemplateResource>> GetAllLinkedToGatewayAsync(string gatewayName, ExtractorParameters extractorParameters)
@@ -58,7 +85,10 @@ public async Task<List<ApiTemplateResource>> GetAllLinkedToGatewayAsync(string g
5885
string requestUrl = string.Format(GetApisLinkedToGatewayRequest,
5986
this.BaseUrl, azSubId, extractorParameters.ResourceGroup, extractorParameters.SourceApimName, gatewayName, GlobalConstants.ApiVersion);
6087

61-
return await this.GetPagedResponseAsync<ApiTemplateResource>(azToken, requestUrl);
88+
var apis = await this.GetPagedResponseAsync<ApiTemplateResource>(azToken, requestUrl);
89+
this.apiDataProcessor.ProcessData(apis);
90+
91+
return apis;
6292
}
6393
}
6494
}

src/ArmTemplates/Common/Extensions/NamingHelper.cs

+22
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System.Linq;
77
using System.Text.RegularExpressions;
8+
using Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Constants;
89

910
namespace Microsoft.Azure.Management.ApiManagement.ArmTemplates.Common.Extensions
1011
{
@@ -13,6 +14,11 @@ public static class NamingHelper
1314
static readonly Regex ExcludeOtherFromLettersAndDigitsRegex = new Regex("[^a-zA-Z0-9]");
1415
static readonly Regex ExcludeOtherFromAlphaNumericsAndHyphensRegex = new Regex("[^a-zA-Z0-9-]");
1516

17+
// ValidDeployment name is the string following the pattern: '^[-\w\._\(\)]+$'. https://docs.microsoft.com/en-us/rest/api/resources/deployments/create-or-update?tabs=HTTP#uri-parameters
18+
// ExcludeOtherFromValidDeploymentNameCharsRegex matches any character other than [alphanumerics + '-' + '.' + '(' + ')' + '_'];
19+
// Example: 'template;rev=1.json' string will match the ';' and '=' chars
20+
static readonly Regex ExcludeOtherFromValidDeploymentNameCharsRegex = new Regex(@"[^-\w\._\(\)]");
21+
1622
public static string GetSubstringBetweenTwoCharacters(char left, char right, string fullString)
1723
{
1824
var regex = new Regex($"(?<={left})(.*?)(?={right})");
@@ -60,5 +66,21 @@ public static string GenerateParametrizedResourceName(string parameterName, stri
6066
{
6167
return $"[concat(parameters('{parameterName}'), '/{resourceName}')]";
6268
}
69+
70+
public static string GenerateValidDeploymentFileName(string fileName)
71+
{
72+
if (string.IsNullOrEmpty(fileName))
73+
{
74+
return string.Empty;
75+
}
76+
77+
var resourceName = ExcludeOtherFromValidDeploymentNameCharsRegex.Replace(fileName, "-");
78+
return resourceName;
79+
}
80+
81+
public static string GenerateApisResourceId(string apiName)
82+
{
83+
return $"[resourceId('{ResourceTypeConstants.API}', parameters('{ParameterNames.ApimServiceName}'), '{apiName}')]";
84+
}
6385
}
6486
}

0 commit comments

Comments
 (0)