tools/DeploymentsSchemaTests/TestSchemaCache.cs (131 lines of code) (raw):
using Azure.Deployments.Core.Components;
using Azure.Deployments.Core.Extensions;
using Azure.Deployments.Core.Resources;
using Azure.Deployments.Templates.Contracts;
using Azure.Deployments.Templates.Export;
using Azure.Deployments.Templates.Extensions;
using Azure.Deployments.Templates.Helpers;
using Azure.Deployments.TemplateSchemas;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace DeploymentsSchemaTests
{
/// <summary>
/// Test schema cache
/// </summary>
internal class TestSchemaCache : INormalizedSchemaCache
{
/// <summary>
/// Initializes the schema cache from a set of file paths.
/// </summary>
/// <param name="filePaths">The file paths to load.</param>
public static INormalizedSchemaCache CreateFromFilePaths(IEnumerable<string> filePaths)
{
var schemaResults = TestSchemaCache.BuildSchemaCacheFromFilePaths(filePaths);
return new TestSchemaCache(schemaResults);
}
private TestSchemaCache(ResultWithErrors<ResourceTypeSchema[]> schemaResults)
{
if (schemaResults.Errors.Any())
{
var errors = schemaResults.Errors
.Where(err => !SchemaLoader.CircularReferenceSchemas.Contains(err.Target))
.ToArray();
if (errors.Any())
{
var errorsJson = JsonConvert.SerializeObject(errors, Formatting.Indented);
throw new InvalidOperationException($"Found errors building normalized cache: {errorsJson}");
}
}
this.schemaCache = schemaResults.Value
.GroupByOrdinalInsensitively(schema => schema.ResourceProviderNamespace)
.ToDictionary(
keySelector: grouping => grouping.Key,
elementSelector: grouping => grouping.ToLookupOrdinalInsensitively(schema => schema.FullyQualifiedResourceType),
comparer: StringComparer.OrdinalIgnoreCase);
}
private IReadOnlyDictionary<string, ILookup<string, ResourceTypeSchema>> schemaCache;
private class OfflineSchema : IResourceProviderSchema
{
public OfflineSchema(string providerNamespace, string apiVersion, JToken schemaContent)
{
this.ResourceProviderNamespace = providerNamespace;
this.SchemaVersion = apiVersion;
this.SchemaContent = schemaContent;
}
public string ResourceProviderNamespace { get; }
public string SchemaVersion { get; }
public JToken SchemaContent { get; }
}
private static readonly Uri SchemaBaseUri = new Uri("https://schema.management.azure.com/schemas/");
private static string GetRelativeSchemaPath(string schemaId)
{
var schemaUri = new Uri(new Uri(schemaId).GetLeftPart(UriPartial.Path));
if (!SchemaBaseUri.IsBaseOf(schemaUri))
{
throw new ArgumentException($"Unable to process schema {schemaUri}");
}
return SchemaBaseUri.MakeRelativeUri(schemaUri).ToString();
}
private static ResultWithErrors<ResourceTypeSchema[]> BuildSchemaCacheFromFilePaths(IEnumerable<string> filePaths)
{
var schemasByPath = filePaths
.Select(File.ReadAllText)
.Select(JObject.Parse)
.ToInsensitiveDictionary(
keySelector: schema => GetRelativeSchemaPath(schema["id"].ToObject<string>()),
elementSelector: schema => schema as JToken);
var externalReferenceSchemasResult = SchemaUtils.GetExternalReferenceSchemas(schemasByPath, SchemaUtils.ExternalReferenceWhitelist.ToArray());
if (externalReferenceSchemasResult.Errors.Any())
{
throw new InvalidOperationException($"Failed to initialize the offline schemas cache");
}
var schemaRefsByFile = SchemaUtils.TopLevelReferenceSchemas
.SelectMany(schemaPath => SchemaUtils.GetExternalReferences(schemasByPath[schemaPath]))
.Select(property => property.Value.ToObject<string>())
.ToLookupOrdinalInsensitively(SchemaUtils.GetFilePathFromUri);
var schemaKeysFound = schemaRefsByFile
.Select(grouping => grouping.Key)
.Where(filePath => !SchemaUtils.ExternalReferenceWhitelist.Contains(filePath))
.Where(filePath => schemasByPath.ContainsKey(filePath))
.ToArray();
var schemaGroups = schemaKeysFound.GroupByInsensitively(keySelector: schemaKey => SchemaUtils.GetSchemaGroupKey(schemaKey));
var schemaNormalizationResults = schemaGroups
.Select(schemaGroup => schemaGroup.ToInsensitiveDictionary(keySelector: schemaKey => schemaKey, elementSelector: schemaKey => schemasByPath[schemaKey]))
.SelectMany(schemaGroup => SchemaUtils.NormalizeAndFilterSchemaGroup(
schemaGroup: schemaGroup,
externalReferenceSchemas: externalReferenceSchemasResult.Value,
schemaRefsByFile: schemaRefsByFile))
.ToInsensitiveDictionary(
keySelector: normalizationResult => normalizationResult.Key,
elementSelector: normalizationResult => normalizationResult.Value);
var resourceTypeSchemasResults = schemaNormalizationResults
.Where(kvp => kvp.Value.Value != null)
.Select(kvp => new OfflineSchema(
providerNamespace: SchemaUtils.GetProviderNamespaceFromSchemaKey(kvp.Key),
apiVersion: SchemaUtils.GetVersionFromSchemaKey(kvp.Key),
schemaContent: kvp.Value.Value))
.Select(schema => schema.GetResourceTypeSchemasResult());
return new ResultWithErrors<ResourceTypeSchema[]>
{
Value = resourceTypeSchemasResults.CollectValues().ToArray(),
Errors = schemaNormalizationResults.Values.CollectErrors()
.ConcatArray(resourceTypeSchemasResults.CollectErrors()),
};
}
/// <inheritdoc/>
public ILookup<string, ResourceTypeSchema> GetSchemasForProvider(string providerNamespace)
{
if (this.schemaCache.TryGetValue(providerNamespace, out var schemaLookup))
{
return schemaLookup;
}
return Enumerable.Empty<ResourceTypeSchema>().ToLookupOrdinalInsensitively(schema => schema.FullyQualifiedResourceType);
}
/// <inheritdoc/>
public IEnumerable<ResourceTypeSchema> GetSchemasForResourceType(string fullyQualifiedResourceType)
{
var providerNamespace = IResourceIdentifiableExtensions.GetNamespaceFromFullyQualifiedType(fullyQualifiedResourceType);
if (!this.schemaCache.TryGetValue(providerNamespace, out var schemaLookup))
{
return Enumerable.Empty<ResourceTypeSchema>();
}
return schemaLookup[fullyQualifiedResourceType];
}
}
}