/// <summary> /// Loads the application configuration from an XML document /// </summary> /// <param name="document">The document</param> /// <param name="settings">An optional settings file</param> /// <param name="checkForMissingSettings">If set to true then any missing settings generate an exception</param> /// <param name="applicationSecretStore">Optional secret store to use in addition to the settings</param> /// <param name="verboseLogger">Optional verbose logger</param> /// <returns>An application configuration</returns> public static async Task<ApplicationConfiguration> FromXDocumentAsync(XDocument document, ApplicationConfigurationSettings settings, bool checkForMissingSettings, IAsyncConfiguration applicationSecretStore = null, Action<string> verboseLogger = null) { if (document.Root == null) return null; HashSet<string> secrets = new HashSet<string>(); verboseLogger?.Invoke("Processing settings"); ApplicationConfiguration configuration = new ApplicationConfiguration(); IApplicationResourceSettingNameProvider nameProvider = new ApplicationResourceSettingNameProvider(); IEnumerable<XElement> allDescendants = document.Descendants(); Regex settingPattern = new Regex(@"(?:\{\{)([^}]*)(?:\}\})"); foreach (XElement element in allDescendants) { if (!element.HasElements) { Match match = settingPattern.Match(element.Value); string value = element.Value; bool containsSecret = false; while (match.Success) { string settingName = match.Groups[1].Value; ApplicationConfigurationSetting setting; if (!settings.Settings.TryGetValue(settingName, out setting) && checkForMissingSettings) { throw new MissingSettingException(); } if (setting != null) { containsSecret |= setting.IsSecret; value = value.Replace($"{{{{{settingName}}}}}", setting.Value); } match = match.NextMatch(); } element.Value = value; if (containsSecret) { secrets.Add(value); } } } document.Root.XPathSelectElements("infrastructure/sql-server").ToList().ForEach(element => { var xfqn = element.Element("fqn"); if (xfqn != null) { var xconnectionstring = element.Element("connection-string"); if (xconnectionstring != null) configuration.SqlServerConnectionStrings.Add(xfqn.Value, xconnectionstring.Value); } }); document.Root.XPathSelectElements("infrastructure/storage-account").ToList().ForEach(element => { ApplicationStorageAccount storageAccount = new ApplicationStorageAccount(element); configuration.StorageAccounts.Add(storageAccount.Fqn, storageAccount); }); document.Root.XPathSelectElements("infrastructure/service-bus").ToList().ForEach(element => { var xfqn = element.Element("fqn"); if (xfqn != null) { var xconnectionstring = element.Element("connection-string"); if (xconnectionstring != null) configuration.ServiceBusConnectionStrings.Add(xfqn.Value, xconnectionstring.Value); } }); foreach (XElement element in document.Root.Elements("component").ToList()) { ApplicationComponent component = new ApplicationComponent { Fqn = element.Attribute("fqn").Value }; IComponentIdentity componentIdentity = new ComponentIdentity(component.Fqn); verboseLogger?.Invoke($"Parsing component {componentIdentity}"); XElement sqlServerElement = element.Element("sql-server"); XElement storageElement = element.Element("storage-account"); XElement serviceBusElement = element.Element("service-bus"); XElement dbContextTypeElement = element.Element("db-context-type"); XElement defaultBlobContainerNameElement = element.Element("default-blob-container-name"); XElement defaultQueueNameElement = element.Element("default-queue-name"); XElement defaultTableNameElement = element.Element("default-table-name"); XElement defaultTableData = element.Element("table-data"); XElement defaultLeaseBlockNameElement = element.Element("default-lease-block-name"); XElement defaultSubscriptionNameElement = element.Element("default-subscription-name"); XElement defaultTopicNameElement = element.Element("default-topic-name"); XElement defaultBrokeredMessageQueueNameElement = element.Element("default-brokered-message-queue-name"); XElement settingsElement = element.Element("settings"); XAttribute defaultBlobContainerAccessAttribute = defaultBlobContainerNameElement?.Attribute("public-permission"); if (sqlServerElement != null) { try { string secret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.SqlConnectionString(componentIdentity)) : null; component.SqlServerConnectionString = secret ?? configuration.SqlServerConnectionStrings[sqlServerElement.Value]; } catch (KeyNotFoundException) { throw new InvalidDataException($"Sql server with fqn of {sqlServerElement.Value} is missing from configuration file."); } } if (storageElement != null) { try { string secret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.StorageAccountConnectionString(componentIdentity)) : null; component.StorageAccountConnectionString = secret ?? configuration.StorageAccounts[storageElement.Value].ConnectionString; } catch (Exception) { throw new InvalidDataException($"Storage account with fqn of {storageElement.Value} is missing from configuration file."); } } if (serviceBusElement != null) { try { verboseLogger?.Invoke($"Looking for service bus connection string for component {componentIdentity}"); string secret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.ServiceBusConnectionString(componentIdentity)) : null; if (secret != null) { verboseLogger?.Invoke($"Using secret store service bus connection string for component {componentIdentity}"); } component.ServiceBusConnectionString = secret ?? configuration.ServiceBusConnectionStrings[serviceBusElement.Value]; } catch (Exception) { throw new InvalidDataException($"Service bus account with fqn of {serviceBusElement.Value} is missing from configuration file."); } } string name = nameProvider.SqlContextType(componentIdentity); verboseLogger?.Invoke(name); string dbContextTypeSecret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(name) : null; if (!string.IsNullOrWhiteSpace(dbContextTypeSecret)) verboseLogger?.Invoke($"Using secret store for dbContextType for component {componentIdentity}"); component.DbContextType = dbContextTypeSecret ?? dbContextTypeElement?.Value; string defaultBlobContainerNameSecret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.DefaultBlobContainerName(componentIdentity)) : null; if (!string.IsNullOrWhiteSpace(defaultBlobContainerNameSecret)) verboseLogger?.Invoke($"Using secret store for default blob container name for component {componentIdentity}"); component.DefaultBlobContainerName = defaultBlobContainerNameSecret ?? defaultBlobContainerNameElement?.Value; string defaultQueueNameSecret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.DefaultQueueName(componentIdentity)) : null; if (!string.IsNullOrWhiteSpace(defaultQueueNameSecret)) verboseLogger?.Invoke($"Using secret store for default queue name for component {componentIdentity}"); component.DefaultQueueName = defaultQueueNameSecret ?? defaultQueueNameElement?.Value; string defaultTableNameSecret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.DefaultTableName(componentIdentity)) : null; if (!string.IsNullOrWhiteSpace(defaultTableNameSecret)) verboseLogger?.Invoke($"Using secret store for default table name for component {componentIdentity}"); component.DefaultTableName = defaultTableNameSecret ?? defaultTableNameElement?.Value; component.DefaultBlobContainerAccessType = BlobContainerPublicAccessTypeEnum.Off; string defaultLeaseBlockNameSecret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.DefaultLeaseBlockName(componentIdentity)) : null; if (!string.IsNullOrWhiteSpace(defaultLeaseBlockNameSecret)) verboseLogger?.Invoke($"Using secret store for default lease block name for component {componentIdentity}"); component.DefaultLeaseBlockName = defaultLeaseBlockNameSecret ?? defaultLeaseBlockNameElement?.Value; string defaultTopicNameSecret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.DefaultTopicName(componentIdentity)) : null; if (!string.IsNullOrWhiteSpace(defaultTopicNameSecret)) verboseLogger?.Invoke($"Using secret store for default topic name for component {componentIdentity}"); component.DefaultTopicName = defaultTopicNameSecret ?? defaultTopicNameElement?.Value; string defaultSubscriptionNameSecret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.DefaultSubscriptionName(componentIdentity)) : null; if (!string.IsNullOrWhiteSpace(defaultSubscriptionNameSecret)) verboseLogger?.Invoke($"Using secret store for default subscription name for component {componentIdentity}"); component.DefaultSubscriptionName = defaultSubscriptionNameSecret ?? defaultSubscriptionNameElement?.Value; string defaultBrokeredMessageQueueNameSecret = applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.DefaultBrokeredMessageQueueName(componentIdentity)) : null; if (!string.IsNullOrWhiteSpace(defaultBrokeredMessageQueueNameSecret)) verboseLogger?.Invoke($"Using secret store for default brokered message queue name for component {componentIdentity}"); component.DefaultBrokeredMessageQueueName = defaultBrokeredMessageQueueNameSecret ?? defaultBrokeredMessageQueueNameElement?.Value; component.TableData = defaultTableData?.Value; component.Uploads = element.Elements("upload").Select(x => x.Value).ToList(); if (defaultBlobContainerAccessAttribute != null) { string accessAttribtueValue = defaultBlobContainerAccessAttribute.Value.ToLower(); if (accessAttribtueValue == "blob") { component.DefaultBlobContainerAccessType = BlobContainerPublicAccessTypeEnum.Blob; } else if (accessAttribtueValue == "container") { component.DefaultBlobContainerAccessType = BlobContainerPublicAccessTypeEnum.Container; } } verboseLogger?.Invoke($"Processing settings for {componentIdentity}"); if (settingsElement != null) { foreach (XElement componentSettingsElement in settingsElement.Elements().ToList()) { string resourceType = null; XAttribute resourceTypeAttr = componentSettingsElement.Attribute("resource-type"); if (resourceTypeAttr != null) { resourceType = resourceTypeAttr.Value; } Dictionary<string, string> attributeDictionary = componentSettingsElement.Attributes().ToDictionary(attribute => attribute.Name.LocalName, attribute => attribute.Value); component.Settings.Add(new ApplicationComponentSetting { Key = componentSettingsElement.Name.LocalName, ResourceType = resourceType, Value = (applicationSecretStore != null ? await applicationSecretStore.GetAsync(nameProvider.SettingName(componentIdentity, componentSettingsElement.Name.LocalName)) : null) ?? componentSettingsElement.Value, Attributes = attributeDictionary }); } } configuration.ApplicationComponents.Add(component); verboseLogger?.Invoke($"Finished parsing component {componentIdentity}"); } configuration.Secrets = secrets.ToList(); return configuration; }
public static ApplicationConfiguration FromFile(string filename, ApplicationConfigurationSettings settings) { ApplicationConfiguration configuration = new ApplicationConfiguration(); XDocument document; using (StreamReader reader = new StreamReader(filename)) { if (settings != null) { string processedXml = settings.Merge(reader); document = XDocument.Parse(processedXml); } else { document = XDocument.Load(reader); } } document.Root.XPathSelectElements("infrastructure/sql-server").ToList().ForEach(element => { configuration.SqlServerConnectionStrings.Add(element.Element("fqn").Value, element.Element("connection-string").Value); }); document.Root.XPathSelectElements("infrastructure/storage-account").ToList().ForEach(element => { ApplicationStorageAccount storageAccount = new ApplicationStorageAccount(element); configuration.StorageAccounts.Add(storageAccount.Fqn, storageAccount); }); document.Root.XPathSelectElements("infrastructure/service-bus").ToList().ForEach(element => { configuration.ServiceBusConnectionStrings.Add(element.Element("fqn").Value, element.Element("connection-string").Value); }); document.Root.Elements("component").ToList().ForEach(element => { ApplicationComponent component = new ApplicationComponent { Fqn = element.Attribute("fqn").Value }; XElement sqlServerElement = element.Element("sql-server"); XElement storageElement = element.Element("storage-account"); XElement serviceBusElement = element.Element("service-bus"); XElement dbContextTypeElement = element.Element("db-context-type"); XElement defaultBlobContainerNameElement = element.Element("default-blob-container-name"); XElement defaultQueueNameElement = element.Element("default-queue-name"); XElement defaultTableNameElement = element.Element("default-table-name"); XElement defaultTableData = element.Element("table-data"); XElement defaultLeaseBlockNameElement = element.Element("default-lease-block-name"); XElement defaultSubscriptionNameElement = element.Element("default-subscription-name"); XElement defaultTopicNameElement = element.Element("default-topic-name"); XElement defaultBrokeredMessageQueueNameElement = element.Element("default-brokered-message-queue-name"); XElement settingsElement = element.Element("settings"); XAttribute defaultBlobContainerAccessAttribute = defaultBlobContainerNameElement?.Attribute("public-permission"); if (sqlServerElement != null) { try { component.SqlServerConnectionString = configuration.SqlServerConnectionStrings[sqlServerElement.Value]; component.SqlServerConnectionString = configuration.SqlServerConnectionStrings[sqlServerElement.Value]; } catch (KeyNotFoundException) { throw new InvalidDataException($"Sql server with fqn of {sqlServerElement.Value} is missing from configuration file."); } } if (storageElement != null) { try { component.StorageAccountConnectionString = configuration.StorageAccounts[storageElement.Value].ConnectionString; } catch (Exception) { throw new InvalidDataException($"Storage account with fqn of {storageElement.Value} is missing from configuration file."); } } if (serviceBusElement != null) { try { component.ServiceBusConnectionString = configuration.ServiceBusConnectionStrings[serviceBusElement.Value]; } catch (Exception) { throw new InvalidDataException($"Service bus account with fqn of {serviceBusElement.Value} is missing from configuration file."); } } component.DbContextType = dbContextTypeElement?.Value; component.DefaultBlobContainerName = defaultBlobContainerNameElement?.Value; component.DefaultQueueName = defaultQueueNameElement?.Value; component.DefaultTableName = defaultTableNameElement?.Value; component.DefaultBlobContainerAccessType = BlobContainerPublicAccessTypeEnum.Off; component.DefaultLeaseBlockName = defaultLeaseBlockNameElement?.Value; component.DefaultTopicName = defaultTopicNameElement?.Value; component.DefaultSubscriptionName = defaultSubscriptionNameElement?.Value; component.DefaultBrokeredMessageQueueName = defaultBrokeredMessageQueueNameElement?.Value; component.TableData = defaultTableData?.Value; component.Uploads = element.Elements("upload").Select(x => x.Value).ToList(); if (defaultBlobContainerAccessAttribute != null) { string accessAttribtueValue = defaultBlobContainerAccessAttribute.Value.ToLower(); if (accessAttribtueValue == "blob") { component.DefaultBlobContainerAccessType = BlobContainerPublicAccessTypeEnum.Blob; } else if (accessAttribtueValue == "container") { component.DefaultBlobContainerAccessType = BlobContainerPublicAccessTypeEnum.Container; } } settingsElement?.Elements().ToList().ForEach(x => { string resourceType = null; XAttribute resourceTypeAttr = x.Attribute("resource-type"); if (resourceTypeAttr != null) { resourceType = resourceTypeAttr.Value; } Dictionary<string, string> attributeDictionary = x.Attributes().ToDictionary(attribute => attribute.Name.LocalName, attribute => attribute.Value); component.Settings.Add(new ApplicationComponentSetting { Key = x.Name.LocalName, ResourceType = resourceType, Value = x.Value, Attributes = attributeDictionary }); }); configuration.ApplicationComponents.Add(component); }); return configuration; }