Awesome
CoherentSolutions.Extensions.Configuration.AnyWhere
Build & Tests | Engine | Adapters List |
---|---|---|
For list of available configuration adapters please see the list
About the project
CoherentSolutions.Extensions.Configuration.AnyWhere is an extension to Microsoft.Extensions.Configuration. This extension allows application to configure it's configuration sources using environment variables.
How it works?
The CoherentSolutions.Extensions.Configuration.AnyWhere is made of two parts: configuration engine and configuration adapter.
Configuration engine is configured once in the application code using AddAnyWhereConfiguration
method. The configuration engine then is responsible for reading required values from environment variables and load all of the requested configuration sources.
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(
config =>
{
config.AddAnyWhereConfiguration();
})
Configuration adapter is a "bridge" between configuration engine and configuration source. It is represented by IAnyWhereConfigurationAdapter
interface:
public interface IAnyWhereConfigurationAdapter
{
void ConfigureAppConfiguration(
IConfigurationBuilder configurationBuilder,
IAnyWhereConfigurationEnvironmentReader environmentReader);
}
Usually but not mandatory configuration adapter is implemented in the separate assembly.
Coupling between configuration engine and configuration adapters is done using special environment variables. All variables can be divided into GLOBAL and LOCAL.
GLOBAL variables are consumed by configuration engine. They have the following format: ANYWHERE_ADAPTER_GLOBAL_{VARIABLE_NAME}:
- ANYWHERE_ADAPTER_GLOBAL - is a predefined prefix.
- {VARIABLE_NAME} - is a name of the variable.
Currently configuration engine supports the following GLOBAL variables:
- PROBING_PATH - is the list of paths (separated by Path.PathSeparator) to search for an adapter assembly (by default only current directory is scanned).
It should be noted that current directory is always scanned during assemblies lookup.
LOCAL variables are consumed by both configuration engine and configuration adapters. They have the following format: ANYWHERE_ADAPTER_{INDEX}_{VARIABLE_NAME}:
- ANYWHERE_ADAPTER - is a predefined prefix.
- {INDEX} - is a zero based index of the adapter being configured.
- {VARIABLE_NAME} - is a name of the variable.
When configuring configuration adapters it is critically to understand that configuration adapter's indexes should be sequential and start from 0.
Any space / a gap between indexes is treated as end of list and the rest of configuration is ignored.
Configuration adapter is identified and loaded by configuration engine using two variables:
- TYPE_NAME - is the full type name of the configuration adapter's type.
- ASSEMBLY_NAME - is the name of the assembly file where configuration adapter type is implemented.
All additional parameters required by the underlying IConfigurationSource
are passed using the LOCAL variables or with help of CONFIGURATION FILE and consumed using supplied instance of IAnyWhereConfigurationEnvironmentReader
.
Please see documentation for more details
Where it can be used?
Imagine a simplest ASP.NET Core application with entry point configured as following:
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build()
.Run();
NOTE
This section doesn't present and explains all features / aspects of the CoherentSolutions.Extensions.Configuration.AnyWhere. The complete documentation is available on project wiki.
Application is build into container and deployed to development environment (developers machine) and staging (Kubernetes cluster in Azure).
In development environment application consumes secrets from shared .json
configuration file (friendly environment).
In contrary to development environment in staging environment application secrets are consumed from Azure Key Vault (hostile environment).
So the full code snippet is:
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration(
(ctx,config) =>
{
if (ctx.HostingEnvironment.IsDevelopment())
{
// Load values from shared .json configuration file
}
if (ctx.HostingEnvironment.IsStaging())
{
// Load values from Azure Key Vault
}
})
.Build()
.Run();
This works (and there is nothing bad in this approach). The downside of this is a requirement to modify startup code each time something is changed - new environment added, some critical parameters are changed etc.
A bit more flexibility can be achieved by using CoherentSolutions.Extensions.Configuration.AnyWhere.
Updating application
- Add reference to <a href="https://www.nuget.org/packages/CoherentSolutions.Extensions.Configuration.AnyWhere/">CoherentSolutions.Extensions.Configuration.AnyWhere</a> nuget package.
- Update application entry point:
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration(
(ctx,config) =>
{
config.AddAnyWhereConfiguration();
})
.Build()
.Run();
You can use CoherentSolutions.Extensions.Configuration.AnyWhere in combination with other configuration sources.
Consuming configuration in development
The configuration for .json
format is already implemented (Microsoft.Extensions.Configuration.Json) so all we need to do is to make it available for configuration engine.
NOTE
The configuration adapter for
json
configuration is already implemented and available as package or binaries.
Here is the code for the .json
configuration adapter:
// The code is taken from CoherentSolutions.Extensions.Configuration.AnyWhere.Json.dll
// The project has reference to CoherentSolutions.Extensions.Configuration.AnyWhere.Abstractions package
// The project has reference to Microsoft.Extensions.Configuration.Json package
namespace CoherentSolutions.Extensions.Configuration.AnyWhere.Json
{
public class AnyWhereJsonConfigurationSourceAdapter : IAnyWhereConfigurationSourceAdapter
{
public void ConfigureAppConfiguration(
IConfigurationBuilder configurationBuilder,
IAnyWhereConfigurationEnvironmentReader environmentReader)
{
if (configurationBuilder == null)
{
throw new ArgumentNullException(nameof(configurationBuilder));
}
if (environmentReader == null)
{
throw new ArgumentNullException(nameof(environmentReader));
}
// Adding Json configuration source and reading parameters from the environment
configurationBuilder.AddJsonFile(
environmentReader.GetString("PATH"),
environmentReader.GetBool("OPTIONAL", optional: true),
environmentReader.GetBool("RELOAD_ON_CHANGE", optional: true));
}
}
}
The environment variables are configured as following:
- ANYWHERE_ADAPTER_GLOBAL_PROBING_PATH=<locations>
- ANYWHERE_ADAPTER_0_TYPE_NAME=CoherentSolutions.Extensions.Configuration.AnyWhere.Json.AnyWhereJsonConfigurationSourceAdapter
- ANYWHERE_ADAPTER_0_ASSEMBLY_NAME=CoherentSolutions.Extensions.Configuration.AnyWhere.Json
- ANYWHERE_ADAPTER_0_PATH=<configuration file location>
- ANYWHERE_ADAPTER_0_OPTIONAL=false
The configuration adapter assembly should be placed either in current directory or it's directory should be specified in PROBING_PATH variable (GLOBAL scope).
Consuming configuration in staging
The Kubernetes can be integrated with Azure Key Vault using kubernetes-keyvault-flexvol project. This way all requested secrets are downloaded to Kubernetes volume in key-per-file
format.
The configuration for key-per-file
format is already implemented (Microsoft.Extensions.Configuration.KeyPerFile) so all we need to do is to make it available for configuration engine.
NOTE
The configuration adapter for
key-per-file
configuration is already implemented and available as package or binaries.
Here is the code for the key-per-file
configuration adapter:
// The code is taken from CoherentSolutions.Extensions.Configuration.AnyWhere.KeyPerFile.dll
// The project has reference to CoherentSolutions.Extensions.Configuration.AnyWhere.Abstractions package
// The project has reference to Microsoft.Extensions.Configuration.KeyPerFile package
namespace CoherentSolutions.Extensions.Configuration.AnyWhere.KeyPerFile
{
public class AnyWhereKeyPerFileConfigurationSourceAdapter : AnyWhereConfigurationSourceAdapter
{
public void ConfigureAppConfiguration(
IConfigurationBuilder configurationBuilder,
IAnyWhereConfigurationEnvironmentReader environmentReader)
{
if (configurationBuilder == null)
{
throw new ArgumentNullException(nameof(configurationBuilder));
}
if (environmentReader == null)
{
throw new ArgumentNullException(nameof(environmentReader));
}
configurationBuilder.AddKeyPerFile(
environmentReader.GetString("DIRECTORY_PATH"),
environmentReader.GetBool("OPTIONAL", optional: true));
}
}
}
The environment variables are configured as following:
- ANYWHERE_ADAPTER_GLOBAL_PROBING_PATH=<locations>
- ANYWHERE_ADAPTER_0_TYPE_NAME=CoherentSolutions.Extensions.Configuration.AnyWhere.KeyPerFile.AnyWhereKeyPerFileConfigurationSourceAdapter
- ANYWHERE_ADAPTER_0_ASSEMBLY_NAME=CoherentSolutions.Extensions.Configuration.AnyWhere.KeyPerFile
- ANYWHERE_ADAPTER_0_DIRECTORY_PATH=<volume mapping location>
- ANYWHERE_ADAPTER_0_OPTIONAL=false
Well-known configuration adapters
There are set of well known configuration adapters:
The Json
, EnvironmentVariables
and KeyPerFile
configuration adapters make use of corresponding Microsoft.Extensions.Configuration.* packages to create the configuration source. The implementation translates parameters from environment variables to configuration source parameters.
The AzureKeyVault
configuration adapter implements workflow for obtaining Secrets from Azure Key Vault in managed identity scenarios.
These configuration adapters available in form of packages and binaries:
Name | Package | Binary |
---|---|---|
Json | ||
EnvironmentVariables | ||
KeyPerFile | ||
AzureKeyVault |
Well known configuration adapters are defined in CoherentSolutions.Extensions.Configuration.AnyWhere.AdapterList package and configured using AddAnyWhereConfigurationAdapterList
method:
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration(
config =>
{
config
.AddAnyWhereConfigurationAdapterList()
.AddAnyWhereConfiguration();
})
.Build()
.Run();
NOTE
AddAnyWhereConfigurationAdapterList
must be called beforeAddAnyWhereConfiguration
.
These well-known configuration adapters can be configured in the simplified fashion. Instead of using TYPE_NAME and ASSEMBLY_NAME variables you can simply use NAME variable:
- ANYWHERE_ADAPTER_GLOBAL_PROBING_PATH=<locations>
- ANYWHERE_ADAPTER_0_NAME=Json
- ANYWHERE_ADAPTER_0_PATH=<configuration location>
- ANYWHERE_ADAPTER_0_OPTIONAL=false
Conclusion
That is it :)
Contributing
For additional information on how to contribute to this project, please see CONTRIBUTING.md.
Authors
This project is owned and maintained by Coherent Solutions.
License
This project is licensed under the MIT License - see the LICENSE.md for details.
See Also
Besides this project Coherent Solutions also maintains a few more open source projects and workshops:
Projects
- service-fabric-run-tests - is a Docker image designed to run Service Fabric oriented unit-tests (.NET Core) on Linux.
- CoherentSolutions.Extensions.Hosting.ServiceFabric - is an extension to existing HostBuilder. The idea is to simplify configuration of Reliable Services by removing unnecessary code and improving separation of concerns.
Workshops
- Building Self-Driving Car Architecture with Robot Operating System - is a workshop to introduce attendee to Robot Operating System (ROS) design and create simple self-driving car architecture.