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

Commit 7911271

Browse files
authored
When extracting single API only extract named values used the API (#558)
1 parent 834617a commit 7911271

File tree

4 files changed

+146
-23
lines changed

4 files changed

+146
-23
lines changed

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,5 +179,69 @@ public bool DoesBackendReferenceNamedValue(TemplateResource namedValueResource,
179179
|| namedValue == backendTemplateResource.properties.description
180180
|| namedValue == backendTemplateResource.properties.title);
181181
}
182+
183+
public async Task<bool> IsNamedValueUsedInBackends(string apimname, string resourceGroup, string singleApiName, List<TemplateResource> apiTemplateResources, Extractor exc, string propertyName, string propertyDisplayName)
184+
{
185+
// isolate api and operation policy resources in the case of a single api extraction, as they may reference backends
186+
var policyResources = apiTemplateResources.Where(resource => (resource.type == ResourceTypeConstants.APIPolicy || resource.type == ResourceTypeConstants.APIOperationPolicy || resource.type == ResourceTypeConstants.ProductPolicy));
187+
var emptyNamedValueResources = new List<TemplateResource>();
188+
189+
// pull all backends for service
190+
JObject oBackends = new JObject();
191+
int skipNumberOfBackends = 0;
192+
193+
do
194+
{
195+
string backends = await GetBackendsAsync(apimname, resourceGroup, skipNumberOfBackends);
196+
oBackends = JObject.Parse(backends);
197+
198+
foreach (var item in oBackends["value"])
199+
{
200+
var content = item.ToString();
201+
202+
// check if backend references the named value, credentials for example
203+
if (content.Contains(string.Concat("{{", propertyName, "}}")) || content.Contains(string.Concat("{{", propertyDisplayName, "}}")))
204+
{
205+
//only true if this is a full extraction, or in the case of a single api, if it is referenced by one of the API policies
206+
if (singleApiName == null)
207+
{
208+
return true;
209+
}
210+
else
211+
{
212+
// is this backend related to the single api?
213+
// is backend used in the extracted policies for this API
214+
// if backend id is referenced in policy
215+
// or a named value is referenced in policy to a backend, we have already checked the policy for named value.
216+
217+
// check if this backend is used by any of the policies extracted
218+
string backendName = ((JValue)item["name"]).Value.ToString();
219+
string backend = await GetBackendDetailsAsync(apimname, resourceGroup, backendName);
220+
221+
// convert returned backend to template resource class
222+
BackendTemplateResource backendTemplateResource = JsonConvert.DeserializeObject<BackendTemplateResource>(backend);
223+
224+
// we have already checked if the named value is used in a policy, we just need to confirm if the backend is referenced by this single api within the policy file
225+
// this is why an empty named values must be passed to this method for validation
226+
foreach (PolicyTemplateResource policyTemplateResource in policyResources)
227+
{
228+
string policyContent = ExtractorUtils.GetPolicyContent(exc, policyTemplateResource);
229+
230+
if (DoesPolicyReferenceBackend(policyContent, emptyNamedValueResources, backendName, backendTemplateResource))
231+
{
232+
// dont need to go through all policies and backends if the named values has already been found
233+
return true;
234+
}
235+
}
236+
}
237+
}
238+
}
239+
240+
skipNumberOfBackends += GlobalConstants.NumOfRecords;
241+
}
242+
while (oBackends["nextLink"] != null);
243+
244+
return false;
245+
}
182246
}
183247
}

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

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,8 @@ public string[] GenerateAPIRevisionDependencies(string curRevTemplate)
440440
public async Task<Template> CreateMasterTemplateParameterValues(List<string> apisToExtract, Extractor exc,
441441
Dictionary<string, object> apiLoggerId,
442442
Dictionary<string, string> loggerResourceIds,
443-
Dictionary<string, BackendApiParameters> backendParams)
443+
Dictionary<string, BackendApiParameters> backendParams,
444+
List<TemplateResource> propertyResources)
444445
{
445446
// used to create the parameter values for use in parameters file
446447
// create empty template
@@ -523,14 +524,20 @@ public async Task<Template> CreateMasterTemplateParameterValues(List<string> api
523524
{
524525
JToken oProperty = JObject.Parse(extractedProperty);
525526
string propertyName = ((JValue)oProperty["name"]).Value.ToString();
526-
string fullPropertyResource = await pExc.GetPropertyDetailsAsync(exc.sourceApimName, exc.resourceGroup, propertyName);
527-
PropertyTemplateResource propertyTemplateResource = JsonConvert.DeserializeObject<PropertyTemplateResource>(fullPropertyResource);
528-
//Only add the property if it is not controlled by keyvault
529-
if (propertyTemplateResource?.properties.keyVault == null)
527+
528+
// check if the property has been extracted as it is being used in a policy or backend
529+
if (propertyResources.Count(item => item.name.Contains(propertyName)) > 0)
530530
{
531-
string propertyValue = propertyTemplateResource.properties.value;
532-
string validPName = ExtractorUtils.GenValidParamName(propertyName, ParameterPrefix.Property);
533-
namedValues.Add(validPName, propertyValue);
531+
string fullPropertyResource = await pExc.GetPropertyDetailsAsync(exc.sourceApimName, exc.resourceGroup, propertyName);
532+
PropertyTemplateResource propertyTemplateResource = JsonConvert.DeserializeObject<PropertyTemplateResource>(fullPropertyResource);
533+
534+
//Only add the property if it is not controlled by keyvault
535+
if (propertyTemplateResource?.properties.keyVault == null)
536+
{
537+
string propertyValue = propertyTemplateResource.properties.value;
538+
string validPName = ExtractorUtils.GenValidParamName(propertyName, ParameterPrefix.Property);
539+
namedValues.Add(validPName, propertyValue);
540+
}
534541
}
535542
}
536543
TemplateObjectParameterProperties namedValueProperties = new TemplateObjectParameterProperties()
@@ -549,13 +556,18 @@ public async Task<Template> CreateMasterTemplateParameterValues(List<string> api
549556
{
550557
JToken oProperty = JObject.Parse(extractedProperty);
551558
string propertyName = ((JValue)oProperty["name"]).Value.ToString();
552-
string fullPropertyResource = await pExc.GetPropertyDetailsAsync(exc.sourceApimName, exc.resourceGroup, propertyName);
553-
PropertyTemplateResource propertyTemplateResource = JsonConvert.DeserializeObject<PropertyTemplateResource>(fullPropertyResource);
554-
if (propertyTemplateResource?.properties.keyVault != null)
559+
560+
// check if the property has been extracted as it is being used in a policy or backend
561+
if (propertyResources.Count(item => item.name.Contains(propertyName)) > 0)
555562
{
556-
string propertyValue = propertyTemplateResource.properties.keyVault.secretIdentifier;
557-
string validPName = ExtractorUtils.GenValidParamName(propertyName, ParameterPrefix.Property);
558-
keyVaultNamedValues.Add(validPName, propertyValue);
563+
string fullPropertyResource = await pExc.GetPropertyDetailsAsync(exc.sourceApimName, exc.resourceGroup, propertyName);
564+
PropertyTemplateResource propertyTemplateResource = JsonConvert.DeserializeObject<PropertyTemplateResource>(fullPropertyResource);
565+
if (propertyTemplateResource?.properties.keyVault != null)
566+
{
567+
string propertyValue = propertyTemplateResource.properties.keyVault.secretIdentifier;
568+
string validPName = ExtractorUtils.GenValidParamName(propertyName, ParameterPrefix.Property);
569+
keyVaultNamedValues.Add(validPName, propertyValue);
570+
}
559571
}
560572
}
561573
TemplateObjectParameterProperties keyVaultNamedValueProperties = new TemplateObjectParameterProperties()

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

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

50-
public async Task<Template> GenerateNamedValuesTemplateAsync(string singleApiName, List<TemplateResource> apiTemplateResources, Extractor exc)
50+
public async Task<Template> GenerateNamedValuesTemplateAsync(string singleApiName, List<TemplateResource> apiTemplateResources, Extractor exc, BackendExtractor backendExtractor, List<TemplateResource> loggerTemplateResources)
5151
{
5252
Template armTemplate = GenerateEmptyPropertyTemplateWithParameters();
5353

@@ -82,6 +82,9 @@ public async Task<Template> GenerateNamedValuesTemplateAsync(string singleApiNam
8282
// pull all named values (properties) for service
8383
string[] properties = await GetPropertiesAsync(exc.sourceApimName, exc.resourceGroup);
8484

85+
// isolate api and operation policy resources in the case of a single api extraction, as they may reference named value
86+
var policyResources = apiTemplateResources.Where(resource => (resource.type == ResourceTypeConstants.APIPolicy || resource.type == ResourceTypeConstants.APIOperationPolicy || resource.type == ResourceTypeConstants.ProductPolicy));
87+
8588
foreach (var extractedProperty in properties)
8689
{
8790
JToken oProperty = JObject.Parse(extractedProperty);
@@ -119,14 +122,58 @@ public async Task<Template> GenerateNamedValuesTemplateAsync(string singleApiNam
119122
}
120123
else
121124
{
122-
// TODO - if the user is executing a single api, extract all the named values used in the template resources
123-
Console.WriteLine("'{0}' Named value found", propertyName);
124-
templateResources.Add(propertyTemplateResource);
125-
};
125+
// if the user is executing a single api, extract all the named values used in the template resources
126+
bool foundInPolicy = DoesPolicyReferenceNamedValue(exc, policyResources, propertyName, propertyTemplateResource);
127+
bool foundInBackEnd = await backendExtractor.IsNamedValueUsedInBackends(exc.sourceApimName, exc.resourceGroup, singleApiName, apiTemplateResources, exc, propertyName, propertyTemplateResource.properties.displayName);
128+
bool foundInLogger = DoesLoggerReferenceNamedValue(loggerTemplateResources, propertyName, propertyTemplateResource);
129+
130+
// check if named value is referenced in a backend
131+
if (foundInPolicy || foundInBackEnd || foundInLogger)
132+
{
133+
// named value was used in policy, extract it
134+
Console.WriteLine("'{0}' Named value found", propertyName);
135+
templateResources.Add(propertyTemplateResource);
136+
}
137+
}
126138
}
127139

128140
armTemplate.resources = templateResources.ToArray();
129141
return armTemplate;
130142
}
143+
144+
private bool DoesPolicyReferenceNamedValue(Extractor exc, IEnumerable<TemplateResource> policyResources, string propertyName, PropertyTemplateResource propertyTemplateResource)
145+
{
146+
// check if named value is referenced in a policy file
147+
foreach (PolicyTemplateResource policyTemplateResource in policyResources)
148+
{
149+
string policyContent = ExtractorUtils.GetPolicyContent(exc, policyTemplateResource);
150+
151+
if (policyContent.Contains(string.Concat("{{", propertyTemplateResource.properties.displayName, "}}")) || policyContent.Contains(string.Concat("{{", propertyName, "}}")))
152+
{
153+
// dont need to go through all policies if the named value has already been found
154+
return true;
155+
}
156+
}
157+
return false;
158+
}
159+
160+
private bool DoesLoggerReferenceNamedValue(IEnumerable<TemplateResource> loggerTemplateResources, string propertyName, PropertyTemplateResource propertyTemplateResource)
161+
{
162+
foreach(LoggerTemplateResource logger in loggerTemplateResources)
163+
{
164+
if (logger.properties.credentials != null)
165+
{
166+
if ((!string.IsNullOrEmpty(logger.properties.credentials.connectionString) && logger.properties.credentials.connectionString.Contains(propertyName)) ||
167+
(!string.IsNullOrEmpty(logger.properties.credentials.instrumentationKey) && logger.properties.credentials.instrumentationKey.Contains(propertyName)) ||
168+
(!string.IsNullOrEmpty(logger.properties.credentials.connectionString) && logger.properties.credentials.connectionString.Contains(propertyTemplateResource.properties.displayName)) ||
169+
(!string.IsNullOrEmpty(logger.properties.credentials.instrumentationKey) && logger.properties.credentials.instrumentationKey.Contains(propertyTemplateResource.properties.displayName)))
170+
{
171+
// dont need to go through all loggers if the named value has already been found
172+
return true;
173+
}
174+
}
175+
}
176+
return false;
177+
}
131178
}
132179
}

src/APIM_ARMTemplate/apimtemplate/Extractor/Utilities/ExtractorUtils.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,22 @@ public static async Task GenerateTemplates(
107107
Template productAPITemplate = await productAPIExtractor.GenerateAPIProductsARMTemplateAsync(singleApiName, multipleApiNames, exc);
108108
Template apiTagTemplate = await apiTagExtractor.GenerateAPITagsARMTemplateAsync(singleApiName, multipleApiNames, exc);
109109
List<TemplateResource> productTemplateResources = productTemplate.resources.ToList();
110-
Template namedValueTemplate = await propertyExtractor.GenerateNamedValuesTemplateAsync(singleApiName, apiTemplateResources, exc);
110+
List<TemplateResource> loggerResources = loggerTemplate.resources.ToList();
111+
Template namedValueTemplate = await propertyExtractor.GenerateNamedValuesTemplateAsync(singleApiName, apiTemplateResources, exc, backendExtractor, loggerResources);
111112
Template tagTemplate = await tagExtractor.GenerateTagsTemplateAsync(sourceApim, resourceGroup, singleApiName, apiTemplateResources, productTemplateResources, policyXMLBaseUrl, policyXMLSasToken);
112113
List<TemplateResource> namedValueResources = namedValueTemplate.resources.ToList();
113114

114115
Tuple<Template, Dictionary<string, BackendApiParameters>> backendResult = await backendExtractor.GenerateBackendsARMTemplateAsync(sourceApim, resourceGroup, singleApiName, apiTemplateResources, namedValueResources, exc);
115116

116117
Dictionary<string, string> loggerResourceIds = null;
117118
if (exc.paramLogResourceId)
118-
{
119-
List<TemplateResource> loggerResources = loggerTemplate.resources.ToList();
119+
{
120120
loggerResourceIds = loggerExtractor.GetAllLoggerResourceIds(loggerResources);
121121
loggerTemplate = loggerExtractor.SetLoggerResourceId(loggerTemplate);
122122
}
123123

124124
// create parameters file
125-
Template templateParameters = await masterTemplateExtractor.CreateMasterTemplateParameterValues(apisToExtract, exc, apiLoggerId, loggerResourceIds, backendResult.Item2);
125+
Template templateParameters = await masterTemplateExtractor.CreateMasterTemplateParameterValues(apisToExtract, exc, apiLoggerId, loggerResourceIds, backendResult.Item2, namedValueResources);
126126

127127
// write templates to output file location
128128
string apiFileName = fileNameGenerator.GenerateExtractorAPIFileName(singleApiName, fileNames.baseFileName);

0 commit comments

Comments
 (0)