Skip to content

Commit 2c3b987

Browse files
Merge pull request #18 from patrickvecchio/bug/remove-inline-html-from-readme
Removed inline html from readme to match non-HTML friendly places (like NuGet.org)
2 parents ebbeb62 + 31ed5ca commit 2c3b987

File tree

1 file changed

+72
-115
lines changed

1 file changed

+72
-115
lines changed

README.md

Lines changed: 72 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,54 @@
11
# Custom Configuration Providers
22

3-
Custom Configuration Providers is a collection of configuration providers for .Net that extend Microsoft.Extensions.Configuration to pull configuration data from a variety of sources. Details on how this custom configration provider is built can be found here:
3+
Custom Configuration Providers is a collection of configuration providers for .Net that extend Microsoft.Extensions.Configuration to pull configuration data from a variety of sources. Details on how this custom configuration provider is built can be found here:
44
[Implement a custom configuration provider in .NET](https://docs.microsoft.com/en-us/dotnet/core/extensions/custom-configuration-provider)
55

6-
# AWS Secrets Manager Configuration Provider
7-
## Overview
8-
Using this custom configuration provider, you can put AWS Secrets Manager keys into your appsettings files and the values stored in AWS Secrets Manager will be loaded into the config at startup time as if they were in the appsettings file already. What this auto-population of the secret configuration valus allow us to do is to leverage the [Options pattern in .Net](https://docs.microsoft.com/en-us/dotnet/core/extensions/options) along with Dependy Injection to automatically load these settings into the classes that need them. In addition, having the settings pre-loaded at startup time means we can eliminate the need to mock an AwsSecretsManager and try to fake the settings at execution time. We can simply use a test version of our appsettings file or in-memory configuration provider with the test values pre-populated and leave AWS Secrets Manager out of our unit tests completely.
9-
10-
So, here's the appsettings before and after running AddAwsSecretsManager:
11-
12-
<table>
13-
<thead>
14-
<tr>
15-
<th>
16-
appsettings.json file
17-
</th>
18-
<th>
19-
IConfiguration
20-
</th>
21-
<th>
22-
IConfiguration after .AddAwsSecretsManager()
23-
</th>
24-
</tr>
25-
</thead>
26-
<tbody>
27-
<tr>
28-
<td>
29-
<pre lang="json">
6+
## AWS Secrets Manager Configuration Provider
7+
8+
### Overview
9+
10+
Using this custom configuration provider, you can put AWS Secrets Manager keys into your appsettings files and the values stored in AWS Secrets Manager will be loaded into the config at startup time as if they were in the appsettings file already. Using single AWS secrets keys we can selectively load just the secrets our app needs.
11+
12+
By resolving the secret values are startup allows us to do is to leverage the [Options pattern in .Net](https://docs.microsoft.com/en-us/dotnet/core/extensions/options) along with Dependency Injection to automatically load these settings into the classes that need them, meaning our code can be written in a way that it doesn't care where the settings come from. This simplifies our testing because our unit tests can use an in-memory configuration provider with the test values pre-populated leaving them without the need to mock Secrets Manager or make connections from our CI/CD pipeline to a test instance of Secrets Manager.
13+
14+
So, here is an example of some values stored in the "/dev/db/options" key of our AWS Secrets Manager instance:
15+
16+
```json
3017
{
31-
"Database": {
32-
"AwsSecret": "/dev/db/options"
33-
}
18+
"server": "db1.myurl.com"
19+
"port": "3306"
20+
"timeout": "5"
21+
"userName": "ApiReadOnly"
22+
"password": "NunyaBizness!"
3423
}
35-
</pre>
36-
</td>
37-
<td>
38-
"Database:AwsSecret": "/dev/db/options"<br>
39-
</td>
40-
<td>
41-
"Database:server": "db1.myurl.com"<br>
42-
"Database:port": "3306"<br>
43-
"Database:timeout": "5"<br>
44-
"Database:userName": "ApiReadOnly"<br>
45-
"Database:password": "NunyaBizness!"<br>
46-
</td>
47-
</tr>
48-
</tbody>
49-
</table>
50-
51-
## How it works
52-
Let's say you have the following secret stored in AWS Secrets Manager under the key "/dev/db/options".
53-
Setting | Value
54-
------- | -----
55-
server | someserver.us-west-2.aws.amazon.com
56-
port | 3306
57-
timeout | 5
58-
username | ApiReadOnly
59-
password | NunyaBizness!
60-
61-
Using this provider you could put the following in your `appsettings.{environment}.json` file:
24+
```
25+
26+
Here are our appsettings:
27+
6228
```json
63-
{
64-
"Database": {
65-
"AwsSecret": "/dev/db/options"
29+
{
30+
"Database": {
31+
"AwsSecret": "/dev/db/options"
32+
}
6633
}
67-
}
6834
```
69-
> **Note:**
70-
>
71-
> In the next step, you'll see how you can choose any string you want in place of "AwsSecret"
7235

73-
Add the provider along with an AwsSecretsManager client (this is to allow for injecting mock versions or other versions of the client) and the name of the key you chose to put in your appsettings (e.g. "AwsSecret"):
36+
Here is what our IConfiguration values look like before and after we call .AddAwsSecretsManager, specifying "AwsSecret" as our placeholder key:
37+
38+
| IConfiguration before | IConfiguration after |
39+
|---|---|
40+
| "Database:AwsSecret": "/dev/db/options" | "Database:server": "db1.myurl.com" |
41+
|| "Database:port": "3306" |
42+
|| "Database:timeout": "5" |
43+
|| "Database:userName": "ApiReadOnly" |
44+
|| "Database:password": "NunyaBizness!" |
45+
46+
### How it works
47+
48+
Custom configuration providers work as extension methods for ConfigurationBuilder. As long as the custom provider is included in your source and it's namespace is in your "using" statements, you should be able to just call ".AddAwsSecretsManager(...)".
49+
50+
In your app's startup code, add the provider to your configuration builder along with an AwsSecretsManager client (this is to allow for injecting mock versions or other versions of the client) and the name of the placeholder key you chose to put in your appsettings (e.g. "AwsSecret"):
51+
7452
```csharp
7553
var configurationBuilder = new ConfigurationBuilder();
7654
configurationBuilder.Sources.Clear();
@@ -81,55 +59,28 @@ Add the provider along with an AwsSecretsManager client (this is to allow for in
8159
.AddAwsSecretsManager(new AmazonSecretsManagerClient(Amazon.RegionEndpoint.USWest2), "AwsSecret")
8260
.AddEnvironmentVariables();
8361
```
62+
8463
> A couple of things worth noting in this sample
8564
>
8665
> * We're supplying an AmazonSecretsManagerClient to simplify testing and to choose the region in our app startup.
87-
> * In `AddAwsSecretsManager()` we're specifying the key to search and replace (e.g. "AwsSecret") with AWS Secrets Manager values. You can choose whatever string you want.
88-
89-
When you build the config builder, the custom configuration provider will look in the appsettings files you've loaded for the key "AwsSecret" take it's value "/dev/db/options" and replace the whole key with the values retrieved from AWS Secrets Manager (in the IConfiguration tree in memory, not the appsettings file) like so:
90-
<table>
91-
<thead>
92-
<tr>
93-
<th>
94-
appsettings.json file
95-
</th>
96-
<th>
97-
IConfiguration
98-
</th>
99-
<th>
100-
IConfiguration after .AddAwsSecretsManager()
101-
</th>
102-
</tr>
103-
</thead>
104-
<tbody>
105-
<tr>
106-
<td>
107-
<pre lang="json">
108-
{
109-
"Database": {
110-
"AwsSecret": "/dev/db/options"
111-
}
112-
}
113-
</pre>
114-
</td>
115-
<td>
116-
"Database:AwsSecret": "/dev/db/options"<br>
117-
</td>
118-
<td>
119-
"Database:server": "db1.myurl.com"<br>
120-
"Database:port": "3306"<br>
121-
"Database:timeout": "5"<br>
122-
"Database:userName": "ApiReadOnly"<br>
123-
"Database:password": "NunyaBizness!"<br>
124-
</td>
125-
</tr>
126-
</tbody>
127-
</table>
128-
129-
## Options Pattern and Dependency Injection
66+
> * In `AddAwsSecretsManager()` we're specifying the placeholder key to search and replace (e.g. "AwsSecret") with AWS Secrets Manager values. You can choose whatever string you want.
67+
68+
When you call "Build" on the config builder, the custom configuration provider will look in the appsettings files you've loaded for the placeholder key you specified (e.g. "AwsSecret") take it's value "/dev/db/options" and replace the value with the values retrieved from AWS Secrets Manager (i.e. in the IConfiguration tree in memory, not the appsettings file) like so:
69+
70+
| IConfiguration before | IConfiguration after |
71+
|---|---|
72+
| "Database:AwsSecret": "/dev/db/options" | "Database:server": "db1.myurl.com" |
73+
|| "Database:port": "3306" |
74+
|| "Database:timeout": "5" |
75+
|| "Database:userName": "ApiReadOnly" |
76+
|| "Database:password": "NunyaBizness!" |
77+
78+
### Options Pattern and Dependency Injection
79+
13080
With the AWS Secrets expanded using the configuration provider, we can use the settings in the Options Pattern to map the settings to a class, add it to the Dependency Injection IServiceCollection and inject it automatically into classes that need it.
13181

132-
### Create a class to hold the settings
82+
#### Create a class to hold the settings
83+
13384
```csharp
13485
public class DatabaseOptions
13586
{
@@ -140,19 +91,23 @@ public class DatabaseOptions
14091
public string Password { get; set; }
14192
}
14293
```
94+
14395
> Note:
144-
>
145-
> Since AWS Secrets Manager stores key-value pairs as <string,string> options classes shoud contain strings. It is possible to add code to the Configuration Provider that parses the JSON into proper types, but it complicates the code that I think has more cost than benefit.
96+
>
97+
> Since AWS Secrets Manager stores key-value pairs as <string,string> options classes should contain strings. It is possible to add code to the Configuration Provider that parses the JSON into proper types, but it complicates the code in a way that I think has more cost than benefit. Simpler to treat them as strings and convert them to the needed types by the classes that consume them.
98+
99+
#### Inject them into a Dependency Injection collection
146100

147-
### Inject them into a Dependency Injection collection
148101
```csharp
149102
services.AddOptions<DatabaseOptions>()
150103
.Configure<IConfiguration>((settings, configuration) =>
151104
{
152105
configuration.GetSection("Database").Bind(settings);
153106
});
154107
```
155-
### And inject them into your classes
108+
109+
#### And inject them into your classes
110+
156111
```csharp
157112
private DatabaseOptions _databaseOptions;
158113

@@ -162,10 +117,12 @@ public ServiceThatUsesDatabaseOptions(IOptions<DatabaseOptions> databaseOptions)
162117
}
163118
```
164119

165-
## Simplified Testing
120+
### Simplified Testing
121+
166122
Since we're pre-loading the real values in the ConfigurationBuilder at startup using the AWS Secrets Manager Configuration Provider, everything downstream is using the real secrets. Therefore, when we're testing, we can just put our test values directly into our test config and eliminate AWS Secrets Manager from our unit tests.
167123

168-
### Example unit test
124+
#### Example unit test
125+
169126
```csharp
170127
public DependencyInjectionMockUnitTests()
171128
{
@@ -191,10 +148,10 @@ public DependencyInjectionMockUnitTests()
191148
{
192149
_configuration.GetSection("Database").Bind(settings);
193150
});
194-
_services.AddScoped<IServiceThatUsesDatabaseOptions, IServiceThatUsesDatabaseOptions>();
151+
_services.AddScoped<IServiceThatUsesDatabaseOptions, ServiceThatUsesDatabaseOptions>();
195152

196153
_serviceProvider = _services.BuildServiceProvider();
197-
154+
198155
// Get the service to be tested
199156
var serviceToTest = _serviceProvider.GetRequiredService<IServiceThatUsesDatabaseOptions>();
200157
}

0 commit comments

Comments
 (0)