SamplesV1/ADFSecurePublish/AdfKeyVaultDeployment/AdfFileHelper.cs (234 lines of code) (raw):

using Newtonsoft.Json.Linq; using Newtonsoft.Json.Schema; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.ADF.Deployment.AdfKeyVaultDeployment.Models; namespace Microsoft.ADF.Deployment.AdfKeyVaultDeployment { public enum FileType { LinkedService, Table, Pipeline, Unknown } /// <summary> /// This class finds information on the ADF file types. It uses the ADF json schemas in order to determine which files they are. /// It also performs resolutions of deployment settings if specified in a deployment config and Key Vault tokens from the chosen Key Vault /// </summary> /// <seealso cref="Microsoft.ADF.Deployment.AdfKeyVaultDeployment.IAdfFileHelper" /> public class AdfFileHelper : IAdfFileHelper { const string linkedServiceSchema = "http://datafactories.schema.management.azure.com/schemas/2015-09-01/Microsoft.DataFactory.LinkedService.json"; const string tableSchema = "http://datafactories.schema.management.azure.com/schemas/2015-09-01/Microsoft.DataFactory.Table.json"; const string configSchema = "http://datafactories.schema.management.azure.com/vsschemas/V1/Microsoft.DataFactory.Config.json"; private JSchema jsonLinkedServiceSchema; private JSchema jsonTableSchema; private JSchema jsonPipelineSchema; private JSchema jsonConfigSchema; private IKeyVaultResolver keyVaultResolver; private ILogger logger; public AdfFileHelper(IKeyVaultResolver keyVaultResolver, ILogger logger) { this.keyVaultResolver = keyVaultResolver; this.logger = logger; } public async Task GetSchemas(IHttpClient httpClient) { // If publishing a second time, no need to get the schemas again if (jsonLinkedServiceSchema == null) { var tasks = new List<Task> { httpClient.GetAsync(linkedServiceSchema).ContinueWith(x => { jsonLinkedServiceSchema = JSchema.Parse(x.Result.Content.ReadAsStringAsync().Result, new JSchemaUrlResolver()); }), httpClient.GetAsync(configSchema).ContinueWith(x => { jsonConfigSchema = JSchema.Parse(x.Result.Content.ReadAsStringAsync().Result, new JSchemaUrlResolver()); }) }; await Task.WhenAll(tasks); // Get schema for pipelines var assembly = Assembly.GetExecutingAssembly(); var pipelineSchemaResource = "Microsoft.ADF.Deployment.AdfKeyVaultDeployment.EmbeddedResources.PipelineSchema.json"; var tableSchemaResource = "Microsoft.ADF.Deployment.AdfKeyVaultDeployment.EmbeddedResources.TableSchema.json"; using (Stream stream = assembly.GetManifestResourceStream(pipelineSchemaResource)) { using (StreamReader reader = new StreamReader(stream)) { string schemaText = reader.ReadToEnd(); jsonPipelineSchema = JSchema.Parse(schemaText, new JSchemaUrlResolver()); } } using (Stream stream = assembly.GetManifestResourceStream(tableSchemaResource)) { using (StreamReader reader = new StreamReader(stream)) { string schemaText = reader.ReadToEnd(); jsonTableSchema = JSchema.Parse(schemaText, new JSchemaUrlResolver()); } } httpClient.Dispose(); } } /// <summary> /// Gets the information on the deployment configuration file. /// </summary> /// <param name="filePath">The file path.</param> public DeployConfigInfo GetDeployConfigInfo(string filePath) { try { string fileContents = File.ReadAllText(filePath); JObject jObject = JObject.Parse(fileContents); if (jObject.IsValid(jsonConfigSchema)) { var deploymentDict = jObject.Properties() .ToDictionary(x => x.Name, y => y.Value.ToDictionary(z => z["name"].ToString(), z => z["value"].ToString())); return new DeployConfigInfo { FilePath = filePath, FileName = Path.GetFileName(filePath), DeploymentDictionary = deploymentDict }; } return null; } catch { return null; } } /// <summary> /// Gets the information on the ADF file. /// </summary> /// <param name="filePath">The file path.</param> /// <param name="deployConfig">The deploy configuration.</param> public async Task<AdfFileInfo> GetFileInfo(string filePath, DeployConfigInfo deployConfig = null) { string fileContents = File.ReadAllText(filePath); string fileName = Path.GetFileNameWithoutExtension(filePath); // Initialize props to default values AdfFileInfo fileInfo = new AdfFileInfo { FileType = FileType.Unknown, SubType = string.Empty, Name = string.Empty, IsValid = false, FileContents = fileContents, FileName = fileName }; JObject jObject = null; try { jObject = JObject.Parse(fileContents); JObject propertyJObject = jObject.GetValue("properties", StringComparison.OrdinalIgnoreCase) as JObject; JToken nameJToken = jObject.GetValue("name", StringComparison.OrdinalIgnoreCase); if (propertyJObject == null || nameJToken == null) { logger.Write($"{fileInfo.FileName} is a not a valid ADF file."); return fileInfo; } fileInfo.Name = nameJToken.ToObject<string>(); if (deployConfig != null) { // Update ADF files with deploymnet settings if they exist jObject = ResolveDeploymentSettings(jObject, fileInfo.Name, deployConfig); fileInfo.FileContents = jObject.ToString(); } JToken typeJToken = propertyJObject.GetValue("type", StringComparison.OrdinalIgnoreCase); if (typeJToken != null) { fileInfo.SubType = typeJToken.ToObject<string>(); } if (fileInfo.SubType == "CPSServiceBusProxy" || jObject.IsValid(jsonLinkedServiceSchema)) { fileInfo.FileType = FileType.LinkedService; fileInfo.IsValid = true; logger.Write($"Retreived LinkedService: {fileInfo.FileName}"); } else if (jObject.IsValid(jsonTableSchema)) { fileInfo.FileType = FileType.Table; fileInfo.IsValid = true; logger.Write($"Retreived Dataset: {fileInfo.FileName}"); } else if (jObject.IsValid(jsonPipelineSchema)) { fileInfo.FileType = FileType.Pipeline; fileInfo.IsValid = true; logger.Write($"Retreived Pipeline: {fileInfo.FileName}"); // Get all custom activity packages if available JArray activities = propertyJObject.GetValue("activities", StringComparison.InvariantCultureIgnoreCase) as JArray; if (activities != null) { fileInfo.CustomActivityPackages = new List<CustomActivityPackageInfo>(); foreach (JObject activity in activities) { JToken activityTypeJToken = activity.GetValue("type", StringComparison.OrdinalIgnoreCase); if (activityTypeJToken != null && activityTypeJToken.ToObject<string>().Equals("DotNetActivity", StringComparison.CurrentCultureIgnoreCase)) { CustomActivityPackageInfo packageInfo = new CustomActivityPackageInfo(); packageInfo.PackageLinkedService = activity.SelectToken("$.typeProperties.packageLinkedService")?.ToObject<string>(); packageInfo.PackageFile = activity.SelectToken("$.typeProperties.packageFile")?.ToObject<string>(); logger.Write($"Retreived Custom Activity package: {packageInfo.PackageFile}"); fileInfo.CustomActivityPackages.Add(packageInfo); } } } } else { fileInfo.FileType = FileType.Unknown; logger.Write($"{fileInfo.FileName} is a not a valid ADF file."); } } catch (Exception e) { fileInfo.ErrorException = e; logger.Write($"{fileInfo.FileName} is a not a valid ADF file. Error message: {e}"); logger.WriteError(e); } if (fileInfo.IsValid) { // Search for keyvault tokens and resolve them string keyVaultResolvedContents = await ResolveKeyVault(fileInfo.FileContents); if (fileInfo.FileContents != keyVaultResolvedContents) { fileInfo.FileContents = keyVaultResolvedContents; fileInfo.JObject = JObject.Parse(fileInfo.FileContents); } else { fileInfo.JObject = jObject; } } return fileInfo; } /// <summary> /// Resolves the deployment settings from the deployment config. /// </summary> private JObject ResolveDeploymentSettings(JObject jObject, string name, DeployConfigInfo deployConfig) { // Update with values from delpoyment config if exists if (deployConfig.DeploymentDictionary.Count > 0 && deployConfig.DeploymentDictionary.ContainsKey(name)) { foreach (KeyValuePair<string, string> pair in deployConfig.DeploymentDictionary[name]) { JToken token = jObject.SelectToken(pair.Key); if (token == null) { logger.Write( $"The deployment configuration file '{deployConfig.FileName}' contains a substitution for '{name}' however the JPath '{pair.Key}' is not found in '{name}'."); } else { token.Replace(pair.Value); } } } return jObject; } /// <summary> /// Resolves the key vault tokens with the actual values from Key Vault. /// </summary> /// <param name="fileContents">The file contents.</param> public async Task<string> ResolveKeyVault(string fileContents) { var matches = Regex.Matches(fileContents, "\\<KeyVault:(?<key>.*?)\\>"); foreach (Match match in matches) { string value = match.Groups["key"].Value.Trim(); logger.Write($"Found Key Vault token '{value}'. Resolving from Key Vault '{keyVaultResolver.KeyVaultName}'."); // Get keyvault value var secretTask = await keyVaultResolver.GetSecret(value); string secret = secretTask.Value; fileContents = fileContents.Replace($"<KeyVault:{value}>", secret); } return fileContents; } } }