├── icon.png
├── logo.png
├── public.snk
├── NOTICE
├── samples
├── Samples
│ ├── appsettings.json
│ ├── appsettings.Development.json
│ ├── Settings.cs
│ ├── Samples.csproj
│ ├── Controllers
│ │ └── ValuesController.cs
│ ├── Properties
│ │ └── launchSettings.json
│ └── Program.cs
└── Samples.sln
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── documentation.yml
│ ├── feature-request.yml
│ └── bug-report.yml
├── dependabot.yml
├── workflows
│ ├── handle-stale-discussions.yml
│ ├── closed-issue-message.yml
│ ├── semgrep-analysis.yml
│ ├── issue-regression-labeler.yml
│ ├── change-file-in-pr.yml
│ ├── aws-ci.yml
│ ├── stale_issues.yml
│ ├── create-release-pr.yml
│ └── sync-main-dev.yml
└── PULL_REQUEST_TEMPLATE.md
├── CODE_OF_CONDUCT.md
├── .autover
└── autover.json
├── src
├── ruleset.xml
└── Amazon.Extensions.Configuration.SystemsManager
│ ├── Internal
│ ├── ISystemsManagerProcessor.cs
│ ├── DictionaryExtensions.cs
│ ├── AwsOptionsProvider.cs
│ ├── ServiceClientAppender.cs
│ ├── JsonConfigurationParser.cs
│ └── SystemsManagerProcessor.cs
│ ├── SystemsManagerExceptionContext.cs
│ ├── IParameterProcessor.cs
│ ├── JsonOrStringParameterProcessor.cs
│ ├── JsonParameterProcessor.cs
│ ├── ISystemsManagerConfigurationSource.cs
│ ├── DuplicateParameterException.cs
│ ├── ConfigurationExtensions.cs
│ ├── SystemsManagerConfigurationSource.cs
│ ├── DefaultParameterProcessor.cs
│ ├── AppConfig
│ ├── AppConfigConfigurationSource.cs
│ ├── AppConfigProcessor.cs
│ └── AppConfigForLambdaExtensions.cs
│ ├── Amazon.Extensions.Configuration.SystemsManager.csproj
│ ├── Utils
│ └── ParameterProcessorUtil.cs
│ ├── SystemsManagerConfigurationProvider.cs
│ └── SystemsManagerExtensions.cs
├── test
├── Amazon.Extensions.Configuration.SystemsManager.Integ
│ ├── Amazon.Extensions.Configuration.SystemsManager.Integ.csproj
│ ├── ConfigurationBuilderIntegrationTests.cs
│ ├── ConfigurtaionBuilderIntegrationTestFixture.cs
│ └── AppConfigEndToEndTests.cs
└── Amazon.Extensions.Configuration.SystemsManager.Tests
│ ├── Amazon.Extensions.Configuration.SystemsManager.Tests.csproj
│ ├── SystemsManagerProcessorTests.cs
│ ├── AppConfigConfigurationSourceTests.cs
│ ├── SystemsManagerConfigurationSourceTests.cs
│ ├── DictionaryExtensionsTests.cs
│ ├── JsonParameterProcessorTests.cs
│ ├── AppConfigExtensionsTests.cs
│ ├── AppConfigForLambdaExtensionsTests.cs
│ ├── Utils
│ └── ParameterProcessorUtilTests.cs
│ ├── SystemsManagerConfigurationProviderTests.cs
│ ├── DefaultParameterProcessorTests.cs
│ ├── SystemsManagerExtensionsTests.cs
│ ├── JsonOrStringParameterProcessorTests.cs
│ └── AppConfigProcessorTests.cs
├── .gitignore
├── Amazon.Extensions.Configuration.sln
├── CONTRIBUTING.md
├── CHANGELOG.md
└── LICENSE
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws/aws-dotnet-extensions-configuration/HEAD/icon.png
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws/aws-dotnet-extensions-configuration/HEAD/logo.png
--------------------------------------------------------------------------------
/public.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws/aws-dotnet-extensions-configuration/HEAD/public.snk
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | AWS Dotnet Extensions Configuration
2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 |
--------------------------------------------------------------------------------
/samples/Samples/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning"
5 | }
6 | },
7 | "AllowedHosts": "*"
8 | }
9 |
--------------------------------------------------------------------------------
/samples/Samples/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWS": {
3 | "Region": "us-east-1"
4 | },
5 | "Logging": {
6 | "LogLevel": {
7 | "Default": "Debug",
8 | "System": "Information",
9 | "Microsoft": "Information"
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | ---
2 | blank_issues_enabled: false
3 | contact_links:
4 | - name: 💬 General Question
5 | url: https://github.com/aws/aws-dotnet-extensions-configuration/discussions/categories/q-a
6 | about: Please ask and answer questions as a discussion thread
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/samples/Samples/Settings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Samples
4 | {
5 | public class Settings
6 | {
7 | public string StringValue { get; set; }
8 | public int IntegerValue { get; set; }
9 | public DateTime DateTimeValue { get; set; }
10 | public bool BooleanValue { get; set; }
11 | public TimeSpan TimeSpanValue { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.autover/autover.json:
--------------------------------------------------------------------------------
1 | {
2 | "Projects": [
3 | {
4 | "Name": "Amazon.Extensions.Configuration.SystemsManager",
5 | "Path": "src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj"
6 | }
7 | ],
8 | "UseCommitsForChangelog": false,
9 | "UseSameVersionForAllProjects": false,
10 | "DefaultIncrementType": "Patch",
11 | "ChangeFilesDetermineIncrementType": true
12 | }
--------------------------------------------------------------------------------
/samples/Samples/Samples.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 |
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | # Check for updates to GitHub Actions every quarter
8 | interval: "quarterly"
9 | labels:
10 | - "Release Not Needed"
11 | target-branch: "dev"
12 | # Group all github-actions updates into a single PR
13 | groups:
14 | all-github-actions:
15 | applies-to: "version-updates"
16 | patterns:
17 | - "*"
18 |
--------------------------------------------------------------------------------
/src/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.github/workflows/handle-stale-discussions.yml:
--------------------------------------------------------------------------------
1 | name: HandleStaleDiscussions
2 | on:
3 | schedule:
4 | - cron: '0 */4 * * *'
5 | discussion_comment:
6 | types: [created]
7 |
8 | jobs:
9 | handle-stale-discussions:
10 | name: Handle stale discussions
11 | runs-on: ubuntu-latest
12 | permissions:
13 | discussions: write
14 | steps:
15 | - name: Stale discussions action
16 | uses: aws-github-ops/handle-stale-discussions@c0beee451a5d33d9c8f048a6d4e7c856b5422544 #v1.6.0
17 | env:
18 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: "📕 Documentation Issue"
3 | description: Report an issue in the API Reference documentation or Developer Guide
4 | title: "(short issue description)"
5 | labels: [documentation, needs-triage]
6 | assignees: []
7 | body:
8 | - type: textarea
9 | id: description
10 | attributes:
11 | label: Describe the issue
12 | description: A clear and concise description of the issue.
13 | validations:
14 | required: true
15 |
16 | - type: textarea
17 | id: links
18 | attributes:
19 | label: Links
20 | description: |
21 | Include links to affected documentation page(s).
22 | validations:
23 | required: true
24 |
--------------------------------------------------------------------------------
/samples/Samples/Controllers/ValuesController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.Extensions.Options;
3 |
4 | namespace Samples.Controllers
5 | {
6 | [Route("api/[controller]")]
7 | [ApiController]
8 | public class ValuesController : ControllerBase
9 | {
10 | private readonly IOptions _settings;
11 |
12 | public ValuesController(IOptions settings)
13 | {
14 | _settings = settings;
15 | }
16 |
17 | // GET api/values
18 | [HttpGet]
19 | public ActionResult Get()
20 | {
21 | return Ok(new
22 | {
23 | Settings = _settings.Value
24 | });
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.github/workflows/closed-issue-message.yml:
--------------------------------------------------------------------------------
1 | name: Closed Issue Message
2 | on:
3 | issues:
4 | types: [closed]
5 | permissions:
6 | issues: write
7 |
8 | jobs:
9 | auto_comment:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: aws-actions/closed-issue-message@10aaf6366131b673a7c8b7742f8b3849f1d44f18 #v2
13 | with:
14 | # These inputs are both required
15 | repo-token: "${{ secrets.GITHUB_TOKEN }}"
16 | message: |
17 | ### ⚠️COMMENT VISIBILITY WARNING⚠️
18 | Comments on closed issues are hard for our team to see.
19 | If you need more assistance, please either tag a team member or open a new issue that references this one.
20 | If you wish to keep having a conversation with other community members under this issue feel free to do so.
21 |
--------------------------------------------------------------------------------
/samples/Samples/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:5766",
8 | "sslPort": 0
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "api/values",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "Samples": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "api/values",
24 | "applicationUrl": "http://localhost:5000",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/Internal/ISystemsManagerProcessor.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System.Collections.Generic;
17 | using System.Threading.Tasks;
18 |
19 | namespace Amazon.Extensions.Configuration.SystemsManager.Internal
20 | {
21 | public interface ISystemsManagerProcessor
22 | {
23 | Task> GetDataAsync();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Integ/Amazon.Extensions.Configuration.SystemsManager.Integ.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | true
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.github/workflows/semgrep-analysis.yml:
--------------------------------------------------------------------------------
1 | name: Semgrep
2 |
3 | on:
4 | # Scan changed files in PRs, block on new issues only (existing issues ignored)
5 | pull_request:
6 |
7 | push:
8 | branches: ["dev", "main"]
9 |
10 | schedule:
11 | - cron: '23 20 * * 1'
12 |
13 | # Manually trigger the workflow
14 | workflow_dispatch:
15 |
16 | jobs:
17 | semgrep:
18 | name: Scan
19 | permissions:
20 | security-events: write
21 | runs-on: ubuntu-latest
22 | container:
23 | image: returntocorp/semgrep
24 | # Skip any PR created by dependabot to avoid permission issues
25 | if: (github.actor != 'dependabot[bot]')
26 | steps:
27 | # Fetch project source
28 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
29 |
30 | - run: semgrep ci --sarif > semgrep.sarif
31 | env:
32 | SEMGREP_RULES: >- # more at semgrep.dev/explore
33 | p/security-audit
34 | p/secrets
35 | p/owasp-top-ten
36 |
37 | - name: Upload SARIF file for GitHub Advanced Security Dashboard
38 | uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 #v3.30.5
39 | with:
40 | sarif_file: semgrep.sarif
41 | if: always()
42 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/Amazon.Extensions.Configuration.SystemsManager.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | true
6 | false
7 | true
8 | ..\..\public.snk
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/.github/workflows/issue-regression-labeler.yml:
--------------------------------------------------------------------------------
1 | # Apply potential regression label on issues
2 | name: issue-regression-label
3 | on:
4 | issues:
5 | types: [opened, edited]
6 | jobs:
7 | add-regression-label:
8 | runs-on: ubuntu-latest
9 | permissions:
10 | issues: write
11 | steps:
12 | - name: Fetch template body
13 | id: check_regression
14 | uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #v8.0.0
15 | env:
16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 | TEMPLATE_BODY: ${{ github.event.issue.body }}
18 | with:
19 | script: |
20 | const regressionPattern = /\[x\] Select this option if this issue appears to be a regression\./i;
21 | const template = `${process.env.TEMPLATE_BODY}`
22 | const match = regressionPattern.test(template);
23 | core.setOutput('is_regression', match);
24 | - name: Manage regression label
25 | env:
26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27 | run: |
28 | if [ "${{ steps.check_regression.outputs.is_regression }}" == "true" ]; then
29 | gh issue edit ${{ github.event.issue.number }} --add-label "potential-regression" -R ${{ github.repository }}
30 | else
31 | gh issue edit ${{ github.event.issue.number }} --remove-label "potential-regression" -R ${{ github.repository }}
32 | fi
33 |
--------------------------------------------------------------------------------
/.github/workflows/change-file-in-pr.yml:
--------------------------------------------------------------------------------
1 | name: Change File Included in PR
2 |
3 | on:
4 | pull_request:
5 | types: [opened, synchronize, reopened, labeled]
6 |
7 | permissions:
8 | contents: read
9 |
10 | jobs:
11 | check-files-in-directory:
12 | if: ${{ !contains(github.event.pull_request.labels.*.name, 'Release Not Needed') && !contains(github.event.pull_request.labels.*.name, 'Release PR') }}
13 | name: Change File Included in PR
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Checkout PR code
18 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
19 |
20 | - name: Get List of Changed Files
21 | id: changed-files
22 | uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c #v45
23 |
24 | - name: Check for Change File(s) in .autover/changes/
25 | run: |
26 | DIRECTORY=".autover/changes/"
27 | if echo "${{ steps.changed-files.outputs.all_changed_files }}" | grep -q "$DIRECTORY"; then
28 | echo "✅ One or more change files in '$DIRECTORY' are included in this PR."
29 | else
30 | echo "❌ No change files in '$DIRECTORY' are included in this PR."
31 | echo "Refer to the 'Adding a change file to your contribution branch' section of https://github.com/aws/aws-dotnet-extensions-configuration/blob/master/CONTRIBUTING.md"
32 | exit 1
33 | fi
34 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/SystemsManagerProcessorTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Amazon.Extensions.Configuration.SystemsManager.Internal;
4 | using Amazon.SimpleSystemsManagement.Model;
5 | using Moq;
6 | using Xunit;
7 |
8 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
9 | {
10 | public class SystemsManagerProcessorTests
11 | {
12 | [Theory]
13 | [InlineData("/aws/reference/secretsmanager/", true)]
14 | [InlineData("/not-sm-path/", false)]
15 | public void IsSecretsManagerPathTest(string path, bool expected)
16 | {
17 | Assert.Equal(expected, SystemsManagerProcessor.IsSecretsManagerPath(path));
18 | }
19 |
20 | [Theory]
21 | [InlineData(null)]
22 | [InlineData("prefix")]
23 | public void AddPrefixTest(string prefix)
24 | {
25 | var data = new Dictionary { { "Key", "Value" } };
26 | var output = SystemsManagerProcessor.AddPrefix(data, prefix);
27 |
28 | if (prefix == null)
29 | {
30 | Assert.Equal(data, output);
31 | }
32 | else
33 | {
34 | foreach (var item in output)
35 | {
36 | Assert.StartsWith($"{prefix}:", item.Key);
37 | }
38 | }
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ######################################
2 | # Visual Studio per-user settings data
3 | ######################################
4 | *.suo
5 | *.user
6 |
7 | ####################
8 | # Build/Test folders
9 | ####################
10 |
11 | **/bin/
12 | **/obj/
13 | **/TestResults/
14 | **/Temp/
15 | **/buildlogs/
16 | **/.vs/
17 |
18 | **/*project.lock.json
19 | **/project.lock.json
20 | **/*.nuspec
21 |
22 | sdk/packages/**
23 | sdk/test/PCLTests/AndroidTests/packages/**
24 | sdk/.vs/**
25 |
26 | **/fxcop-report-*.xml
27 |
28 | docgenerator/.vs/**
29 |
30 | Deployment/**
31 | DocDeployment/**
32 | DocBuildAssemblies/**
33 | Include/**
34 |
35 | **/*.userprefs
36 | /sdk/test/CrossPlatformTests/CommonTests/Resources/settings.json
37 | packages
38 | /sdk/xamarin-components/**/*.xam
39 | /sdk/test/Unity/Unity3DTests/Assets/Resources/settings.json
40 | /sdk/test/Unity/Unity3DTests/Assets/Resources/awsconfig.xml
41 | /sdk/test/Unity/Unity3DTests/Unity3DTests.CSharp.csproj
42 | /sdk/test/Unity/Unity3DTests/Unity3DTests.CSharp.Editor.csproj
43 | /sdk/test/Unity/Unity3DTests/Unity3DTests.sln
44 | /sdk/test/Unity/Unity3DTests/Library
45 | /sdk/test/Unity/Unity3DTests/Assets/SDK/*.dll
46 | /sdk/test/Unity/Unity3DTests/Assets/SDK/*.pdb
47 |
48 | *.bak
49 | generator/.vs/**
50 | *.mdb
51 | *.apk
52 | *.meta
53 | /sdk/test/Unity/AWSAndroidHelper/.settings
54 | /sdk/test/Unity3DTests/Assets/SDK/*.mdb
55 |
56 | **/*.partial.sln
57 | **/*.partial.FxCop
58 | **/*.partial.csproj
59 |
60 | **/.DS_Store
61 |
62 | #JetBrains Rider user folders
63 | .idea/
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/SystemsManagerExceptionContext.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using Microsoft.Extensions.Configuration;
18 |
19 | namespace Amazon.Extensions.Configuration.SystemsManager
20 | {
21 | /// Contains information about a Systems Manager load exception.
22 | public class SystemsManagerExceptionContext
23 | {
24 | ///
25 | /// The that caused the exception.
26 | ///
27 | public IConfigurationProvider Provider { get; set; }
28 |
29 | /// The exception that occured in Load.
30 | public Exception Exception { get; set; }
31 |
32 | /// If true, the exception will not be rethrown.
33 | public bool Ignore { get; set; }
34 |
35 | /// If true, the exception was raised on a reload event
36 | public bool Reload { get; set; }
37 | }
38 | }
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigConfigurationSourceTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using Amazon.Extensions.Configuration.SystemsManager.AppConfig;
17 | using Amazon.Extensions.NETCore.Setup;
18 | using Microsoft.Extensions.Configuration;
19 | using Xunit;
20 |
21 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
22 | {
23 | public class AppConfigConfigurationSourceTests
24 | {
25 | [Fact]
26 | public void BuildShouldReturnSystemsManagerConfigurationProvider()
27 | {
28 | var source = new AppConfigConfigurationSource
29 | {
30 | ApplicationId = "appId",
31 | EnvironmentId = "envId",
32 | ConfigProfileId = "profileId",
33 | AwsOptions = new AWSOptions()
34 | };
35 | var builder = new ConfigurationBuilder();
36 |
37 | var result = source.Build(builder);
38 |
39 | Assert.IsType(result);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/SystemsManagerConfigurationSourceTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using Amazon.Extensions.NETCore.Setup;
17 | using Microsoft.Extensions.Configuration;
18 | using Xunit;
19 |
20 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
21 | {
22 | public class SystemsManagerConfigurationSourceTests
23 | {
24 | [Fact]
25 | public void BuildSuccessTest()
26 | {
27 | var source = new SystemsManagerConfigurationSource
28 | {
29 | AwsOptions = new AWSOptions(),
30 | Path = "/temp/"
31 | };
32 | var builder = new ConfigurationBuilder();
33 |
34 | var result = source.Build(builder);
35 |
36 | Assert.IsType(result);
37 | }
38 |
39 | [Fact]
40 | public void FiltersInitializedTest()
41 | {
42 | var source = new SystemsManagerConfigurationSource();
43 |
44 | Assert.NotNull(source.Filters);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/IParameterProcessor.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using Amazon.SimpleSystemsManagement.Model;
19 | using Microsoft.Extensions.Configuration;
20 |
21 | namespace Amazon.Extensions.Configuration.SystemsManager
22 | {
23 | ///
24 | /// Processor responsible for deciding if a should be included and processing the Key
25 | ///
26 | public interface IParameterProcessor
27 | {
28 | ///
29 | /// Process parameters from AWS Parameter Store into a dictionary for
30 | ///
31 | /// Enumeration of s to be processed
32 | /// Path used when retrieving the
33 | /// Configuration values for
34 | IDictionary ProcessParameters(IEnumerable parameters, string path);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/samples/Samples.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32126.317
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Extensions.Configuration.SystemsManager", "..\src\Amazon.Extensions.Configuration.SystemsManager\Amazon.Extensions.Configuration.SystemsManager.csproj", "{CE965321-158B-47C8-BDA3-748F18745532}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "Samples\Samples.csproj", "{74F8A828-62EB-4338-A106-F28D91FE4B3A}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {CE965321-158B-47C8-BDA3-748F18745532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {CE965321-158B-47C8-BDA3-748F18745532}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {CE965321-158B-47C8-BDA3-748F18745532}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {CE965321-158B-47C8-BDA3-748F18745532}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {74F8A828-62EB-4338-A106-F28D91FE4B3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {74F8A828-62EB-4338-A106-F28D91FE4B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {74F8A828-62EB-4338-A106-F28D91FE4B3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {74F8A828-62EB-4338-A106-F28D91FE4B3A}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {5B66FEC1-55C5-47A4-8ADF-8A853A41AA42}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/Internal/DictionaryExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System.Collections.Generic;
17 |
18 | namespace Amazon.Extensions.Configuration.SystemsManager.Internal
19 | {
20 | public static class DictionaryExtensions
21 | {
22 | public static bool EquivalentTo(this IDictionary first, IDictionary second) => EquivalentTo(first, second, null);
23 |
24 | public static bool EquivalentTo(this IDictionary first, IDictionary second, IEqualityComparer valueComparer)
25 | {
26 | if (first == second) return true;
27 | if (first == null || second == null) return false;
28 | if (first.Count != second.Count) return false;
29 |
30 | valueComparer = valueComparer ?? EqualityComparer.Default;
31 |
32 | foreach (var kvp in first)
33 | {
34 | if (!second.TryGetValue(kvp.Key, out var secondValue)) return false;
35 | if (!valueComparer.Equals(kvp.Value, secondValue)) return false;
36 | }
37 | return true;
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/DictionaryExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Amazon.Extensions.Configuration.SystemsManager.Internal;
3 | using Xunit;
4 |
5 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
6 | {
7 | public class DictionaryExtensionsTests
8 | {
9 | [Theory]
10 | [MemberData(nameof(EquivalentToData))]
11 | public void TestEquivalentTo(IDictionary first, IDictionary second, bool equals)
12 | {
13 | Assert.Equal(equals, first.EquivalentTo(second));
14 | }
15 |
16 | public static TheoryData, IDictionary, bool> EquivalentToData => new TheoryData, IDictionary, bool>
17 | {
18 | {new Dictionary(), new Dictionary(), true},
19 | {new Dictionary(), null, false},
20 | {new Dictionary(), new Dictionary {{"a", "a"}}, false},
21 | {new Dictionary {{"a", "a"}}, new Dictionary {{"a", "a"}}, true},
22 | {new Dictionary {{"a", "a"}}, new Dictionary {{"a", "a"}, {"b", "b"}}, false},
23 | {new Dictionary {{"a", "a"}}, new Dictionary {{"b", "b"}}, false},
24 | {new Dictionary {{"a", "a"}}, new Dictionary {{"a", "b"}}, false},
25 | {new Dictionary {{"a", "a"}}, new Dictionary {{"b", "a"}}, false},
26 | {new Dictionary {{"a", "a"},{"b", "b"}}, new Dictionary {{"b", "b"},{"a", "a"}}, true}
27 | };
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/JsonOrStringParameterProcessor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.Json;
5 | using Amazon.SimpleSystemsManagement;
6 | using Amazon.SimpleSystemsManagement.Model;
7 | using static Amazon.Extensions.Configuration.SystemsManager.Utils.ParameterProcessorUtil;
8 |
9 | namespace Amazon.Extensions.Configuration.SystemsManager
10 | {
11 | ///
12 | ///
13 | /// A processor that prioritizes JSON parameters but falls back to string parameters,
14 | /// in accordance with Systems Manager's suggested naming conventions
15 | ///
16 | public class JsonOrStringParameterProcessor : DefaultParameterProcessor
17 | {
18 | public override IDictionary ProcessParameters(IEnumerable parameters, string path)
19 | {
20 | var result = new Dictionary(StringComparer.OrdinalIgnoreCase);
21 |
22 | foreach (var parameter in parameters.Where(parameter => IncludeParameter(parameter, path)))
23 | {
24 | var keyPrefix = GetKey(parameter, path);
25 | var value = GetValue(parameter, path);
26 |
27 | if (parameter.Type == ParameterType.StringList)
28 | {
29 | ParseStringListParameter(keyPrefix, value, result);
30 | continue;
31 | }
32 |
33 | try
34 | {
35 | ParseJsonParameter(keyPrefix, value, result);
36 | }
37 | catch (JsonException)
38 | {
39 | ParseStringParameter(keyPrefix, value, result);
40 | }
41 | }
42 |
43 | return result;
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Integ/ConfigurationBuilderIntegrationTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using Microsoft.Extensions.Configuration;
18 | using Xunit;
19 |
20 | namespace Amazon.Extensions.Configuration.SystemsManager.Integ
21 | {
22 | public class ConfigurationBuilderIntegrationTests : IClassFixture
23 | {
24 | private IntegTestFixture fixture;
25 |
26 | public ConfigurationBuilderIntegrationTests(IntegTestFixture fixture)
27 | {
28 | this.fixture = fixture;
29 | }
30 |
31 | [Fact]
32 | public void TestConfigurationBuilder()
33 | {
34 | var configurationBuilder = new ConfigurationBuilder();
35 | configurationBuilder.AddSystemsManager(IntegTestFixture.ParameterPrefix, fixture.AWSOptions);
36 | var configurations = configurationBuilder.Build();
37 |
38 | Assert.All(fixture.TestData, (pair) => {
39 | Assert.Equal(pair.Value, configurations[pair.Key]);
40 | });
41 |
42 | // Since there is no reload going on this should return back immediately.
43 | configurations.WaitForSystemsManagerReloadToComplete(TimeSpan.FromHours(1));
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/.github/workflows/aws-ci.yml:
--------------------------------------------------------------------------------
1 | name: AWS CI
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | branches:
7 | - main
8 | - dev
9 | - 'feature/**'
10 |
11 | permissions:
12 | id-token: write
13 |
14 | jobs:
15 | run-ci:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Configure AWS Credentials
19 | uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v4
20 | with:
21 | role-to-assume: ${{ secrets.CI_MAIN_TESTING_ACCOUNT_ROLE_ARN }}
22 | role-duration-seconds: 7200
23 | aws-region: us-west-2
24 | - name: Invoke Load Balancer Lambda
25 | id: lambda
26 | shell: pwsh
27 | run: |
28 | aws lambda invoke response.json --function-name "${{ secrets.CI_TESTING_LOAD_BALANCER_LAMBDA_NAME }}" --cli-binary-format raw-in-base64-out --payload '{"Roles": "${{ secrets.CI_TEST_RUNNER_ACCOUNT_ROLES }}", "ProjectName": "${{ secrets.CI_TESTING_CODE_BUILD_PROJECT_NAME }}", "Branch": "${{ github.sha }}"}'
29 | $roleArn=$(cat ./response.json)
30 | "roleArn=$($roleArn -replace '"', '')" >> $env:GITHUB_OUTPUT
31 | - name: Configure Test Runner Credentials
32 | uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v4
33 | with:
34 | role-to-assume: ${{ steps.lambda.outputs.roleArn }}
35 | role-duration-seconds: 7200
36 | aws-region: us-west-2
37 | - name: Run Tests on AWS
38 | id: codebuild
39 | uses: aws-actions/aws-codebuild-run-build@4d15a47425739ac2296ba5e7eee3bdd4bfbdd767 #v1.0.18
40 | with:
41 | project-name: ${{ secrets.CI_TESTING_CODE_BUILD_PROJECT_NAME }}
42 | - name: CodeBuild Link
43 | shell: pwsh
44 | run: |
45 | $buildId = "${{ steps.codebuild.outputs.aws-build-id }}"
46 | echo $buildId
47 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/Internal/AwsOptionsProvider.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using Amazon.Extensions.NETCore.Setup;
17 | using Microsoft.Extensions.Configuration;
18 |
19 | namespace Amazon.Extensions.Configuration.SystemsManager.Internal
20 | {
21 | public static class AwsOptionsProvider
22 | {
23 | private const string AwsOptionsConfigurationKey = "AWS_CONFIGBUILDER_AWSOPTIONS";
24 |
25 | public static AWSOptions GetAwsOptions(IConfigurationBuilder builder)
26 | {
27 | if (builder.Properties.TryGetValue(AwsOptionsConfigurationKey, out var value) && value is AWSOptions existingOptions)
28 | {
29 | return existingOptions;
30 | }
31 |
32 | var config = builder.Build();
33 | var newOptions = config.GetAWSOptions();
34 |
35 | if (builder.Properties.ContainsKey(AwsOptionsConfigurationKey))
36 | {
37 | builder.Properties[AwsOptionsConfigurationKey] = newOptions;
38 | }
39 | else
40 | {
41 | builder.Properties.Add(AwsOptionsConfigurationKey, newOptions);
42 | }
43 |
44 | return newOptions;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/JsonParameterProcessor.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.Linq;
19 | using Amazon.SimpleSystemsManagement.Model;
20 | using static Amazon.Extensions.Configuration.SystemsManager.Utils.ParameterProcessorUtil;
21 |
22 | namespace Amazon.Extensions.Configuration.SystemsManager
23 | {
24 | ///
25 | ///
26 | /// A processor specifically designed for handling JSON parameters,
27 | /// following Systems Manager's recommended naming conventions
28 | ///
29 | public class JsonParameterProcessor : DefaultParameterProcessor
30 | {
31 | public override IDictionary ProcessParameters(IEnumerable parameters, string path)
32 | {
33 | var result = new Dictionary(StringComparer.OrdinalIgnoreCase);
34 |
35 | foreach (var parameter in parameters.Where(parameter => IncludeParameter(parameter, path)))
36 | {
37 | var keyPrefix = GetKey(parameter, path);
38 | var value = GetValue(parameter, path);
39 |
40 | ParseJsonParameter(keyPrefix, value, result);
41 | }
42 |
43 | return result;
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/ISystemsManagerConfigurationSource.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using Microsoft.Extensions.Configuration;
18 |
19 | namespace Amazon.Extensions.Configuration.SystemsManager
20 | {
21 | ///
22 | ///
23 | /// Represents AWS Systems Manager variables as an .
24 | ///
25 | public interface ISystemsManagerConfigurationSource : IConfigurationSource
26 | {
27 | ///
28 | /// Determines if loading configuration data from AWS Systems Manager Parameter Store is optional.
29 | ///
30 | #pragma warning disable CA1716 // Identifiers should not match keywords: This would be a breaking change to change this name now.
31 | bool Optional { get; set; }
32 | #pragma warning restore CA1716 // Identifiers should not match keywords
33 |
34 | ///
35 | /// Parameters will be reloaded from the AWS Systems Manager Parameter Store after the specified time frame
36 | ///
37 | TimeSpan? ReloadAfter { get; set; }
38 |
39 | ///
40 | /// Will be called if an uncaught exception occurs in .Load.
41 | ///
42 | Action OnLoadException { get; set; }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/stale_issues.yml:
--------------------------------------------------------------------------------
1 | name: "Close stale issues"
2 |
3 | # Controls when the action will run.
4 | on:
5 | schedule:
6 | - cron: "0 0 * * *"
7 |
8 | permissions:
9 | issues: write
10 | pull-requests: write
11 |
12 | jobs:
13 | cleanup:
14 | runs-on: ubuntu-latest
15 | name: Stale issue job
16 | steps:
17 | - uses: aws-actions/stale-issue-cleanup@5650b49bcd757a078f6ca06c373d7807b773f9bc #v7.1.0
18 | with:
19 | # Setting messages to an empty string will cause the automation to skip
20 | # that category
21 | ancient-issue-message: We have noticed this issue has not received attention in 1 year. We will close this issue for now. If you think this is in error, please feel free to comment and reopen the issue.
22 | stale-issue-message: This issue has not received a response in 5 days. If you want to keep this issue open, please just leave a comment below and auto-close will be canceled.
23 |
24 | # These labels are required
25 | stale-issue-label: closing-soon
26 | exempt-issue-labels: no-autoclose
27 | stale-pr-label: no-pr-activity
28 | exempt-pr-labels: awaiting-approval
29 | response-requested-label: response-requested
30 |
31 | # Don't set closed-for-staleness label to skip closing very old issues
32 | # regardless of label
33 | closed-for-staleness-label: closed-for-staleness
34 |
35 | # Issue timing
36 | days-before-stale: 5
37 | days-before-close: 2
38 | days-before-ancient: 36500
39 |
40 | # If you don't want to mark a issue as being ancient based on a
41 | # threshold of "upvotes", you can set this here. An "upvote" is
42 | # the total number of +1, heart, hooray, and rocket reactions
43 | # on an issue.
44 | minimum-upvotes-to-exempt: 10
45 |
46 | repo-token: ${{ secrets.GITHUB_TOKEN }}
47 | #loglevel: DEBUG
48 | # Set dry-run to true to not perform label or close actions.
49 | #dry-run: true
50 |
51 |
--------------------------------------------------------------------------------
/samples/Samples/Program.cs:
--------------------------------------------------------------------------------
1 | using Amazon;
2 | using Amazon.Extensions.NETCore.Setup;
3 | using Amazon.SimpleSystemsManagement;
4 | using Amazon.SimpleSystemsManagement.Model;
5 | using Samples;
6 |
7 | //populates some sample data to be used by this example project
8 | await PopulateSampleDataForThisProject().ConfigureAwait(false);
9 |
10 | var builder = WebApplication.CreateBuilder(args);
11 |
12 | builder.Configuration.AddSystemsManager($"/dotnet-aws-samples/systems-manager-sample/");
13 |
14 | builder.Services.Configure(builder.Configuration.GetSection($"common:settings"));
15 |
16 | // Add services to the container.
17 | builder.Services.AddControllers();
18 |
19 | var app = builder.Build();
20 |
21 | app.UseHttpsRedirection();
22 | app.UseAuthorization();
23 | app.MapControllers();
24 |
25 | app.Run();
26 |
27 |
28 | static async Task PopulateSampleDataForThisProject()
29 | {
30 | var awsOptions = new AWSOptions { Region = RegionEndpoint.USEast1 };
31 |
32 | var root = $"/dotnet-aws-samples/systems-manager-sample/common";
33 | var parameters = new[]
34 | {
35 | new {Name = "StringValue", Value = "string-value"},
36 | new {Name = "IntegerValue", Value = "10"},
37 | new {Name = "DateTimeValue", Value = "2000-01-01"},
38 | new {Name = "BooleanValue", Value = "True"},
39 | new {Name = "TimeSpanValue", Value = "00:05:00"},
40 | };
41 |
42 | using (var client = awsOptions.CreateServiceClient())
43 | {
44 | var result = await client.GetParametersByPathAsync(new GetParametersByPathRequest { Path = root, Recursive = true }).ConfigureAwait(false);
45 | if (result.Parameters.Count == parameters.Length) return;
46 |
47 | foreach (var parameter in parameters)
48 | {
49 | var name = $"{root}/settings/{parameter.Name}";
50 | await client.PutParameterAsync(new PutParameterRequest { Name = name, Value = parameter.Value, Type = ParameterType.String, Overwrite = true }).ConfigureAwait(false);
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/Internal/ServiceClientAppender.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System.Reflection;
17 | using Amazon.Extensions.Configuration.SystemsManager.AppConfig;
18 | using Amazon.Runtime;
19 |
20 | namespace Amazon.Extensions.Configuration.SystemsManager.Internal
21 | {
22 | public static class ServiceClientAppender
23 | {
24 | private static readonly string AssemblyVersion = typeof(AppConfigProcessor).GetTypeInfo().Assembly.GetName().Version.ToString();
25 | private static readonly string UserAgentSuffix = $"lib/SSMConfigProvider#{AssemblyVersion}";
26 |
27 | public static void ServiceClientBeforeRequestEvent(object sender, RequestEventArgs e)
28 | {
29 | WebServiceRequestEventArgs args = e as WebServiceRequestEventArgs;
30 | if (args != null && args.Request is Amazon.Runtime.Internal.IAmazonWebServiceRequest internalRequest &&
31 | #if NET8_0_OR_GREATER
32 | !internalRequest.UserAgentDetails.GetCustomUserAgentComponents().Contains(UserAgentSuffix, System.StringComparison.InvariantCulture)
33 | #else
34 | !internalRequest.UserAgentDetails.GetCustomUserAgentComponents().Contains(UserAgentSuffix)
35 | #endif
36 | )
37 | {
38 | internalRequest.UserAgentDetails.AddUserAgentComponent(UserAgentSuffix);
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Description
4 |
5 |
6 | ## Motivation and Context
7 |
8 |
9 |
10 | ## Testing
11 |
12 |
13 |
14 |
15 | ## Screenshots (if appropriate)
16 |
17 | ## Types of changes
18 |
19 | - [ ] Bug fix (non-breaking change which fixes an issue)
20 | - [ ] New feature (non-breaking change which adds functionality)
21 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
22 |
23 | ## Checklist
24 |
25 |
26 | - [ ] My code follows the code style of this project
27 | - [ ] My change requires a change to the documentation
28 | - [ ] I have updated the documentation accordingly
29 | - [ ] I have read the **README** document
30 | - [ ] I have added tests to cover my changes
31 | - [ ] All new and existing tests passed
32 |
33 | ## License
34 |
35 |
36 |
37 | - [ ] I confirm that this pull request can be released under the Apache 2 license
38 |
39 | [issues]: https://github.com/aws/aws-dotnet-extensions-configuration/issues
40 | [license]: http://aws.amazon.com/apache2.0/
41 | [cla]: http://en.wikipedia.org/wiki/Contributor_License_Agreement
42 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/DuplicateParameterException.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 |
18 | namespace Amazon.Extensions.Configuration.SystemsManager
19 | {
20 | ///
21 | /// This exception is thrown when duplicate Systems Manager parameter keys (case insensitive) are
22 | /// detected irrespective of whether the parameter is optional or not.
23 | ///
24 | /// For example, keys /some-path/some-key and /some-path/SOME-KEY are considered as duplicates.
25 | ///
26 | ///
27 | public class DuplicateParameterException : ArgumentException
28 | {
29 | ///
30 | /// Initializes a new instance of DuplicateParameterException.
31 | ///
32 | public DuplicateParameterException()
33 | {
34 | }
35 |
36 | ///
37 | /// Initializes a new instance of DuplicateParameterException.
38 | ///
39 | /// The message that describes the error.
40 | public DuplicateParameterException(string message)
41 | : base(message)
42 | {
43 | }
44 |
45 | ///
46 | /// Initializes a new instance of DuplicateParameterException.
47 | ///
48 | /// The message that describes the error.
49 | /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified.
50 | public DuplicateParameterException(string message, Exception innerException)
51 | : base(message, innerException)
52 | {
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/ConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using Microsoft.Extensions.Configuration;
18 |
19 | namespace Amazon.Extensions.Configuration.SystemsManager
20 | {
21 | ///
22 | /// This extension is an a different namespace to avoid misuse of this method which should only be called when being used from AWS Lambda.
23 | ///
24 | public static class ConfigurationExtensions
25 | {
26 | ///
27 | /// This method blocks while any SystemsManagerConfigurationProvider added to IConfiguration are
28 | /// currently reloading the parameters from Parameter Store.
29 | ///
30 | /// This is generally only needed when the provider is being called from a AWS Lambda function. Without this call
31 | /// in a AWS Lambda environment there is a potential of the background thread doing the refresh never running successfully.
32 | /// This can happen because the AWS Lambda compute environment is frozen after the current AWS Lambda event is complete.
33 | ///
34 | ///
35 | /// Maximum time to wait for reload to be completed
36 | public static void WaitForSystemsManagerReloadToComplete(this IConfiguration configuration, TimeSpan timeout)
37 | {
38 | if (configuration is ConfigurationRoot configRoot)
39 | {
40 | foreach (var provider in configRoot.Providers)
41 | {
42 | if (provider is SystemsManagerConfigurationProvider ssmProvider)
43 | {
44 | ssmProvider.WaitForReloadToComplete(timeout);
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🚀 Feature Request
3 | description: Suggest an idea for this project
4 | title: "(short issue description)"
5 | labels: [feature-request, needs-triage]
6 | assignees: []
7 | body:
8 | - type: textarea
9 | id: description
10 | attributes:
11 | label: Describe the feature
12 | description: A clear and concise description of the feature you are proposing.
13 | validations:
14 | required: true
15 | - type: textarea
16 | id: use-case
17 | attributes:
18 | label: Use Case
19 | description: |
20 | Why do you need this feature? For example: "I'm always frustrated when..."
21 | validations:
22 | required: true
23 | - type: textarea
24 | id: solution
25 | attributes:
26 | label: Proposed Solution
27 | description: |
28 | Suggest how to implement the addition or change. Please include prototype/workaround/sketch/reference implementation.
29 | validations:
30 | required: false
31 | - type: textarea
32 | id: other
33 | attributes:
34 | label: Other Information
35 | description: |
36 | Any alternative solutions or features you considered, a more detailed explanation, stack traces, related issues, links for context, etc.
37 | validations:
38 | required: false
39 | - type: checkboxes
40 | id: ack
41 | attributes:
42 | label: Acknowledgements
43 | options:
44 | - label: I may be able to implement this feature request
45 | required: false
46 | - label: This feature might incur a breaking change
47 | required: false
48 |
49 | - type: textarea
50 | id: dotnet-sdk-version
51 | attributes:
52 | label: AWS .NET SDK and/or Package version used
53 | description: NuGet Packages used
54 | placeholder: AWSSDK.S3 3.7.8.13
55 | validations:
56 | required: true
57 |
58 | - type: input
59 | id: platform-used
60 | attributes:
61 | label: Targeted .NET Platform
62 | description: "Example: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc."
63 | placeholder: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc.
64 | validations:
65 | required: true
66 |
67 | - type: input
68 | id: operating-system
69 | attributes:
70 | label: Operating System and version
71 | description: "Example: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc."
72 | placeholder: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc.
73 | validations:
74 | required: true
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/SystemsManagerConfigurationSource.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using Amazon.Extensions.NETCore.Setup;
19 | using Amazon.SimpleSystemsManagement.Model;
20 | using Microsoft.Extensions.Configuration;
21 |
22 | namespace Amazon.Extensions.Configuration.SystemsManager
23 | {
24 | ///
25 | ///
26 | /// Represents AWS Systems Manager Parameter Store variables as an .
27 | ///
28 | public class SystemsManagerConfigurationSource : ISystemsManagerConfigurationSource
29 | {
30 | public SystemsManagerConfigurationSource()
31 | {
32 | Filters = new List();
33 | }
34 |
35 | ///
36 | /// A Path used to filter parameters.
37 | ///
38 | public string Path { get; set; }
39 |
40 | ///
41 | /// used to create an AWS Systems Manager Client />.
42 | ///
43 | public AWSOptions AwsOptions { get; set; }
44 |
45 | ///
46 | public bool Optional { get; set; }
47 |
48 | ///
49 | public TimeSpan? ReloadAfter { get; set; }
50 |
51 | ///
52 | /// Prepends the supplied Prefix to all result keys
53 | ///
54 | public string Prefix { get; set; }
55 |
56 | ///
57 | public Action OnLoadException { get; set; }
58 |
59 | ///
60 | /// Implementation of used to process results. Defaults to .
61 | ///
62 | public IParameterProcessor ParameterProcessor { get; set; }
63 |
64 | ///
65 | /// Filters to limit the request results.
66 | /// You can't filter using the parameter name.
67 | ///
68 | public List Filters { get; }
69 |
70 | ///
71 | public IConfigurationProvider Build(IConfigurationBuilder builder)
72 | {
73 | return new SystemsManagerConfigurationProvider(this);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/JsonParameterProcessorTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text.Json;
3 | using Amazon.SimpleSystemsManagement.Model;
4 | using Xunit;
5 |
6 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
7 | {
8 | public class JsonParameterProcessorTests
9 | {
10 | private readonly IParameterProcessor _parameterProcessor = new JsonParameterProcessor();
11 |
12 | [Fact]
13 | public void ProcessParametersTest()
14 | {
15 | var parameters = new List
16 | {
17 | new Parameter {Name = "/p1", Value = "{\"p1\": \"p1\"}"},
18 | new Parameter {Name = "p2", Value = "{\"p2\": \"p2\"}"},
19 | new Parameter {Name = "/p1/p3", Value = "{\"p3key\": \"p3value\"}"},
20 | new Parameter {Name = "/p4", Value = "{\"p4key\": { \"p5key\": \"p5value\" } }"},
21 | new Parameter {Name = "/p6", Value = "{\"p6key\": { \"p7key\": { \"p8key\": \"p8value\" } } }"},
22 | new Parameter {Name = "/ObjectA", Value = "{\"Bucket\": \"arnA\"}"},
23 | new Parameter {Name = "/ObjectB", Value = "{\"Bucket\": \"arnB\"}"},
24 | new Parameter {Name = "/", Value = "{\"testParam\": \"testValue\"}"}
25 | };
26 | var expected = new Dictionary() {
27 | { "p1:p1", "p1" },
28 | { "p2:p2", "p2" },
29 | { "p1:p3:p3key", "p3value" },
30 | { "p4:p4key:p5key", "p5value" },
31 | { "p6:p6key:p7key:p8key", "p8value" },
32 | { "ObjectA:Bucket", "arnA" },
33 | { "ObjectB:Bucket", "arnB" },
34 | { "testParam", "testValue" }
35 | };
36 |
37 | const string path = "/";
38 |
39 | var data = _parameterProcessor.ProcessParameters(parameters, path);
40 |
41 | Assert.All(expected, item => Assert.Equal(item.Value, data[item.Key]));
42 | }
43 |
44 | [Fact]
45 | public void DuplicateParametersTest()
46 | {
47 | var parameters = new List
48 | {
49 | new Parameter {Name = "/p1", Value = "{\"p1\": \"p1\"}"},
50 | new Parameter {Name = "p2", Value = "{\"p2\": \"p2\"}"},
51 | new Parameter {Name = "p1", Value = "{\"P1\": \"p1-1\"}"},
52 | };
53 |
54 | const string path = "/";
55 | Assert.Throws(() => _parameterProcessor.ProcessParameters(parameters, path));
56 | }
57 |
58 | [Fact]
59 | public void InvalidJsonTest()
60 | {
61 | var parameters = new List
62 | {
63 | new Parameter {Name = "/p1", Value = "String value"},
64 | };
65 |
66 | const string path = "/";
67 | Assert.ThrowsAny(() => _parameterProcessor.ProcessParameters(parameters, path));
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/DefaultParameterProcessor.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.Linq;
19 | using Amazon.SimpleSystemsManagement;
20 | using Amazon.SimpleSystemsManagement.Model;
21 | using Microsoft.Extensions.Configuration;
22 | using static Amazon.Extensions.Configuration.SystemsManager.Utils.ParameterProcessorUtil;
23 |
24 | namespace Amazon.Extensions.Configuration.SystemsManager
25 | {
26 | ///
27 | ///
28 | /// Default parameter processor based on Systems Manager's suggested naming convention
29 | ///
30 | public class DefaultParameterProcessor : IParameterProcessor
31 | {
32 | ///
33 | public static readonly string KeyDelimiter = ConfigurationPath.KeyDelimiter;
34 |
35 | public virtual bool IncludeParameter(Parameter parameter, string path) => true;
36 |
37 | /// Get the extra prefix if the path is subset of parameter name.
38 | public virtual string GetKey(Parameter parameter, string path)
39 | {
40 | var name = parameter.Name.StartsWith(path, StringComparison.OrdinalIgnoreCase)
41 | ? parameter.Name.Substring(path.Length)
42 | : parameter.Name;
43 | #if NET8_0_OR_GREATER
44 | return name.TrimStart('/').Replace("/", KeyDelimiter, StringComparison.InvariantCulture);
45 | #else
46 | return name.TrimStart('/').Replace("/", KeyDelimiter);
47 | #endif
48 | }
49 |
50 | public virtual string GetValue(Parameter parameter, string path) => parameter.Value;
51 |
52 | public virtual IDictionary ProcessParameters(IEnumerable parameters, string path)
53 | {
54 | var result = new Dictionary(StringComparer.OrdinalIgnoreCase);
55 |
56 | foreach (var parameter in parameters.Where(parameter => IncludeParameter(parameter, path)))
57 | {
58 | var keyPrefix = GetKey(parameter, path);
59 | var value = GetValue(parameter, path);
60 |
61 | if (parameter.Type == ParameterType.StringList)
62 | {
63 | ParseStringListParameter(keyPrefix, value, result);
64 | continue;
65 | }
66 |
67 | ParseStringParameter(keyPrefix, value, result);
68 | }
69 |
70 | return result;
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigConfigurationSource.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using System.Net.Http;
18 | using Amazon.Extensions.NETCore.Setup;
19 | using Microsoft.Extensions.Configuration;
20 |
21 | namespace Amazon.Extensions.Configuration.SystemsManager.AppConfig
22 | {
23 | ///
24 | ///
25 | /// Represents AWS Systems Manager AppConfig variables as an .
26 | ///
27 | public class AppConfigConfigurationSource : ISystemsManagerConfigurationSource
28 | {
29 | ///
30 | /// AppConfig Application Id.
31 | ///
32 | public string ApplicationId { get; set; }
33 |
34 | ///
35 | /// AppConfig Environment Id.
36 | ///
37 | public string EnvironmentId { get; set; }
38 |
39 | ///
40 | /// AppConfig Configuration Profile Id.
41 | ///
42 | public string ConfigProfileId { get; set; }
43 |
44 | ///
45 | /// used to create an AWS Systems Manager Client />.
46 | ///
47 | public AWSOptions AwsOptions { get; set; }
48 |
49 | ///
50 | public bool Optional { get; set; }
51 |
52 | ///
53 | public TimeSpan? ReloadAfter { get; set; }
54 |
55 | ///
56 | /// Indicates to use configured lambda extension HTTP client to retrieve AppConfig data.
57 | ///
58 | internal bool UseLambdaExtension { get; set; }
59 |
60 | ///
61 | /// Used only when integrating with the AWS AppConfig Lambda extension.
62 | ///
63 | /// This property allows customizing the HttpClient connecting to the AWS AppConfig Lambda extension. This is useful
64 | /// to instrument the HttpClient for telemetry. For example adding the Amazon.XRay.Recorder.Handlers.System.Net.HttpClientXRayTracingHandler handle for AWS X-Ray tracing.
65 | ///
66 | public HttpClient CustomHttpClientForLambdaExtension { get; set; }
67 |
68 | ///
69 | public Action OnLoadException { get; set; }
70 |
71 | ///
72 | public IConfigurationProvider Build(IConfigurationBuilder builder)
73 | {
74 | return new SystemsManagerConfigurationProvider(this);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Amazon.Extensions.Configuration.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32126.317
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{00A2C0BE-9179-44B3-B30E-7E64C319AE24}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{31D7A0E9-FAC1-4EC5-8F77-CA21B8A4F9F0}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Extensions.Configuration.SystemsManager", "src\Amazon.Extensions.Configuration.SystemsManager\Amazon.Extensions.Configuration.SystemsManager.csproj", "{EC9A4865-6217-4E8D-98EC-12A0F2061F04}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Extensions.Configuration.SystemsManager.Tests", "test\Amazon.Extensions.Configuration.SystemsManager.Tests\Amazon.Extensions.Configuration.SystemsManager.Tests.csproj", "{E8C9EC1F-E466-4100-B55C-569127918CC3}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Extensions.Configuration.SystemsManager.Integ", "test\Amazon.Extensions.Configuration.SystemsManager.Integ\Amazon.Extensions.Configuration.SystemsManager.Integ.csproj", "{60C9F6C0-C13E-45AA-BC1F-C7E8BEA2E022}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{30F7DC8F-EF72-4619-8C48-2EC26B91A530}"
17 | ProjectSection(SolutionItems) = preProject
18 | README.md = README.md
19 | RELEASE.CHANGELOG.md = RELEASE.CHANGELOG.md
20 | EndProjectSection
21 | EndProject
22 | Global
23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
24 | Debug|Any CPU = Debug|Any CPU
25 | Release|Any CPU = Release|Any CPU
26 | EndGlobalSection
27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
28 | {EC9A4865-6217-4E8D-98EC-12A0F2061F04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {EC9A4865-6217-4E8D-98EC-12A0F2061F04}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {EC9A4865-6217-4E8D-98EC-12A0F2061F04}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {EC9A4865-6217-4E8D-98EC-12A0F2061F04}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {E8C9EC1F-E466-4100-B55C-569127918CC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {E8C9EC1F-E466-4100-B55C-569127918CC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {E8C9EC1F-E466-4100-B55C-569127918CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {E8C9EC1F-E466-4100-B55C-569127918CC3}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {60C9F6C0-C13E-45AA-BC1F-C7E8BEA2E022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {60C9F6C0-C13E-45AA-BC1F-C7E8BEA2E022}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {60C9F6C0-C13E-45AA-BC1F-C7E8BEA2E022}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {60C9F6C0-C13E-45AA-BC1F-C7E8BEA2E022}.Release|Any CPU.Build.0 = Release|Any CPU
40 | EndGlobalSection
41 | GlobalSection(SolutionProperties) = preSolution
42 | HideSolutionNode = FALSE
43 | EndGlobalSection
44 | GlobalSection(NestedProjects) = preSolution
45 | {EC9A4865-6217-4E8D-98EC-12A0F2061F04} = {00A2C0BE-9179-44B3-B30E-7E64C319AE24}
46 | {E8C9EC1F-E466-4100-B55C-569127918CC3} = {31D7A0E9-FAC1-4EC5-8F77-CA21B8A4F9F0}
47 | {60C9F6C0-C13E-45AA-BC1F-C7E8BEA2E022} = {31D7A0E9-FAC1-4EC5-8F77-CA21B8A4F9F0}
48 | EndGlobalSection
49 | GlobalSection(ExtensibilityGlobals) = postSolution
50 | SolutionGuid = {6502AC14-8D11-4D72-88EF-154344F3B98D}
51 | EndGlobalSection
52 | EndGlobal
53 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: "🐛 Bug Report"
3 | description: Report a bug
4 | title: "(short issue description)"
5 | labels: [bug, needs-triage]
6 | assignees: []
7 | body:
8 | - type: textarea
9 | id: description
10 | attributes:
11 | label: Describe the bug
12 | description: What is the problem? A clear and concise description of the bug.
13 | validations:
14 | required: true
15 | - type: checkboxes
16 | id: regression
17 | attributes:
18 | label: Regression Issue
19 | description: What is a regression? If it worked in a previous version but doesn't in the latest version, it's considered a regression. In this case, please provide specific version number in the report.
20 | options:
21 | - label: Select this option if this issue appears to be a regression.
22 | required: false
23 | - type: textarea
24 | id: expected
25 | attributes:
26 | label: Expected Behavior
27 | description: |
28 | What did you expect to happen?
29 | validations:
30 | required: true
31 | - type: textarea
32 | id: current
33 | attributes:
34 | label: Current Behavior
35 | description: |
36 | What actually happened?
37 |
38 | Please include full errors, uncaught exceptions, stack traces, and relevant logs.
39 | If service responses are relevant, please include wire logs.
40 | validations:
41 | required: true
42 | - type: textarea
43 | id: reproduction
44 | attributes:
45 | label: Reproduction Steps
46 | description: |
47 | Provide a self-contained, concise snippet of code that can be used to reproduce the issue.
48 | For more complex issues provide a repo with the smallest sample that reproduces the bug.
49 |
50 | Avoid including business logic or unrelated code, it makes diagnosis more difficult.
51 | The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce.
52 | validations:
53 | required: true
54 | - type: textarea
55 | id: solution
56 | attributes:
57 | label: Possible Solution
58 | description: |
59 | Suggest a fix/reason for the bug
60 | validations:
61 | required: false
62 | - type: textarea
63 | id: context
64 | attributes:
65 | label: Additional Information/Context
66 | description: |
67 | Anything else that might be relevant for troubleshooting this bug. Providing context helps us come up with a solution that is most useful in the real world.
68 | validations:
69 | required: false
70 |
71 | - type: textarea
72 | id: dotnet-sdk-version
73 | attributes:
74 | label: AWS .NET SDK and/or Package version used
75 | description: NuGet Packages used
76 | placeholder: AWSSDK.S3 3.7.8.13
77 | validations:
78 | required: true
79 |
80 | - type: input
81 | id: platform-used
82 | attributes:
83 | label: Targeted .NET Platform
84 | description: "Example: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc."
85 | placeholder: .NET Framework 4.7, .NET Core 3.1, .NET 6, etc.
86 | validations:
87 | required: true
88 |
89 | - type: input
90 | id: operating-system
91 | attributes:
92 | label: Operating System and version
93 | description: "Example: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc."
94 | placeholder: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc.
95 | validations:
96 | required: true
97 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | netstandard2.0;net8.0
6 | Amazon.Extensions.Configuration.SystemsManager
7 | Amazon.Extensions.Configuration.SystemsManager
8 | Library
9 | true
10 | Amazon.Extensions.Configuration.SystemsManager
11 | .NET Configuration Extensions for AWS Systems Manager
12 | Amazon.Extensions.Configuration.SystemsManager
13 | .NET Configuration Extensions for AWS Systems Manager
14 | Amazon Web Services
15 | AWS;Amazon;aws-sdk-v4;SimpleSystemsManagement;configuration
16 | https://github.com/aws/aws-dotnet-extensions-configuration/
17 | LICENSE
18 | icon.png
19 | https://github.com/aws/aws-dotnet-extensions-configuration/
20 | Amazon Web Services
21 | true
22 | ../ruleset.xml
23 |
24 | true
25 | true
26 | true
27 | snupkg
28 |
29 | 7.0.1
30 |
31 |
32 |
33 | true
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | true
47 | ..\..\public.snk
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | all
62 |
63 |
64 | all
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using Amazon.Extensions.Configuration.SystemsManager.AppConfig;
18 | using Microsoft.Extensions.Configuration;
19 | using Moq;
20 | using Xunit;
21 |
22 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
23 | {
24 | public class AppConfigExtensionsTests
25 | {
26 | [Fact]
27 | public void AddAppConfigWithProperInputShouldReturnProperConfigurationBuilder()
28 | {
29 | var expectedBuilder = new ConfigurationBuilder();
30 | expectedBuilder.Sources.Add(new Mock().Object);
31 | const string applicationId = "appId";
32 | const string environmentId = "envId";
33 | const string configProfileId = "profId";
34 |
35 | var builder = new ConfigurationBuilder();
36 | builder.AddAppConfig(applicationId, environmentId, configProfileId);
37 |
38 | Assert.Contains(
39 | builder.Sources,
40 | source => source is AppConfigConfigurationSource configurationSource
41 | && configurationSource.ApplicationId == applicationId
42 | && configurationSource.EnvironmentId == environmentId
43 | && configurationSource.ConfigProfileId == configProfileId
44 | );
45 | }
46 |
47 | [Fact]
48 | public void AddAppConfigWithoutAwsOptionsShouldThrowException()
49 | {
50 | var builder = new ConfigurationBuilder();
51 | Func func = () => builder.AddAppConfig("appId", "envId", "profileId", awsOptions: null);
52 |
53 | var ex = Assert.Throws(func);
54 | Assert.Contains("awsOptions", ex.Message);
55 | }
56 |
57 | [Fact]
58 | public void AddAppConfigWithoutApplicationIdShouldThrowException()
59 | {
60 | var builder = new ConfigurationBuilder();
61 | Func func = () => builder.AddAppConfig(null, "envId", "profileId");
62 |
63 | var ex = Assert.Throws(func);
64 | Assert.Contains("applicationId", ex.Message);
65 | }
66 |
67 | [Fact]
68 | public void AddAppConfigWithoutEnvironmentIdShouldThrowException()
69 | {
70 | var builder = new ConfigurationBuilder();
71 | Func func = () => builder.AddAppConfig("appId", null, "profileId");
72 |
73 | var ex = Assert.Throws(func);
74 | Assert.Contains("environmentId", ex.Message);
75 | }
76 |
77 | [Fact]
78 | public void AddAppConfigWithoutProfileIdShouldThrowException()
79 | {
80 | var builder = new ConfigurationBuilder();
81 | Func func = () => builder.AddAppConfig("appId", "envId", null);
82 |
83 | var ex = Assert.Throws(func);
84 | Assert.Contains("configProfileId", ex.Message);
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigForLambdaExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using System.Reflection;
18 | using System.Linq;
19 | using Amazon.Extensions.Configuration.SystemsManager.AppConfig;
20 | using Microsoft.Extensions.Configuration;
21 | using Moq;
22 | using Xunit;
23 |
24 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
25 | {
26 | public class AppConfigForLambdaExtensionsTests
27 | {
28 | [Fact]
29 | public void AddAppConfigForLambdaWithProperInputShouldReturnProperConfigurationBuilder()
30 | {
31 | var expectedBuilder = new ConfigurationBuilder();
32 | expectedBuilder.Sources.Add(new Mock().Object);
33 | const string applicationId = "appId";
34 | const string environmentId = "envId";
35 | const string configProfileId = "profId";
36 |
37 | var builder = new ConfigurationBuilder();
38 | builder.AddAppConfigUsingLambdaExtension(applicationId, environmentId, configProfileId);
39 |
40 | var configurationSource = builder.Sources.FirstOrDefault(source => source is AppConfigConfigurationSource) as AppConfigConfigurationSource;
41 | Assert.NotNull(configurationSource);
42 | Assert.Equal(applicationId, configurationSource.ApplicationId);
43 | Assert.Equal(environmentId, configurationSource.EnvironmentId);
44 | Assert.Equal(configProfileId, configurationSource.ConfigProfileId);
45 |
46 | var property = typeof(AppConfigConfigurationSource).GetProperty("UseLambdaExtension", BindingFlags.Instance | BindingFlags.NonPublic);
47 | Assert.NotNull(property);
48 | Assert.True((bool)property.GetValue(configurationSource));
49 | }
50 |
51 | [Fact]
52 | public void AddAppConfigForLambdaWithoutApplicationIdShouldThrowException()
53 | {
54 | var builder = new ConfigurationBuilder();
55 | Func func = () => builder.AddAppConfigUsingLambdaExtension(null, "envId", "profileId");
56 |
57 | var ex = Assert.Throws(func);
58 | Assert.Contains("applicationId", ex.Message);
59 | }
60 |
61 | [Fact]
62 | public void AddAppConfigForLambdaWithoutEnvironmentIdShouldThrowException()
63 | {
64 | var builder = new ConfigurationBuilder();
65 | Func func = () => builder.AddAppConfigUsingLambdaExtension("appId", null, "profileId");
66 |
67 | var ex = Assert.Throws(func);
68 | Assert.Contains("environmentId", ex.Message);
69 | }
70 |
71 | [Fact]
72 | public void AddAppConfigForLambdaWithoutProfileIdShouldThrowException()
73 | {
74 | var builder = new ConfigurationBuilder();
75 | Func func = () => builder.AddAppConfigUsingLambdaExtension("appId", "envId", null);
76 |
77 | var ex = Assert.Throws(func);
78 | Assert.Contains("configProfileId", ex.Message);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/Utils/ParameterProcessorUtilTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text.Json;
3 | using Amazon.Extensions.Configuration.SystemsManager.Utils;
4 | using Xunit;
5 |
6 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests.Utils
7 | {
8 | public class ParameterProcessorUtilTests
9 | {
10 | [Fact]
11 | public void ParseJsonParameterSuccessfully()
12 | {
13 | var result = new Dictionary();
14 | var value = "{\"key\": \"value\"}";
15 | var keyPrefix = "prefix";
16 |
17 | ParameterProcessorUtil.ParseJsonParameter(keyPrefix, value, result);
18 |
19 | Assert.Single(result);
20 | Assert.Contains("prefix:key", result.Keys);
21 | Assert.Equal("value", result["prefix:key"]);
22 | }
23 |
24 | [Fact]
25 | public void ParseJsonParameterWithDuplicateKeyThrowsException()
26 | {
27 | var result = new Dictionary { { "prefix:key", "value" } };
28 | var value = "{\"key\": \"newvalue\"}";
29 | var keyPrefix = "prefix";
30 |
31 | Assert.Throws(() => ParameterProcessorUtil.ParseJsonParameter(keyPrefix, value, result));
32 | }
33 |
34 | [Fact]
35 | public void ParseJsonParameterForInvalidJsonThrowsException()
36 | {
37 | var result = new Dictionary();
38 | var value = "invalid json";
39 | var keyPrefix = "";
40 |
41 | Assert.ThrowsAny(() => ParameterProcessorUtil.ParseJsonParameter(keyPrefix, value, result));
42 | }
43 |
44 | [Fact]
45 | public void ParseStringListParameterSuccessfully()
46 | {
47 | var result = new Dictionary();
48 | var value = "value1,value2,value3";
49 | var keyPrefix = "prefix";
50 |
51 | ParameterProcessorUtil.ParseStringListParameter(keyPrefix, value, result);
52 |
53 | Assert.Equal(3, result.Count);
54 | Assert.Contains("prefix:0", result.Keys);
55 | Assert.Contains("prefix:1", result.Keys);
56 | Assert.Contains("prefix:2", result.Keys);
57 | Assert.Equal("value1", result["prefix:0"]);
58 | Assert.Equal("value2", result["prefix:1"]);
59 | Assert.Equal("value3", result["prefix:2"]);
60 | }
61 |
62 | [Fact]
63 | public void ParseStringListParameterWithDuplicateKeyThrowsException()
64 | {
65 | var result = new Dictionary { { "prefix:0", "value" } };
66 | var value = "value1,value2,value3";
67 | var keyPrefix = "prefix";
68 |
69 | Assert.Throws(() => ParameterProcessorUtil.ParseStringListParameter(keyPrefix, value, result));
70 | }
71 |
72 | [Fact]
73 | public void ParseStringParameterSuccessfully()
74 | {
75 | var result = new Dictionary();
76 | var value = "stringValue";
77 | var key = "myKey";
78 |
79 | ParameterProcessorUtil.ParseStringParameter(key, value, result);
80 |
81 | Assert.Single(result);
82 | Assert.Contains("myKey", result.Keys);
83 | Assert.Equal("stringValue", result["myKey"]);
84 | }
85 |
86 | [Fact]
87 | public void ParseStringParameterWithDuplicateKeyThrowsException()
88 | {
89 | var result = new Dictionary { { "myKey", "existingValue" } };
90 | var value = "newValue";
91 | var key = "myKey";
92 |
93 | Assert.Throws(() => ParameterProcessorUtil.ParseStringParameter(key, value, result));
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/Utils/ParameterProcessorUtil.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Amazon.Extensions.Configuration.SystemsManager.Internal;
4 | using Microsoft.Extensions.Configuration;
5 | using System.Text.Json;
6 |
7 | namespace Amazon.Extensions.Configuration.SystemsManager.Utils
8 | {
9 | public static class ParameterProcessorUtil
10 | {
11 | ///
12 | /// Parses the SSM parameter as JSON
13 | ///
14 | /// prefix to add in configution key
15 | /// SSM parameter value
16 | /// append the parsed JSON value into
17 | /// SSM parameter key is already present in
18 | /// does not represent a valid single JSON value.
19 | public static void ParseJsonParameter(string keyPrefix, string value, IDictionary result)
20 | {
21 | foreach (var kv in JsonConfigurationParser.Parse(value))
22 | {
23 | var key = !string.IsNullOrEmpty(keyPrefix) ? ConfigurationPath.Combine(keyPrefix, kv.Key) : kv.Key;
24 | if (result.ContainsKey(key))
25 | {
26 | throw new DuplicateParameterException($"Duplicate parameter '{key}' found. Parameter keys are case-insensitive.");
27 | }
28 |
29 | result.Add(key, kv.Value);
30 | }
31 | }
32 |
33 | ///
34 | /// Parses the StringList SSM parameter as List of String
35 | ///
36 | /// Items in a StringList must be separated by a comma (,).
37 | /// You can't use other punctuation or special characters to escape items in the list.
38 | /// If you have a parameter value that requires a comma, then use the String type.
39 | /// https://docs.aws.amazon.com/systems-manager/latest/userguide/param-create-cli.html#param-create-cli-stringlist
40 | ///
41 | /// prefix to add in configution key
42 | /// SSM parameter
43 | /// append the parsed string list value into
44 | /// SSM parameter key is already present in
45 | public static void ParseStringListParameter(string keyPrefix, string value, IDictionary result)
46 | {
47 | var configKeyValuePairs = value
48 | .Split(',')
49 | .Select((eachValue, idx) => new KeyValuePair($"{keyPrefix}{ConfigurationPath.KeyDelimiter}{idx}", eachValue));
50 |
51 | foreach (var kv in configKeyValuePairs)
52 | {
53 | if (result.ContainsKey(kv.Key))
54 | {
55 | throw new DuplicateParameterException($"Duplicate parameter '{kv.Key}' found. Parameter keys are case-insensitive.");
56 | }
57 |
58 | result.Add(kv.Key, kv.Value);
59 | }
60 | }
61 |
62 | ///
63 | /// Parses the SSM parameter as String
64 | ///
65 | /// key to be used for configution key
66 | /// SSM parameter
67 | /// append the parsed string value into
68 | /// SSM parameter key is already present in
69 | public static void ParseStringParameter(string key, string value, IDictionary result)
70 | {
71 | if (result.ContainsKey(key))
72 | {
73 | throw new DuplicateParameterException($"Duplicate parameter '{key}' found. Parameter keys are case-insensitive.");
74 | }
75 |
76 | result.Add(key, value);
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/Internal/JsonConfigurationParser.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.Globalization;
19 | using System.IO;
20 | using System.Linq;
21 | using System.Text;
22 | using System.Text.Json;
23 | using Microsoft.Extensions.Configuration;
24 |
25 | namespace Amazon.Extensions.Configuration.SystemsManager.Internal
26 | {
27 | public class JsonConfigurationParser
28 | {
29 | private JsonConfigurationParser() { }
30 |
31 | private readonly IDictionary _data = new SortedDictionary(StringComparer.OrdinalIgnoreCase);
32 | private readonly Stack _context = new Stack();
33 | private string _currentPath;
34 |
35 | public static IDictionary Parse(Stream input)
36 | {
37 | using (var doc = JsonDocument.Parse(input))
38 | {
39 | var parser = new JsonConfigurationParser();
40 | parser.VisitElement(doc.RootElement);
41 | return parser._data;
42 | }
43 | }
44 |
45 | public static IDictionary Parse(string input)
46 | {
47 | using (var doc = JsonDocument.Parse(input))
48 | {
49 | var parser = new JsonConfigurationParser();
50 | parser.VisitElement(doc.RootElement);
51 | return parser._data;
52 | }
53 | }
54 |
55 | private void VisitElement(JsonElement element)
56 | {
57 | switch (element.ValueKind)
58 | {
59 | case JsonValueKind.Undefined:
60 | break;
61 | case JsonValueKind.Object:
62 | foreach (var property in element.EnumerateObject())
63 | {
64 | EnterContext(property.Name);
65 | VisitElement(property.Value);
66 | ExitContext();
67 | }
68 | break;
69 | case JsonValueKind.Array:
70 | VisitArray(element);
71 | break;
72 | case JsonValueKind.String:
73 | case JsonValueKind.Number:
74 | case JsonValueKind.True:
75 | case JsonValueKind.False:
76 | VisitPrimitive(element);
77 | break;
78 | case JsonValueKind.Null:
79 | VisitNull();
80 | break;
81 | }
82 |
83 | }
84 |
85 | private void VisitArray(JsonElement array)
86 | {
87 | int index = 0;
88 | foreach (var item in array.EnumerateArray())
89 | {
90 | EnterContext(index.ToString(CultureInfo.InvariantCulture));
91 | VisitElement(item);
92 | ExitContext();
93 |
94 | index++;
95 | }
96 | }
97 |
98 | private void VisitNull()
99 | {
100 | var key = _currentPath;
101 | _data[key] = null;
102 | }
103 |
104 | private void VisitPrimitive(JsonElement data)
105 | {
106 | var key = _currentPath;
107 |
108 | if (_data.ContainsKey(key))
109 | {
110 | throw new FormatException($"A duplicate key '{key}' was found.");
111 | }
112 |
113 | _data[key] = data.ToString();
114 | }
115 |
116 | private void EnterContext(string context)
117 | {
118 | _context.Push(context);
119 | _currentPath = ConfigurationPath.Combine(_context.Reverse());
120 | }
121 |
122 | private void ExitContext()
123 | {
124 | _context.Pop();
125 | _currentPath = ConfigurationPath.Combine(_context.Reverse());
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/Internal/SystemsManagerProcessor.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using Amazon.SimpleSystemsManagement;
17 | using Amazon.SimpleSystemsManagement.Model;
18 | using System;
19 | using System.Collections.Generic;
20 | using System.Linq;
21 | using System.Threading.Tasks;
22 | using Microsoft.Extensions.Configuration;
23 |
24 | namespace Amazon.Extensions.Configuration.SystemsManager.Internal
25 | {
26 | public class SystemsManagerProcessor : ISystemsManagerProcessor
27 | {
28 | private const string SecretsManagerPath = "/aws/reference/secretsmanager/";
29 |
30 | private SystemsManagerConfigurationSource Source { get; }
31 |
32 | public SystemsManagerProcessor(SystemsManagerConfigurationSource source)
33 | {
34 | if (source.AwsOptions == null) throw new ArgumentNullException(nameof(source.AwsOptions));
35 | if (source.Path == null) throw new ArgumentNullException(nameof(source.Path));
36 |
37 | Source = source;
38 | Source.ParameterProcessor = Source.ParameterProcessor ??
39 | (IsSecretsManagerPath(Source.Path)
40 | ? new JsonParameterProcessor()
41 | : new DefaultParameterProcessor());
42 | }
43 |
44 | public async Task> GetDataAsync()
45 | {
46 | return IsSecretsManagerPath(Source.Path)
47 | ? await GetParameterAsync().ConfigureAwait(false)
48 | : await GetParametersByPathAsync().ConfigureAwait(false);
49 | }
50 |
51 | private async Task> GetParametersByPathAsync()
52 | {
53 | using (var client = Source.AwsOptions.CreateServiceClient())
54 | {
55 | if (client is AmazonSimpleSystemsManagementClient impl)
56 | {
57 | impl.BeforeRequestEvent += ServiceClientAppender.ServiceClientBeforeRequestEvent;
58 | }
59 |
60 | var parameters = new List();
61 | string nextToken = null;
62 | do
63 | {
64 | var response = await client.GetParametersByPathAsync(new GetParametersByPathRequest { Path = Source.Path, Recursive = true, WithDecryption = true, NextToken = nextToken, ParameterFilters = Source.Filters }).ConfigureAwait(false);
65 | nextToken = response.NextToken;
66 | parameters.AddRange(response.Parameters ?? new List());
67 | } while (!string.IsNullOrEmpty(nextToken));
68 |
69 | return AddPrefix(Source.ParameterProcessor.ProcessParameters(parameters, Source.Path), Source.Prefix);
70 | }
71 | }
72 |
73 | private async Task> GetParameterAsync()
74 | {
75 | using (var client = Source.AwsOptions.CreateServiceClient())
76 | {
77 | if (client is AmazonSimpleSystemsManagementClient impl)
78 | {
79 | impl.BeforeRequestEvent += ServiceClientAppender.ServiceClientBeforeRequestEvent;
80 | }
81 |
82 | var response = await client.GetParameterAsync(new GetParameterRequest { Name = Source.Path, WithDecryption = true }).ConfigureAwait(false);
83 |
84 | var prefix = Source.Prefix;
85 | return AddPrefix(Source.ParameterProcessor.ProcessParameters(new []{response.Parameter}, Source.Path), prefix);
86 | }
87 | }
88 |
89 | public static bool IsSecretsManagerPath(string path) => path.StartsWith(SecretsManagerPath, StringComparison.OrdinalIgnoreCase);
90 |
91 | public static IDictionary AddPrefix(IDictionary input, string prefix)
92 | {
93 | return string.IsNullOrEmpty(prefix)
94 | ? input
95 | : input.ToDictionary(pair => $"{prefix}{ConfigurationPath.KeyDelimiter}{pair.Key}", pair => pair.Value, StringComparer.OrdinalIgnoreCase);
96 |
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/SystemsManagerConfigurationProviderTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System.Collections.Generic;
17 | using Amazon.Extensions.Configuration.SystemsManager.AppConfig;
18 | using Amazon.Extensions.Configuration.SystemsManager.Internal;
19 | using Amazon.Extensions.NETCore.Setup;
20 | using Amazon.SimpleSystemsManagement.Model;
21 | using Moq;
22 | using Xunit;
23 |
24 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
25 | {
26 | public class SystemsManagerConfigurationProviderTests
27 | {
28 | private readonly Mock _systemsManagerProcessorMock = new Mock();
29 |
30 | [Fact]
31 | public void LoadForParameterStoreShouldReturnProperParameters()
32 | {
33 | var parameters = new List
34 | {
35 | new Parameter {Name = "/start/path/p1/p2-1", Value = "p1:p2-1"},
36 | new Parameter {Name = "/start/path/p1/p2-2", Value = "p1:p2-2"},
37 | new Parameter {Name = "/start/path/p1/p2/p3-1", Value = "p1:p2:p3-1"},
38 | new Parameter {Name = "/start/path/p1/p2/p3-2", Value = "p1:p2:p3-2"}
39 | };
40 | var parameterProcessorMock = new Mock();
41 | var provider = ConfigureParameterStoreConfigurationProvider(parameterProcessorMock, parameters);
42 |
43 | provider.Load();
44 |
45 | foreach (var parameter in parameters)
46 | {
47 | Assert.True(provider.TryGet(parameter.Value, out _));
48 | }
49 |
50 | parameterProcessorMock.VerifyAll();
51 | }
52 |
53 | [Fact]
54 | public void LoadForAppConfigShouldReturnProperValues()
55 | {
56 | var values = new Dictionary
57 | {
58 | { "testKey", "testValue" },
59 | { "testKey2", "testValue2" },
60 | { "testKey3", "testValue3" },
61 | { "testKey4", "testValue4" },
62 | { "testKey5", "testValue5" }
63 | };
64 | var provider = ConfigureAppConfigConfigurationProvider(values);
65 |
66 | provider.Load();
67 |
68 | foreach (var parameter in values)
69 | {
70 | Assert.True(provider.TryGet(parameter.Key, out _));
71 | }
72 | }
73 |
74 | private SystemsManagerConfigurationProvider ConfigureParameterStoreConfigurationProvider(Mock parameterProcessorMock, IReadOnlyCollection parameters)
75 | {
76 | const string path = "/start/path";
77 | var source = new SystemsManagerConfigurationSource
78 | {
79 | ParameterProcessor = parameterProcessorMock.Object,
80 | AwsOptions = new AWSOptions(),
81 | Path = path
82 | };
83 | var provider = new SystemsManagerConfigurationProvider(source, _systemsManagerProcessorMock.Object);
84 |
85 | var getData = new DefaultParameterProcessor().ProcessParameters(parameters, path);
86 |
87 | _systemsManagerProcessorMock.Setup(p => p.GetDataAsync()).ReturnsAsync(() => getData);
88 |
89 | return provider;
90 | }
91 |
92 | private SystemsManagerConfigurationProvider ConfigureAppConfigConfigurationProvider(IDictionary values)
93 | {
94 | var source = new AppConfigConfigurationSource
95 | {
96 | ApplicationId = "appId",
97 | EnvironmentId = "envId",
98 | ConfigProfileId = "profileId",
99 | AwsOptions = new AWSOptions()
100 | };
101 |
102 | _systemsManagerProcessorMock.Setup(p => p.GetDataAsync()).ReturnsAsync(() => values);
103 |
104 | var provider = new SystemsManagerConfigurationProvider(source, _systemsManagerProcessorMock.Object);
105 |
106 | return provider;
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/.github/workflows/create-release-pr.yml:
--------------------------------------------------------------------------------
1 | # This GitHub Workflow will create a new release branch that contains the updated C# project versions and changelog.
2 | # The workflow will also create a PR that targets `dev` from the release branch.
3 | name: Create Release PR
4 |
5 | # This workflow is manually triggered when in preparation for a release. The workflow should be dispatched from the `dev` branch.
6 | on:
7 | workflow_dispatch:
8 | inputs:
9 | OVERRIDE_VERSION:
10 | description: "Override Version"
11 | type: string
12 | required: false
13 |
14 | permissions:
15 | id-token: write
16 |
17 | jobs:
18 | release-pr:
19 | name: Release PR
20 | runs-on: ubuntu-latest
21 |
22 | env:
23 | INPUT_OVERRIDE_VERSION: ${{ github.event.inputs.OVERRIDE_VERSION }}
24 |
25 | steps:
26 | # Assume an AWS Role that provides access to the Access Token
27 | - name: Configure AWS Credentials
28 | uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v4
29 | with:
30 | role-to-assume: ${{ secrets.RELEASE_WORKFLOW_ACCESS_TOKEN_ROLE_ARN }}
31 | aws-region: us-west-2
32 | # Retrieve the Access Token from Secrets Manager
33 | - name: Retrieve secret from AWS Secrets Manager
34 | uses: aws-actions/aws-secretsmanager-get-secrets@a9a7eb4e2f2871d30dc5b892576fde60a2ecc802 #v2.0.10
35 | with:
36 | secret-ids: |
37 | AWS_SECRET, ${{ secrets.RELEASE_WORKFLOW_ACCESS_TOKEN_NAME }}
38 | parse-json-secrets: true
39 | # Checkout a full clone of the repo
40 | - name: Checkout
41 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
42 | with:
43 | fetch-depth: '0'
44 | token: ${{ env.AWS_SECRET_TOKEN }}
45 | # Install .NET8 which is needed for AutoVer
46 | - name: Setup .NET 8.0
47 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 #v4.3.1
48 | with:
49 | dotnet-version: 8.0.x
50 | # Install AutoVer to automate versioning and changelog creation
51 | - name: Install AutoVer
52 | run: dotnet tool install --global AutoVer --version 0.0.25
53 | # Set up a git user to be able to run git commands later on
54 | - name: Setup Git User
55 | run: |
56 | git config --global user.email "github-aws-sdk-dotnet-automation@amazon.com"
57 | git config --global user.name "aws-sdk-dotnet-automation"
58 | # Create the release branch which will contain the version changes and updated changelog
59 | - name: Create Release Branch
60 | id: create-release-branch
61 | run: |
62 | branch=releases/next-release
63 | git checkout -b $branch
64 | echo "BRANCH=$branch" >> $GITHUB_OUTPUT
65 | # Update the version of projects based on the change files
66 | - name: Increment Version
67 | run: autover version
68 | if: env.INPUT_OVERRIDE_VERSION == ''
69 | # Update the version of projects based on the override version
70 | - name: Increment Version
71 | run: autover version --use-version "$INPUT_OVERRIDE_VERSION"
72 | if: env.INPUT_OVERRIDE_VERSION != ''
73 | # Update the changelog based on the change files
74 | - name: Update Changelog
75 | run: autover changelog
76 | # Push the release branch up as well as the created tag
77 | - name: Push Changes
78 | run: |
79 | branch=${{ steps.create-release-branch.outputs.BRANCH }}
80 | git push origin $branch
81 | git push origin $branch --tags
82 | # Get the release name that will be used to create a PR
83 | - name: Read Release Name
84 | id: read-release-name
85 | run: |
86 | version=$(autover changelog --release-name)
87 | echo "VERSION=$version" >> $GITHUB_OUTPUT
88 | # Get the changelog that will be used to create a PR
89 | - name: Read Changelog
90 | id: read-changelog
91 | run: |
92 | changelog=$(autover changelog --output-to-console)
93 | echo "CHANGELOG<> "$GITHUB_OUTPUT"
94 | # Create the Release PR and label it
95 | - name: Create Pull Request
96 | env:
97 | GITHUB_TOKEN: ${{ env.AWS_SECRET_TOKEN }}
98 | run: |
99 | pr_url="$(gh pr create --title "${{ steps.read-release-name.outputs.VERSION }}" --body "${{ steps.read-changelog.outputs.CHANGELOG }}" --base dev --head ${{ steps.create-release-branch.outputs.BRANCH }})"
100 | gh label create "Release PR" --description "A Release PR that includes versioning and changelog changes" -c "#FF0000" -f
101 | gh pr edit $pr_url --add-label "Release PR"
102 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/DefaultParameterProcessorTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Amazon.SimpleSystemsManagement;
3 | using Amazon.SimpleSystemsManagement.Model;
4 | using Xunit;
5 |
6 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
7 | {
8 | public class DefaultParameterProcessorTests
9 | {
10 | private readonly IParameterProcessor _parameterProcessor = new DefaultParameterProcessor();
11 |
12 | [Fact]
13 | public void ExtractConfigurationKeyFromParameter()
14 | {
15 | var processor = new DefaultParameterProcessor();
16 |
17 | Assert.Equal("level1:level2", processor.GetKey(new Parameter() { Name = "/level1/level2" }, ""));
18 | Assert.Equal("level1:level2", processor.GetKey(new Parameter() { Name = "/level1/level2" }, "/"));
19 | Assert.Equal("level1:level2", processor.GetKey(new Parameter() { Name = "/level1/level2" }, "/someotherlevel"));
20 |
21 | Assert.Equal("level2:level3", processor.GetKey(new Parameter() { Name = "/level1/level2/level3" }, "/level1"));
22 | Assert.Equal("level2", processor.GetKey(new Parameter() { Name = "/level1/level2" }, "/LEVEL1"));
23 | }
24 |
25 | [Fact]
26 | public void ExtractConfigurationValueFromParameter()
27 | {
28 | var processor = new DefaultParameterProcessor();
29 |
30 | Assert.Equal("Some value", processor.GetValue(new Parameter() { Value = "Some value" }, null));
31 | }
32 |
33 | [Fact]
34 | public void ProcessParametersTest()
35 | {
36 | var parameters = new List
37 | {
38 | new Parameter { Name = "/start/path/p1/p2-1", Value = "p1:p2-1" },
39 | new Parameter { Name = "/start/path/p1/p2-2", Value = "p1:p2-2" },
40 | new Parameter { Name = "/start/path/p1/p2/p3-1", Value = "p1:p2:p3-1" },
41 | new Parameter { Name = "/start/path/p1/p2/p3-2", Value = "p1:p2:p3-2" },
42 | };
43 |
44 | const string path = "/start/path";
45 |
46 | var data = _parameterProcessor.ProcessParameters(parameters, path);
47 |
48 | Assert.All(data, item => Assert.Equal(item.Value, item.Key));
49 | }
50 |
51 | [Fact]
52 | public void ProcessParametersStringListTest()
53 | {
54 | var parameters = new List
55 | {
56 | new Parameter { Name = "/string-list/single", Value = "p1", Type = ParameterType.StringList },
57 | new Parameter { Name = "/string-list/multiple", Value = "p1,p2,p3", Type = ParameterType.StringList },
58 | new Parameter { Name = "/string-list/empty", Value = "", Type = ParameterType.StringList },
59 | };
60 |
61 | const string path = "/string-list";
62 |
63 | var data = _parameterProcessor.ProcessParameters(parameters, path);
64 |
65 | Assert.Equal(5, data.Keys.Count);
66 | Assert.Equal("p1", data["single:0"]);
67 | Assert.Equal("p1", data["multiple:0"]);
68 | Assert.Equal("p2", data["multiple:1"]);
69 | Assert.Equal("p3", data["multiple:2"]);
70 | Assert.Equal("", data["empty:0"]);
71 | }
72 |
73 |
74 | [Fact]
75 | public void ProcessParametersRootTest()
76 | {
77 | var parameters = new List
78 | {
79 | new Parameter { Name = "/p1", Value = "p1" },
80 | new Parameter { Name = "p2", Value = "p2" },
81 | };
82 |
83 | const string path = "/";
84 |
85 | var data = _parameterProcessor.ProcessParameters(parameters, path);
86 |
87 | Assert.All(data, item => Assert.Equal(item.Value, item.Key));
88 | }
89 |
90 | [Fact]
91 | public void DuplicateSimpleParametersTest()
92 | {
93 | var parameters = new List
94 | {
95 | new Parameter { Name = "/start/path/p1", Value = "p1:1" },
96 | new Parameter { Name = "/start/path/P1", Value = "p1:2" }
97 | };
98 |
99 | const string path = "/start/path";
100 | Assert.Throws(() => _parameterProcessor.ProcessParameters(parameters, path));
101 | }
102 |
103 | [Fact]
104 | public void DuplicateStringListParametersTest()
105 | {
106 | var parameters = new List
107 | {
108 | new Parameter { Name = "/string-list/multiple", Value = "p1,p2,p3", Type = ParameterType.StringList },
109 | new Parameter { Name = "/string-list/MULTIPLE", Value = "p3,p5,p6", Type = ParameterType.StringList }
110 | };
111 |
112 | const string path = "/string-list";
113 | Assert.Throws(() => _parameterProcessor.ProcessParameters(parameters, path));
114 | }
115 | }
116 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check [existing open](https://github.com/aws/aws-dotnet-extensions-configuration/issues), or [recently closed](https://github.com/aws/aws-dotnet-extensions-configuration/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *master* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 | ## Adding a `change file` to your contribution branch
43 |
44 | Each contribution branch should include a `change file` that contains a changelog message for each project that has been updated, as well as the type of increment to perform for those changes when versioning the project.
45 |
46 | A `change file` looks like the following example:
47 | ```json
48 | {
49 | "Projects": [
50 | {
51 | "Name": "Amazon.Extensions.Configuration.SystemsManager",
52 | "Type": "Patch",
53 | "ChangelogMessages": [
54 | "Fixed an issue causing a failure somewhere"
55 | ]
56 | }
57 | ]
58 | }
59 | ```
60 | The `change file` lists all the modified projects, the changelog message for each project as well as the increment type.
61 |
62 | These files are located in the repo at .autover/changes/
63 |
64 | You can use the `AutoVer` tool to create the change file. You can install it using the following command:
65 | ```
66 | dotnet tool install -g AutoVer
67 | ```
68 |
69 | You can create the `change file` using the following command:
70 | ```
71 | autover change --project-name "Amazon.Extensions.Configuration.SystemsManager" -m "Fixed an issue causing a failure somewhere
72 | ```
73 | Note: Make sure to run the command from the root of the repository.
74 |
75 | You can update the command to specify which project you are updating.
76 | The available projects are:
77 | * Amazon.Extensions.Configuration.SystemsManager
78 |
79 | The possible increment types are:
80 | * Patch
81 | * Minor
82 | * Major
83 |
84 | Note: You do not need to create a new `change file` for every changelog message or project within your branch. You can create one `change file` that contains all the modified projects and the changelog messages.
85 |
86 | ## Finding contributions to work on
87 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/aws-dotnet-extensions-configuration/labels/help%20wanted) issues is a great place to start.
88 |
89 |
90 | ## Code of Conduct
91 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
92 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
93 | opensource-codeofconduct@amazon.com with any additional questions or comments.
94 |
95 |
96 | ## Security issue notifications
97 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
98 |
99 |
100 | ## Licensing
101 |
102 | See the [LICENSE](https://github.com/aws/aws-dotnet-extensions-configuration/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
103 |
104 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.
105 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/SystemsManagerExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | using System;
16 | using Amazon.Extensions.NETCore.Setup;
17 | using Microsoft.Extensions.Configuration;
18 | using Xunit;
19 |
20 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
21 | {
22 | public class SystemsManagerExtensionsTests
23 | {
24 | [Theory]
25 | [MemberData(nameof(SourceExtensionData))]
26 | [MemberData(nameof(WithAWSOptionsExtensionData))]
27 | [MemberData(nameof(NoAWSOptionsExtensionData))]
28 | public void AddSystemsManagerInlineTest(Func configurationBuilder, Type exceptionType, string exceptionMessage)
29 | {
30 | var builder = new ConfigurationBuilder();
31 |
32 | IConfigurationBuilder ExecuteBuilder() => configurationBuilder(builder);
33 |
34 | if (exceptionType != null)
35 | {
36 | var ex = Assert.Throws(exceptionType, ExecuteBuilder);
37 | Assert.Contains(exceptionMessage, ex.Message, StringComparison.Ordinal);
38 | }
39 | else
40 | {
41 | var result = ExecuteBuilder();
42 | Assert.Equal(builder, result);
43 | }
44 | }
45 |
46 | public static TheoryData, Type, string> SourceExtensionData => new TheoryData, Type, string>
47 | {
48 | {builder => builder.AddSystemsManager(CreateSource(null, null, false, null, null)), typeof(ArgumentNullException), "Path"},
49 | {builder => builder.AddSystemsManager(CreateSource(null, null, true, null, null)), typeof(ArgumentNullException), "Path"},
50 | {builder => builder.AddSystemsManager(CreateSource("/path", null, false, null, null)), null, null},
51 | {builder => builder.AddSystemsManager(CreateSource("/aws/reference/secretsmanager/somevalue", null, false, null, null)), null, null}
52 | };
53 |
54 | public static TheoryData, Type, string> WithAWSOptionsExtensionData => new TheoryData, Type, string>
55 | {
56 | {builder => builder.AddSystemsManager(null, null), typeof(ArgumentNullException), "path"},
57 | {builder => builder.AddSystemsManager("/path", null), typeof(ArgumentNullException), "awsOptions"},
58 | {builder => builder.AddSystemsManager(null, new AWSOptions()), typeof(ArgumentNullException), "path"},
59 | {builder => builder.AddSystemsManager("/aws/reference/secretsmanager/somevalue", new AWSOptions()), null, null},
60 | {builder => builder.AddSystemsManager("/path", new AWSOptions(), true), null, null},
61 | {builder => builder.AddSystemsManager("/path", new AWSOptions(), false), null, null},
62 | {builder => builder.AddSystemsManager("/path", new AWSOptions(), TimeSpan.Zero), null, null},
63 | {builder => builder.AddSystemsManager("/path", new AWSOptions(), TimeSpan.Zero), null, null},
64 | {builder => builder.AddSystemsManager("/path", new AWSOptions(), true, TimeSpan.Zero), null, null},
65 | {builder => builder.AddSystemsManager("/path", new AWSOptions(), false, TimeSpan.Zero), null, null}
66 | };
67 |
68 | public static TheoryData, Type, string> NoAWSOptionsExtensionData => new TheoryData, Type, string>
69 | {
70 | {builder => builder.AddSystemsManager(null as string), typeof(ArgumentNullException), "path"},
71 | {builder => builder.AddSystemsManager("/path"), null, null},
72 | {builder => builder.AddSystemsManager("/aws/reference/secretsmanager/somevalue"), null, null},
73 | {builder => builder.AddSystemsManager("/path", true), null, null},
74 | {builder => builder.AddSystemsManager("/path", false), null, null},
75 | {builder => builder.AddSystemsManager("/path", TimeSpan.Zero), null, null},
76 | {builder => builder.AddSystemsManager("/path", TimeSpan.Zero), null, null},
77 | {builder => builder.AddSystemsManager("/path", true, TimeSpan.Zero), null, null},
78 | {builder => builder.AddSystemsManager("/path", false, TimeSpan.Zero), null, null}
79 | };
80 |
81 | private static Action CreateSource(string path, AWSOptions awsOptions, bool optional, TimeSpan? reloadAfter, Action onLoadException)
82 | {
83 | return source =>
84 | {
85 | source.Path = path;
86 | source.AwsOptions = awsOptions;
87 | source.Optional = optional;
88 | source.ReloadAfter = reloadAfter;
89 | source.OnLoadException = onLoadException;
90 | };
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Integ/ConfigurtaionBuilderIntegrationTestFixture.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using Amazon.Extensions.NETCore.Setup;
17 | using Amazon.SimpleSystemsManagement;
18 | using Amazon.SimpleSystemsManagement.Model;
19 | using System;
20 | using System.Collections.Generic;
21 | using System.Linq;
22 | using System.Threading;
23 | using System.Threading.Tasks;
24 |
25 | namespace Amazon.Extensions.Configuration.SystemsManager.Integ
26 | {
27 | public class IntegTestFixture : IDisposable
28 | {
29 | public const string ParameterPrefix = @"/configuration-extension-testdata/ssm/";
30 |
31 | public AWSOptions AWSOptions { get; private set; }
32 |
33 | private bool disposed = false;
34 |
35 | public IDictionary TestData { get; } = new Dictionary
36 | {
37 | {"hello", "world"},
38 | {"hello2", "world2"},
39 | };
40 |
41 | public IntegTestFixture()
42 | {
43 | AWSOptions = new AWSOptions();
44 | AWSOptions.Region = Amazon.RegionEndpoint.USWest2;
45 |
46 | seedTestData();
47 | }
48 |
49 | public void Dispose()
50 | {
51 | Dispose(true);
52 | GC.SuppressFinalize(this);
53 | }
54 |
55 | protected virtual void Dispose(bool disposing)
56 | {
57 | if (disposed) return;
58 | if (disposing)
59 | {
60 | cleanupTestData();
61 | }
62 | disposed = true;
63 | }
64 |
65 | private void seedTestData()
66 | {
67 | bool success = false;
68 | using (var client = AWSOptions.CreateServiceClient())
69 | {
70 | var tasks = new List();
71 | foreach (var kv in TestData)
72 | {
73 | Console.WriteLine($"Adding parameter: ({ParameterPrefix + kv.Key}, {kv.Value})");
74 | tasks.Add(client.PutParameterAsync(new PutParameterRequest
75 | {
76 | Name = ParameterPrefix + kv.Key,
77 | Value = kv.Value,
78 | Type = ParameterType.String
79 | }));
80 | };
81 | Task.WaitAll(tasks.ToArray());
82 |
83 | // due to eventual consistency, wait for 5 sec increments for 3 times to verify
84 | // test data is correctly set before executing tests.
85 | const int tries = 3;
86 | for (int i = 0; i < tries; i++)
87 | {
88 | int count = 0;
89 | GetParametersByPathResponse response;
90 | do
91 | {
92 | response = client.GetParametersByPathAsync(new GetParametersByPathRequest
93 | {
94 | Path = ParameterPrefix
95 | }).Result;
96 |
97 | count += response.Parameters.Count;
98 | } while (!string.IsNullOrEmpty(response.NextToken));
99 |
100 | success = (count == TestData.Count);
101 |
102 | if (success)
103 | {
104 | Console.WriteLine("Verified that test data is available.");
105 | break;
106 | }
107 | else
108 | {
109 | Console.WriteLine($"Waiting on test data to be available. Waiting {count + 1}/{tries}");
110 | Thread.Sleep(5 * 1000);
111 | }
112 | }
113 | }
114 |
115 | if (!success) throw new Exception("Failed to seed integration test data");
116 | }
117 |
118 | private void cleanupTestData()
119 | {
120 | Console.Write($"Delete all test parameters with prefix '{ParameterPrefix}'... ");
121 | using (var client = AWSOptions.CreateServiceClient())
122 | {
123 | GetParametersByPathResponse response;
124 | do
125 | {
126 | response = client.GetParametersByPathAsync(new GetParametersByPathRequest
127 | {
128 | Path = ParameterPrefix
129 | }).Result;
130 |
131 | client.DeleteParametersAsync(new DeleteParametersRequest
132 | {
133 | Names = response.Parameters.Select(p => p.Name).ToList()
134 | }).Wait();
135 | } while (!string.IsNullOrEmpty(response.NextToken));
136 |
137 | // no need to wait for eventual consistency here given we are not running tests back-to-back
138 | }
139 | Console.WriteLine("Done");
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/JsonOrStringParameterProcessorTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Amazon.SimpleSystemsManagement;
3 | using Amazon.SimpleSystemsManagement.Model;
4 | using Xunit;
5 |
6 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
7 | {
8 | public class JsonOrStringParameterProcessorTests
9 | {
10 | private readonly IParameterProcessor _parameterProcessor = new JsonOrStringParameterProcessor();
11 |
12 | [Fact]
13 | public void ParsesJsonParametersSuccessfully()
14 | {
15 | var parameters = new List
16 | {
17 | new Parameter() { Name = "/test/level1/level2", Type = ParameterType.String, Value = "{\"level1Key\":\"level1value\"}" },
18 | new Parameter() { Name = "/test/level1", Type = ParameterType.String, Value = "{\"level1Key\" : {\"level2key\" : \"level2value\"}}" }
19 | };
20 | var result = _parameterProcessor.ProcessParameters(parameters, "/test");
21 |
22 | Assert.Equal(2, result.Count);
23 | Assert.True(result.ContainsKey("level1:level2:level1Key"));
24 | Assert.True(result.ContainsKey("level1:level1Key:level2key"));
25 | Assert.Equal("level1value", result["level1:level2:level1Key"]);
26 | Assert.Equal("level2value", result["level1:level1Key:level2key"]);
27 | }
28 |
29 | [Fact]
30 | public void ProcessParametersParsesJsonParametersWithoutPrefixSuccessfully()
31 | {
32 | var parameters = new List
33 | {
34 | new Parameter()
35 | {
36 | Name = "/test/level1/level2", Type = ParameterType.String, Value = "{\"level1Key\":\"level1value\"}"
37 | },
38 | new Parameter()
39 | {
40 | Name = "/test/level1", Type = ParameterType.String,
41 | Value = "{\"level1Key\" : {\"level2key\" : \"level2value\"}}"
42 | }
43 | };
44 | var result = _parameterProcessor.ProcessParameters(parameters, "");
45 |
46 | Assert.Equal(2, result.Count);
47 | Assert.True(result.ContainsKey("test:level1:level2:level1Key"));
48 | Assert.True(result.ContainsKey("test:level1:level1Key:level2key"));
49 | Assert.Equal("level1value", result["test:level1:level2:level1Key"]);
50 | Assert.Equal("level2value", result["test:level1:level1Key:level2key"]);
51 | }
52 |
53 |
54 | [Fact]
55 | public void ProcessParametersFallBackOnString()
56 | {
57 | var parameters = new List
58 | {
59 | new Parameter() { Name = "/test/stringParam", Type = ParameterType.String, Value = "some string" }
60 | };
61 | var result = _parameterProcessor.ProcessParameters(parameters, "/test");
62 |
63 | Assert.Single(result);
64 | Assert.True(result.ContainsKey("stringParam"));
65 | Assert.Equal("some string", result["stringParam"]);
66 | }
67 |
68 | [Fact]
69 | public void ProcessParametersThrowsOnDuplicateParameter()
70 | {
71 | var parameters = new List
72 | {
73 | new Parameter() { Name = "/test/Duplicate", Type = ParameterType.String, Value = "value1" },
74 | new Parameter() { Name = "/test/duplicate", Type = ParameterType.String, Value = "value2" }
75 | };
76 | var duplicateParameterException = Assert.Throws(() => _parameterProcessor.ProcessParameters(parameters, "/test"));
77 | Assert.Equal("Duplicate parameter 'duplicate' found. Parameter keys are case-insensitive.", duplicateParameterException.Message);
78 | }
79 |
80 | [Fact]
81 | public void ProcessParametersThrowsOnDuplicateParameterAtMultiLevel()
82 | {
83 | var parameters = new List
84 | {
85 | new Parameter() { Name = "/test/level1", Type = ParameterType.String, Value = "{\"level1Key\":\"level1value\"}" },
86 | new Parameter() { Name = "/test/level1/level1key", Type = ParameterType.String, Value = "level1valueOverriden" },
87 | };
88 |
89 | var duplicateParameterException = Assert.Throws(() => _parameterProcessor.ProcessParameters(parameters, "/test"));
90 | Assert.Equal("Duplicate parameter 'level1:level1key' found. Parameter keys are case-insensitive.", duplicateParameterException.Message);
91 | }
92 |
93 | [Fact]
94 | public void ProcessParametersThrowsOnDuplicateParameterAtMultilevelForJsonArray()
95 | {
96 | var parameters = new List
97 | {
98 | new Parameter() { Name = "/test/level1", Type = ParameterType.String, Value = "{\"level1Key\" : [\"level1value1\", \"level1value2\"] }" },
99 | new Parameter() { Name = "/test/level1/Level1key", Type = ParameterType.StringList, Value = "level1valueOverriden" },
100 | };
101 |
102 | var duplicateParameterException = Assert.Throws(() => _parameterProcessor.ProcessParameters(parameters, "/test"));
103 | Assert.Equal("Duplicate parameter 'level1:Level1key:0' found. Parameter keys are case-insensitive.", duplicateParameterException.Message);
104 | }
105 |
106 |
107 | [Fact]
108 | public void ProcessParametersProcessesStringListParameters()
109 | {
110 | var parameters = new List
111 | {
112 | new Parameter() { Name = "/test/stringList", Type = ParameterType.StringList, Value = "value1,value2" }
113 | };
114 | var result = _parameterProcessor.ProcessParameters(parameters, "/test");
115 |
116 | Assert.Equal(2, result.Count);
117 | Assert.True(result.ContainsKey("stringList:0"));
118 | Assert.Equal("value1", result["stringList:0"]);
119 | Assert.True(result.ContainsKey("stringList:1"));
120 | Assert.Equal("value2", result["stringList:1"]);
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigProcessorTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.IO;
19 | using System.Text;
20 | using Amazon.Extensions.Configuration.SystemsManager.AppConfig;
21 | using Xunit;
22 |
23 | namespace Amazon.Extensions.Configuration.SystemsManager.Tests
24 | {
25 | public class AppConfigProcessorTests
26 | {
27 | [Fact]
28 | public void ParseConfig_ApplicationJson_ParsesSuccessfully()
29 | {
30 | var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
31 | using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
32 |
33 | var result = AppConfigProcessor.ParseConfig("application/json", stream);
34 |
35 | Assert.Equal("value1", result["key1"]);
36 | Assert.Equal("value2", result["key2"]);
37 | }
38 |
39 | [Fact]
40 | public void ParseConfig_ApplicationOctetStream_ParsesJsonSuccessfully()
41 | {
42 | var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
43 | using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
44 |
45 | var result = AppConfigProcessor.ParseConfig("application/octet-stream", stream);
46 |
47 | Assert.Equal("value1", result["key1"]);
48 | Assert.Equal("value2", result["key2"]);
49 | }
50 |
51 | [Fact]
52 | public void ParseConfig_ApplicationJsonWithCharset_ParsesSuccessfully()
53 | {
54 | var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
55 | using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
56 |
57 | var result = AppConfigProcessor.ParseConfig("application/json; charset=utf-8", stream);
58 |
59 | Assert.Equal("value1", result["key1"]);
60 | Assert.Equal("value2", result["key2"]);
61 | }
62 |
63 | [Fact]
64 | public void ParseConfig_UnknownContentType_ParsesJsonSuccessfully()
65 | {
66 | var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
67 | using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
68 |
69 | var result = AppConfigProcessor.ParseConfig("application/unknown", stream);
70 |
71 | Assert.Equal("value1", result["key1"]);
72 | Assert.Equal("value2", result["key2"]);
73 | }
74 |
75 | [Fact]
76 | public void ParseConfig_NullContentType_ParsesJsonSuccessfully()
77 | {
78 | var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
79 | using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
80 |
81 | var result = AppConfigProcessor.ParseConfig(null, stream);
82 |
83 | Assert.Equal("value1", result["key1"]);
84 | Assert.Equal("value2", result["key2"]);
85 | }
86 |
87 | [Fact]
88 | public void ParseConfig_NestedJson_ParsesSuccessfully()
89 | {
90 | var jsonContent = "{\"section1\":{\"key1\":\"value1\",\"key2\":\"value2\"},\"section2\":{\"key3\":\"value3\"}}";
91 | using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
92 |
93 | var result = AppConfigProcessor.ParseConfig("application/octet-stream", stream);
94 |
95 | Assert.Equal("value1", result["section1:key1"]);
96 | Assert.Equal("value2", result["section1:key2"]);
97 | Assert.Equal("value3", result["section2:key3"]);
98 | }
99 |
100 | [Fact]
101 | public void ParseConfig_InvalidJson_ThrowsInvalidOperationException()
102 | {
103 | var invalidJsonContent = "{ invalid json content }";
104 | using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJsonContent));
105 |
106 | var exception = Assert.Throws(() =>
107 | AppConfigProcessor.ParseConfig("application/json", stream));
108 |
109 | Assert.Contains("Failed to parse AppConfig content as JSON", exception.Message);
110 | Assert.Contains("Content-Type was 'application/json'", exception.Message);
111 | }
112 |
113 | [Fact]
114 | public void ParseConfig_InvalidJsonWithOctetStream_ThrowsInvalidOperationException()
115 | {
116 | var invalidJsonContent = "not json at all";
117 | using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJsonContent));
118 |
119 | var exception = Assert.Throws(() =>
120 | AppConfigProcessor.ParseConfig("application/octet-stream", stream));
121 |
122 | Assert.Contains("Failed to parse AppConfig content as JSON", exception.Message);
123 | Assert.Contains("Content-Type was 'application/octet-stream'", exception.Message);
124 | }
125 |
126 | [Fact]
127 | public void ParseConfig_EmptyJson_ReturnsEmptyDictionary()
128 | {
129 | var jsonContent = "{}";
130 | using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));
131 |
132 | var result = AppConfigProcessor.ParseConfig("application/json", stream);
133 |
134 | Assert.Empty(result);
135 | }
136 |
137 | [Fact]
138 | public void ParseConfig_EmptyStream_ThrowsInvalidOperationException()
139 | {
140 | using var stream = new MemoryStream();
141 |
142 | var exception = Assert.Throws(() =>
143 | AppConfigProcessor.ParseConfig("application/json", stream));
144 |
145 | Assert.Contains("Failed to parse AppConfig content as JSON", exception.Message);
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/.github/workflows/sync-main-dev.yml:
--------------------------------------------------------------------------------
1 | # This GitHub Workflow is designed to run automatically after the Release PR, which was created by the `Create Release PR` workflow, is closed.
2 | # This workflow has 2 jobs. One will run if the `Release PR` is successfully merged, indicating that a release should go out.
3 | # The other will run if the `Release PR` was closed and a release is not intended to go out.
4 | name: Sync 'dev' and 'main'
5 |
6 | # The workflow will automatically be triggered when any PR is closed.
7 | on:
8 | pull_request:
9 | types: [closed]
10 |
11 | permissions:
12 | contents: write
13 | id-token: write
14 |
15 | jobs:
16 | # This job will check if the PR was successfully merged, it's source branch is `releases/next-release` and target branch is `dev`.
17 | # This indicates that the merged PR was the `Release PR`.
18 | # This job will synchronize `dev` and `main`, create a GitHub Release and delete the `releases/next-release` branch.
19 | sync-dev-and-main:
20 | name: Sync dev and main
21 | if: |
22 | github.event.pull_request.merged == true &&
23 | github.event.pull_request.head.ref == 'releases/next-release' &&
24 | github.event.pull_request.base.ref == 'dev'
25 | runs-on: ubuntu-latest
26 | steps:
27 | # Assume an AWS Role that provides access to the Access Token
28 | - name: Configure AWS Credentials
29 | uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v4
30 | with:
31 | role-to-assume: ${{ secrets.RELEASE_WORKFLOW_ACCESS_TOKEN_ROLE_ARN }}
32 | aws-region: us-west-2
33 | # Retrieve the Access Token from Secrets Manager
34 | - name: Retrieve secret from AWS Secrets Manager
35 | uses: aws-actions/aws-secretsmanager-get-secrets@a9a7eb4e2f2871d30dc5b892576fde60a2ecc802 #v2.0.10
36 | with:
37 | secret-ids: |
38 | AWS_SECRET, ${{ secrets.RELEASE_WORKFLOW_ACCESS_TOKEN_NAME }}
39 | parse-json-secrets: true
40 | # Checkout a full clone of the repo
41 | - name: Checkout code
42 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
43 | with:
44 | ref: dev
45 | fetch-depth: 0
46 | token: ${{ env.AWS_SECRET_TOKEN }}
47 | # Install .NET8 which is needed for AutoVer
48 | - name: Setup .NET 8.0
49 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 #v4.3.1
50 | with:
51 | dotnet-version: 8.0.x
52 | # Install AutoVer which is needed to retrieve information about the current release.
53 | - name: Install AutoVer
54 | run: dotnet tool install --global AutoVer --version 0.0.25
55 | # Set up a git user to be able to run git commands later on
56 | - name: Setup Git User
57 | run: |
58 | git config --global user.email "github-aws-sdk-dotnet-automation@amazon.com"
59 | git config --global user.name "aws-sdk-dotnet-automation"
60 | # Retrieve the release name which is needed for the GitHub Release
61 | - name: Read Release Name
62 | id: read-release-name
63 | run: |
64 | version=$(autover changelog --release-name)
65 | echo "VERSION=$version" >> $GITHUB_OUTPUT
66 | # Retrieve the tag name which is needed for the GitHub Release
67 | - name: Read Tag Name
68 | id: read-tag-name
69 | run: |
70 | tag=$(autover changelog --tag-name)
71 | echo "TAG=$tag" >> $GITHUB_OUTPUT
72 | # Retrieve the changelog which is needed for the GitHub Release
73 | - name: Read Changelog
74 | id: read-changelog
75 | run: |
76 | changelog=$(autover changelog --output-to-console)
77 | echo "CHANGELOG<> "$GITHUB_OUTPUT"
78 | # Merge dev into main in order to synchronize the 2 branches
79 | - name: Merge dev to main
80 | run: |
81 | git fetch origin
82 | git checkout main
83 | git merge dev
84 | git push origin main
85 | # Create the GitHub Release
86 | - name: Create GitHub Release
87 | env:
88 | GITHUB_TOKEN: ${{ env.AWS_SECRET_TOKEN }}
89 | run: |
90 | gh release create "${{ steps.read-tag-name.outputs.TAG }}" --title "${{ steps.read-release-name.outputs.VERSION }}" --notes "${{ steps.read-changelog.outputs.CHANGELOG }}"
91 | # Delete the `releases/next-release` branch
92 | - name: Clean up
93 | run: |
94 | git fetch origin
95 | git push origin --delete releases/next-release
96 | # This job will check if the PR was closed, it's source branch is `releases/next-release` and target branch is `dev`.
97 | # This indicates that the closed PR was the `Release PR`.
98 | # This job will delete the tag created by AutoVer and the release branch.
99 | clean-up-closed-release:
100 | name: Clean up closed release
101 | if: |
102 | github.event.pull_request.merged == false &&
103 | github.event.pull_request.head.ref == 'releases/next-release' &&
104 | github.event.pull_request.base.ref == 'dev'
105 | runs-on: ubuntu-latest
106 | steps:
107 | # Checkout a full clone of the repo
108 | - name: Checkout code
109 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
110 | with:
111 | ref: releases/next-release
112 | fetch-depth: 0
113 | # Install .NET8 which is needed for AutoVer
114 | - name: Setup .NET 8.0
115 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 #v4.3.1
116 | with:
117 | dotnet-version: 8.0.x
118 | # Install AutoVer which is needed to retrieve information about the current release.
119 | - name: Install AutoVer
120 | run: dotnet tool install --global AutoVer --version 0.0.25
121 | # Set up a git user to be able to run git commands later on
122 | - name: Setup Git User
123 | run: |
124 | git config --global user.email "github-aws-sdk-dotnet-automation@amazon.com"
125 | git config --global user.name "aws-sdk-dotnet-automation"
126 | # Retrieve the tag name to be deleted
127 | - name: Read Tag Name
128 | id: read-tag-name
129 | run: |
130 | tag=$(autover changelog --tag-name)
131 | echo "TAG=$tag" >> $GITHUB_OUTPUT
132 | # Delete the tag created by AutoVer and the release branch
133 | - name: Clean up
134 | run: |
135 | git fetch origin
136 | git push --delete origin ${{ steps.read-tag-name.outputs.TAG }}
137 | git push origin --delete releases/next-release
138 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/SystemsManagerConfigurationProvider.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.Threading;
19 | using System.Threading.Tasks;
20 | using Amazon.Extensions.Configuration.SystemsManager.AppConfig;
21 | using Amazon.Extensions.Configuration.SystemsManager.Internal;
22 | using Microsoft.Extensions.Configuration;
23 | using Microsoft.Extensions.Primitives;
24 |
25 | namespace Amazon.Extensions.Configuration.SystemsManager
26 | {
27 | ///
28 | ///
29 | /// An AWS Systems Manager based .
30 | ///
31 | public class SystemsManagerConfigurationProvider : ConfigurationProvider
32 | {
33 | private ISystemsManagerConfigurationSource Source { get; }
34 | private ISystemsManagerProcessor SystemsManagerProcessor { get; }
35 |
36 | private ManualResetEvent ReloadTaskEvent { get; } = new ManualResetEvent(true);
37 |
38 | ///
39 | ///
40 | /// Initializes a new instance with the specified source.
41 | ///
42 | /// The used to retrieve values from AWS Systems Manager Parameter Store
43 | public SystemsManagerConfigurationProvider(SystemsManagerConfigurationSource source) : this(source, new SystemsManagerProcessor(source))
44 | {
45 | }
46 |
47 | ///
48 | ///
49 | /// Initializes a new instance with the specified source.
50 | ///
51 | /// The used to retrieve values from AWS Systems Manager AppConfig
52 | public SystemsManagerConfigurationProvider(AppConfigConfigurationSource source) : this(source, new AppConfigProcessor(source))
53 | {
54 | }
55 |
56 |
57 | ///
58 | ///
59 | /// Initializes a new instance with the specified source.
60 | ///
61 | /// The used to retrieve values from AWS Systems Manager
62 | /// The used to retrieve values from AWS Systems Manager
63 | public SystemsManagerConfigurationProvider(ISystemsManagerConfigurationSource source, ISystemsManagerProcessor systemsManagerProcessor)
64 | {
65 | Source = source ?? throw new ArgumentNullException(nameof(source));
66 | SystemsManagerProcessor = systemsManagerProcessor ?? throw new ArgumentNullException(nameof(systemsManagerProcessor));
67 |
68 | if (source.ReloadAfter != null)
69 | {
70 | ChangeToken.OnChange(() =>
71 | {
72 | var cancellationTokenSource = new CancellationTokenSource(source.ReloadAfter.Value);
73 | var cancellationChangeToken = new CancellationChangeToken(cancellationTokenSource.Token);
74 | return cancellationChangeToken;
75 | }, async () =>
76 | {
77 | ReloadTaskEvent.Reset();
78 | try
79 | {
80 | await LoadAsync(true).ConfigureAwait(false);
81 | }
82 | finally
83 | {
84 | ReloadTaskEvent.Set();
85 | }
86 | });
87 | }
88 | }
89 |
90 | ///
91 | /// If this configuration provider is currently performing a reload of the config data this method will block until
92 | /// the reload is called.
93 | ///
94 | /// This method is not meant for general use. It is exposed so a AWS Lambda function can wait for the reload to complete
95 | /// before completing the event causing the AWS Lambda compute environment to be frozen.
96 | ///
97 | public void WaitForReloadToComplete(TimeSpan timeout)
98 | {
99 | ReloadTaskEvent.WaitOne(timeout);
100 | }
101 |
102 | ///
103 | ///
104 | /// Loads the AWS Systems Manager Parameters.
105 | ///
106 | public override void Load() => LoadAsync(false).ConfigureAwait(false).GetAwaiter().GetResult();
107 |
108 | // If 1) reload flag is set to true and 2) OnLoadException handler is not set,
109 | // all exceptions raised during OnReload() will be ignored.
110 | private async Task LoadAsync(bool reload)
111 | {
112 | try
113 | {
114 | var newData = await SystemsManagerProcessor.GetDataAsync().ConfigureAwait(false) ?? new Dictionary();
115 |
116 | if (!Data.EquivalentTo(newData))
117 | {
118 | Data = newData;
119 |
120 | OnReload();
121 | }
122 | }
123 | catch (DuplicateParameterException) // Throw duplicate parameter exception irrespective of whether parameter is optional or not.
124 | {
125 | throw;
126 | }
127 | catch (Exception ex)
128 | {
129 | if (Source.Optional) return;
130 |
131 | var ignoreException = reload;
132 | if (Source.OnLoadException != null)
133 | {
134 | var exceptionContext = new SystemsManagerExceptionContext
135 | {
136 | Provider = this,
137 | Exception = ex,
138 | Reload = reload
139 | };
140 | Source.OnLoadException(exceptionContext);
141 | ignoreException = exceptionContext.Ignore;
142 | }
143 |
144 | if (!ignoreException)
145 | throw;
146 | }
147 | }
148 | }
149 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Release 2025-11-17
2 |
3 | ### Amazon.Extensions.Configuration.SystemsManager (7.0.1)
4 | * Update AWS SDK Dependencies
5 | * Removed content type check for app config. We now always try to parse json first
6 |
7 | ## Release 2025-04-28
8 |
9 | ### Amazon.Extensions.Configuration.SystemsManager (7.0.0)
10 | * Updated the .NET SDK dependencies to the latest version GA 4.0.0
11 |
12 | ## Release 2025-04-01 #2
13 |
14 | ### Amazon.Extensions.Configuration.SystemsManager (7.0.0-preview.3)
15 | * Update AWS SDK to Preview 11
16 | * Remove support for .NET 6
17 | * Remove support for netcoreapp3.1
18 |
19 | ## Release 2025-02-28
20 |
21 | ### Amazon.Extensions.Configuration.SystemsManager (7.0.0-preview.2)
22 | * Update .NET SDK dependencies to v4.0.0-preview.8
23 |
24 | ## Release 2025-02-20
25 |
26 | ### Amazon.Extensions.Configuration.SystemsManager (6.3.0)
27 | * Add opt-in JsonOrStringParameterProcessor processor supporting parameters being either strings or JSON
28 |
29 | ## Release 2024-10-17
30 |
31 | ### Amazon.Extensions.Configuration.SystemsManager (7.0.0-preview.1)
32 | * Updated the .NET SDK dependencies to the latest version 4.0.0-preview.4
33 | * Marked project as trimmable
34 | * Added SourceLink support
35 |
36 | ## Release 2024-10-15
37 |
38 | ### Amazon.Extensions.Configuration.SystemsManager (6.2.2)
39 | * Update System.Text.Json for .NET Standard 2.0 target to version 8.0.5.
40 |
41 | ## Release 2024-07-30
42 |
43 | ### Amazon.Extensions.Configuration.SystemsManager (6.2.1)
44 | * For .NET Standard 2.0 target updated the dependency version of System.Text.Json to version 8.0.4
45 |
46 | ## Release 2024-06-19
47 |
48 | ### Amazon.Extensions.Configuration.SystemsManager (6.2.0)
49 | * **Breaking Change:** Throw exception if duplicate Systems Manager parameter keys (case insensitive) are detected irrespective of whether the parameter is optional or not.
50 |
51 | ## Release 2024-04-20
52 |
53 | ### Amazon.Extensions.Configuration.SystemsManager (6.1.1)
54 | * Update User-Agent string
55 |
56 | ## Release 2024-03-29
57 |
58 | ### Amazon.Extensions.Configuration.SystemsManager (6.1.0)
59 | * Pull request [#163](https://github.com/aws/aws-dotnet-extensions-configuration/pull/163) adding .NET 8 target. Thanks [Jon Armen](https://github.com/jon-armen)
60 |
61 | ## Release 2023-09-21
62 |
63 | ### Amazon.Extensions.Configuration.SystemsManager (6.0.0)
64 | * BREAKING CHANGE: Added StringList handling in default parameter processor.
65 | * Note: This is re-releasing 5.1.1 as a major version bump because it introduced a change in behavior if a user was already handling StringList parameters.
66 |
67 | ## Release 2023-09-11
68 |
69 | ### Amazon.Extensions.Configuration.SystemsManager (5.1.1)
70 | * Pull request [#142](https://github.com/aws/aws-dotnet-extensions-configuration/pull/142) Added StringList handling in default parameter processor. Thanks [ArtemMelnychenko](https://github.com/Plazmius)
71 | * Note: We have unlisted and re-released this as 6.0.0. due to the breaking change reported in [#156](https://github.com/aws/aws-dotnet-extensions-configuration/issues/156)
72 |
73 | ## Release 2023-05-31
74 |
75 | ### Amazon.Extensions.Configuration.SystemsManager (5.1.0)
76 | * Pull request [#150](https://github.com/aws/aws-dotnet-extensions-configuration/pull/150) Adding .NET Standard 2.0 support for .NET Framework applications. Thanks [Matthew Heaton](https://github.com/heatonmatthew)
77 |
78 | ## Release 2023-03-21
79 |
80 | ### Amazon.Extensions.Configuration.SystemsManager (5.0.2)
81 | * Fixed an issue where AppConfig JSON configuration with charset information in ContentType was not being parsed.
82 |
83 | ## Release 2023-03-06
84 |
85 | ### Amazon.Extensions.Configuration.SystemsManager (5.0.1)
86 | * Fixed an issue where JsonParameterProcessor was prefixing `:` character to parameter properties while retrieving parameter by name.
87 |
88 | ## Release 2023-02-07
89 |
90 | ### Amazon.Extensions.Configuration.SystemsManager (5.0.0)
91 | * **Breaking Change:** Fixed issue when parsing JSON SSM parameter values to include the relative SSM parameter name to the JSON property names.
92 |
93 | ## Release 2023-02-01
94 |
95 | ### Amazon.Extensions.Configuration.SystemsManager (4.0.1)
96 | * Merged PR [#128](https://github.com/aws/aws-dotnet-extensions-configuration/pull/128) fixed issue parsing AppConfig response when services returns empty response for no changes. Thanks [Tyler Ohlsen](https://github.com/tylerohlsen)
97 |
98 | ## Release 2022-06-28
99 |
100 | ### Amazon.Extensions.Configuration.SystemsManager (4.0.0)
101 | * Merged PR [#99](https://github.com/aws/aws-dotnet-extensions-configuration/pull/99) adding support for using AppConfig Lambda extension. Thanks [mgorski-mg](https://github.com/mgorski-mg)
102 | * Merged PR [#69](https://github.com/aws/aws-dotnet-extensions-configuration/pull/69) making IParameterProcessor used for customizing data from AWS into config values more genenric. Thanks [warej](https://github.com/warej)
103 | * Merged PR [#59](https://github.com/aws/aws-dotnet-extensions-configuration/pull/59) removing dependency on Newtonsoft.JSON and now use System.Text.Json. Thanks [Adilson de Almeida Junior](https://github.com/Adilson)
104 | * Accessing AppConfig data now uses the AWS AppConfig Data APIs StargConfigurationSession and GetLatestConfiguration
105 | * **Breaking Change:** The IncludeParameter, GetKey and GetValue methods were removed from IParameterProcessor in favor of the new generic ProcessParameters method allowing more flexibility in implementations.
106 | * **Breaking Change:** `ClientId` was removed from `AppConfigConfigurationSource`.
107 | * **Breaking Change:** When using AppConfig IAM permissions for `appconfig:StartConfigurationSession` and `appconfig:GetLatestConfiguration` are now required and `appconfig:GetConfiguration` is not longer required.
108 |
109 | ## Release 2021-08-18
110 |
111 | ### Amazon.Extensions.Configuration.SystemsManager (3.0.0)
112 | * Merged PR [#82](https://github.com/aws/aws-dotnet-extensions-configuration/pull/82) Adding [AWS AppConfig](https://docs.aws.amazon.com/appconfig/latest/userguide/what-is-appconfig.html) support. Thanks [Michał Górski](https://github.com/mgorski-mg)
113 |
114 | ## Release 2021-07-23
115 |
116 | ### Amazon.Extensions.Configuration.SystemsManager (2.1.1)
117 | * Update AWSSDK.Extensions.NETCore.Setup and AWSSDK.SimpleSystemsManagement package references for SSO credential support.
118 |
119 | ## Release 2021-03-30
120 |
121 | ### Amazon.Extensions.Configuration.SystemsManager (2.1.0)
122 | * Update AWS SDK dependencies to version 3.7
123 |
124 | ## Release 2020-10-09
125 |
126 | ### Amazon.Extensions.Configuration.SystemsManager (2.0.0)
127 | * Merged PR [#75](https://github.com/aws/aws-dotnet-extensions-configuration/pull/75) Update AWS SDK dependencies to 3.5. Thanks [Doug Ferris](https://github.com/doug-ferris)
128 | * Update Newtonsoft.Json to version 12.
129 |
130 | ## Release 2019-07-23
131 |
132 | ### Amazon.Extensions.Configuration.SystemsManager (1.2.0)
133 | * Merged PR [#41](https://github.com/aws/aws-dotnet-extensions-configuration/pull/41) Prevents stripping the first char when the Path is "/". Thanks [Ken Hundley](https://github.com/KenHundley)
134 | * Merged PR [#44](https://github.com/aws/aws-dotnet-extensions-configuration/pull/44) Added `Filters` property to SystemsManagerConfigurationSource. Thanks [Zahy Hwary](https://github.com/zahycs)
135 |
136 | ## Release 2019-04-09
137 |
138 | ### Amazon.Extensions.Configuration.SystemsManager (1.1.1)
139 | * Added AWS Secrets Manager support. Thanks [Ken Hundley](https://github.com/KenHundley)
140 | * Only trigger OnReload when values have changed.
141 | * Update version of the AWS SDK for .NET to 3.3.100
142 |
143 | ## Release 2019-01-11
144 |
145 | ### Amazon.Extensions.Configuration.SystemsManager (1.0.1)
146 | * Made Analysers for development/local only
147 |
148 |
149 | ## Release 2019-12-17
150 |
151 | ### Amazon.Extensions.Configuration.SystemsManager (1.0.0)
152 | * Initial release
153 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using System.Collections.Generic;
18 | using System.IO;
19 | using System.Net.Http;
20 | using System.Text.Json;
21 | using System.Threading;
22 | using System.Threading.Tasks;
23 | using Amazon.AppConfigData;
24 | using Amazon.AppConfigData.Model;
25 | using Amazon.Extensions.Configuration.SystemsManager.Internal;
26 |
27 | namespace Amazon.Extensions.Configuration.SystemsManager.AppConfig
28 | {
29 | // Types that own disposable fields should be disposable. This warning is okay to ignore because
30 | // the instance of IAmazonAppConfigData is meant to last for the length of the application.
31 | #pragma warning disable CA1001
32 | public class AppConfigProcessor : ISystemsManagerProcessor
33 | #pragma warning restore CA1001
34 | {
35 | private AppConfigConfigurationSource Source { get; }
36 | private IDictionary LastConfig { get; set; }
37 |
38 | private string PollConfigurationToken { get; set; }
39 | private DateTime NextAllowedPollTime { get; set; }
40 |
41 | private SemaphoreSlim _lastConfigLock = new SemaphoreSlim(1, 1);
42 | private const int _lastConfigLockTimeout = 3000;
43 |
44 | private Uri _lambdaExtensionUri;
45 | private HttpClient _lambdaExtensionClient;
46 |
47 | private IAmazonAppConfigData _appConfigDataClient;
48 |
49 | public AppConfigProcessor(AppConfigConfigurationSource source)
50 | {
51 | Source = source;
52 |
53 | if (source.ApplicationId == null) throw new ArgumentNullException(nameof(source.ApplicationId));
54 | if (source.EnvironmentId == null) throw new ArgumentNullException(nameof(source.EnvironmentId));
55 | if (source.ConfigProfileId == null) throw new ArgumentNullException(nameof(source.ConfigProfileId));
56 |
57 | // Check to see if the function is being run inside Lambda. If it is not because it is running in a integ test or in the
58 | // the .NET Lambda Test Tool the Lambda extension is not available and fallback to using the AppConfig service directly.
59 | if(Source.UseLambdaExtension && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME")))
60 | {
61 | var port = Environment.GetEnvironmentVariable("AWS_APPCONFIG_EXTENSION_HTTP_PORT") ?? "2772";
62 | _lambdaExtensionUri = new Uri($"http://localhost:{port}/applications/{Source.ApplicationId}/environments/{Source.EnvironmentId}/configurations/{Source.ConfigProfileId}");
63 | _lambdaExtensionClient = source.CustomHttpClientForLambdaExtension ?? new HttpClient();
64 | }
65 | else
66 | {
67 | if(source.AwsOptions != null)
68 | {
69 | _appConfigDataClient = source.AwsOptions.CreateServiceClient();
70 | }
71 | else
72 | {
73 | _appConfigDataClient = new AmazonAppConfigDataClient();
74 | }
75 |
76 | if (_appConfigDataClient is AmazonAppConfigDataClient impl)
77 | {
78 | impl.BeforeRequestEvent += ServiceClientAppender.ServiceClientBeforeRequestEvent;
79 | }
80 | }
81 | }
82 |
83 | public async Task> GetDataAsync()
84 | {
85 |
86 | if(_appConfigDataClient != null)
87 | {
88 | return await GetDataFromServiceAsync().ConfigureAwait(false)
89 | ?? new Dictionary();
90 | }
91 | else
92 | {
93 | return await GetDataFromLambdaExtensionAsync().ConfigureAwait(false)
94 | ?? new Dictionary();
95 | }
96 | }
97 |
98 | private async Task> GetDataFromLambdaExtensionAsync()
99 | {
100 | using (var response = await _lambdaExtensionClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, _lambdaExtensionUri)).ConfigureAwait(false))
101 | using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
102 | {
103 | LastConfig = ParseConfig(response.Content.Headers.ContentType.ToString(), stream);
104 | }
105 |
106 | return LastConfig;
107 | }
108 |
109 | private async Task> GetDataFromServiceAsync()
110 | {
111 | if(await _lastConfigLock.WaitAsync(_lastConfigLockTimeout).ConfigureAwait(false))
112 | {
113 | try
114 | {
115 | if(DateTime.UtcNow < NextAllowedPollTime)
116 | {
117 | return LastConfig;
118 | }
119 |
120 | if (string.IsNullOrEmpty(PollConfigurationToken))
121 | {
122 | this.PollConfigurationToken = await GetInitialConfigurationTokenAsync(_appConfigDataClient).ConfigureAwait(false);
123 | }
124 |
125 | var request = new GetLatestConfigurationRequest
126 | {
127 | ConfigurationToken = PollConfigurationToken
128 | };
129 |
130 | var response = await _appConfigDataClient.GetLatestConfigurationAsync(request).ConfigureAwait(false);
131 | PollConfigurationToken = response.NextPollConfigurationToken;
132 | NextAllowedPollTime = DateTime.UtcNow.AddSeconds(response.NextPollIntervalInSeconds ?? 0);
133 |
134 | // Configuration is empty when the last received config is the latest
135 | // so only attempt to parse the AppConfig response when it is not empty
136 | if (response.ContentLength > 0)
137 | {
138 | LastConfig = ParseConfig(response.ContentType, response.Configuration);
139 | }
140 | }
141 | finally
142 | {
143 | _lastConfigLock.Release();
144 | }
145 | }
146 | else
147 | {
148 | return LastConfig;
149 | }
150 |
151 | return LastConfig;
152 | }
153 |
154 | private async Task GetInitialConfigurationTokenAsync(IAmazonAppConfigData appConfigClient)
155 | {
156 | var request = new StartConfigurationSessionRequest
157 | {
158 | ApplicationIdentifier = Source.ApplicationId,
159 | EnvironmentIdentifier = Source.EnvironmentId,
160 | ConfigurationProfileIdentifier = Source.ConfigProfileId
161 | };
162 |
163 | return (await appConfigClient.StartConfigurationSessionAsync(request).ConfigureAwait(false)).InitialConfigurationToken;
164 | }
165 |
166 | internal static IDictionary ParseConfig(string contentType, Stream configuration)
167 | {
168 | try
169 | {
170 | return JsonConfigurationParser.Parse(configuration);
171 | }
172 | catch (JsonException ex)
173 | {
174 | throw new InvalidOperationException(
175 | $"Failed to parse AppConfig content as JSON. Content-Type was '{contentType}'. {ex.Message}",
176 | ex);
177 | }
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigForLambdaExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using Amazon.Extensions.Configuration.SystemsManager;
18 | using Amazon.Extensions.Configuration.SystemsManager.AppConfig;
19 | using Amazon.Runtime;
20 | using Amazon.AppConfigData;
21 |
22 | // ReSharper disable once CheckNamespace
23 | namespace Microsoft.Extensions.Configuration
24 | {
25 | ///
26 | /// Extension methods for registering with .
27 | ///
28 | public static class AppConfigForLambdaExtensions
29 | {
30 | ///
31 | /// Adds an that reads configuration values from AWS Systems Manager AWS AppConfig using the AWS Lambda Extension.
32 | /// For more information about using the AppConfig Lambda Extension checkout the AppConfig user guide.
33 | ///
34 | ///
35 | /// The AppConfig Lambda extension reloads configuration data using the interval set by the AWS_APPCONFIG_EXTENSION_POLL_INTERVAL_SECONDS environment variable
36 | /// or 45 seconds if not set. The .NET configuration provider will refresh at the same interval plus a 5 second buffer for the extension to complete its update
37 | /// process.
38 | ///
39 | /// The to add to.
40 | /// The AppConfig application id.
41 | /// The AppConfig environment id.
42 | /// The AppConfig configuration profile id.
43 | /// Whether the AWS Systems Manager AppConfig is optional.
44 | /// cannot be null
45 | /// cannot be null
46 | /// cannot be null
47 | /// The .
48 | public static IConfigurationBuilder AddAppConfigUsingLambdaExtension(this IConfigurationBuilder builder, string applicationId, string environmentId, string configProfileId, bool optional)
49 | {
50 | if (applicationId == null) throw new ArgumentNullException(nameof(applicationId));
51 | if (environmentId == null) throw new ArgumentNullException(nameof(environmentId));
52 | if (configProfileId == null) throw new ArgumentNullException(nameof(configProfileId));
53 |
54 | return builder.AddAppConfigUsingLambdaExtension(ConfigureSource(applicationId, environmentId, configProfileId, optional: optional));
55 | }
56 |
57 | ///
58 | /// Adds an that reads configuration values from AWS Systems Manager AWS AppConfig using the AWS Lambda Extension.
59 | /// For more information about using the AppConfig Lambda Extension checkout the AppConfig user guide.
60 | ///
61 | ///
62 | /// The AppConfig Lambda extension reloads configuration data using the interval set by the AWS_APPCONFIG_EXTENSION_POLL_INTERVAL_SECONDS environment variable
63 | /// or 45 seconds if not set. The .NET configuration provider will refresh at the same interval plus a 5 second buffer for the extension to complete its update
64 | /// process.
65 | ///
66 | /// The to add to.
67 | /// The AppConfig application id.
68 | /// The AppConfig environment id.
69 | /// The AppConfig configuration profile id.
70 | /// cannot be null
71 | /// cannot be null
72 | /// cannot be null
73 | /// The .
74 | public static IConfigurationBuilder AddAppConfigUsingLambdaExtension(this IConfigurationBuilder builder, string applicationId, string environmentId, string configProfileId)
75 | {
76 | if (applicationId == null) throw new ArgumentNullException(nameof(applicationId));
77 | if (environmentId == null) throw new ArgumentNullException(nameof(environmentId));
78 | if (configProfileId == null) throw new ArgumentNullException(nameof(configProfileId));
79 |
80 | return builder.AddAppConfigUsingLambdaExtension(ConfigureSource(applicationId, environmentId, configProfileId));
81 | }
82 |
83 | ///
84 | /// Adds an that reads configuration values from AWS Systems Manager AWS AppConfig using the AWS Lambda Extension.
85 | /// For more information about using the AppConfig Lambda Extension checkout the AppConfig user guide.
86 | ///
87 | ///
88 | /// The AppConfig Lambda extension reloads configuration data using the interval set by the AWS_APPCONFIG_EXTENSION_POLL_INTERVAL_SECONDS environment variable
89 | /// or 45 seconds if not set. The .NET configuration provider will refresh at the same interval plus a 5 second buffer for the extension to complete its update
90 | /// process.
91 | ///
92 | /// The to add to.
93 | /// Configuration source.
94 | /// cannot be null
95 | /// . cannot be null
96 | /// . cannot be null
97 | /// . cannot be null
98 | /// The .
99 | public static IConfigurationBuilder AddAppConfigUsingLambdaExtension(this IConfigurationBuilder builder, AppConfigConfigurationSource source)
100 | {
101 | if (source == null) throw new ArgumentNullException(nameof(source));
102 | if (source.ApplicationId == null) throw new ArgumentNullException(nameof(source.ApplicationId));
103 | if (source.EnvironmentId == null) throw new ArgumentNullException(nameof(source.EnvironmentId));
104 | if (source.ConfigProfileId == null) throw new ArgumentNullException(nameof(source.ConfigProfileId));
105 |
106 | // Create a specific instance of AmazonAppConfigClient that is configured to make calls to the endpoint setup by the AppConfig Lambda layer.
107 | source.UseLambdaExtension = true;
108 |
109 | if(!source.ReloadAfter.HasValue)
110 | {
111 | // default polling duration is 45 seconds https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-integration-lambda-extensions.html
112 | var reloadAfter = 45;
113 |
114 | // Since the user is using Lambda extension which automatically refreshes the data default to the configuration provider defaulting
115 | // to reload at the rate the extension reloads plus 5 second buffer.
116 | var reloadAfterStr = Environment.GetEnvironmentVariable("AWS_APPCONFIG_EXTENSION_POLL_INTERVAL_SECONDS");
117 | if (reloadAfterStr != null && !int.TryParse(reloadAfterStr, out reloadAfter))
118 | {
119 | throw new ArgumentException("Environment variable AWS_APPCONFIG_EXTENSION_POLL_INTERVAL_SECONDS used for computing ReloadAfter is not set to a valid integer");
120 | }
121 |
122 | reloadAfter += 5;
123 | source.ReloadAfter = TimeSpan.FromSeconds(reloadAfter);
124 | }
125 |
126 | return builder.Add(source);
127 | }
128 |
129 | private static AppConfigConfigurationSource ConfigureSource(
130 | string applicationId,
131 | string environmentId,
132 | string configProfileId,
133 | bool optional = false
134 | )
135 | {
136 | return new AppConfigConfigurationSource
137 | {
138 | ApplicationId = applicationId,
139 | EnvironmentId = environmentId,
140 | ConfigProfileId = configProfileId,
141 | Optional = optional
142 | };
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/test/Amazon.Extensions.Configuration.SystemsManager.Integ/AppConfigEndToEndTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Text.Json;
7 | using System.Threading.Tasks;
8 |
9 | using Amazon.AppConfig;
10 | using Amazon.AppConfig.Model;
11 | using Amazon.Extensions.NETCore.Setup;
12 | using Microsoft.Extensions.Configuration;
13 | using Xunit;
14 |
15 | namespace Amazon.Extensions.Configuration.SystemsManager.Integ
16 | {
17 | public class AppConfigEndToEndTests
18 | {
19 | IAmazonAppConfig _appConfigClient = new AmazonAppConfigClient(RegionEndpoint.USWest2);
20 |
21 | [Fact]
22 | public async Task RefreshConfiguration()
23 | {
24 | var configSettings = new Dictionary
25 | {
26 | { "key1", "value1" },
27 | { "key2", "value2" }
28 | };
29 | (string applicationId, string environmentId, string configProfileId) = await CreateAppConfigResourcesAsync("RefreshWebAppTest", configSettings);
30 | try
31 | {
32 | var builder = new ConfigurationBuilder()
33 | .AddAppConfig(applicationId, environmentId, configProfileId, new AWSOptions {Region = RegionEndpoint.USWest2 }, TimeSpan.FromSeconds(5));
34 |
35 | var configuration = builder.Build();
36 |
37 | Assert.Equal("value1", configuration["key1"]);
38 |
39 | const string newValue = "newValue1";
40 | configSettings["key1"] = newValue;
41 | var versionNumber = await CreateNewHostedConfig(applicationId, configProfileId, configSettings);
42 | await PerformDeploymentAsync(applicationId, environmentId, configProfileId, versionNumber);
43 |
44 | for(int i = 0; i < 10; i++)
45 | {
46 | // Wait for ConfigProvider to perform the reload
47 | await Task.Delay(TimeSpan.FromSeconds(10));
48 | var value = configuration["key1"];
49 | if(string.Equals(newValue, value))
50 | {
51 | break;
52 | }
53 | }
54 |
55 | Assert.Equal(newValue, configuration["key1"]);
56 | }
57 | finally
58 | {
59 | await CleanupAppConfigResourcesAsync(applicationId, environmentId, configProfileId);
60 | }
61 | }
62 |
63 | [Fact]
64 | public async Task JsonWithCharsetConfiguration()
65 | {
66 | var configSettings = new Dictionary
67 | {
68 | { "key1", "value1" },
69 | { "key2", "value2" }
70 | };
71 | (string applicationId, string environmentId, string configProfileId) = await CreateAppConfigResourcesAsync("JsonWithCharsetConfiguration", configSettings, "application/json; charset=utf-8");
72 | try
73 | {
74 | var builder = new ConfigurationBuilder()
75 | .AddAppConfig(applicationId, environmentId, configProfileId, new AWSOptions { Region = RegionEndpoint.USWest2 }, TimeSpan.FromSeconds(5));
76 |
77 | var configuration = builder.Build();
78 |
79 | Assert.Equal("value1", configuration["key1"]);
80 | }
81 | finally
82 | {
83 | await CleanupAppConfigResourcesAsync(applicationId, environmentId, configProfileId);
84 | }
85 | }
86 |
87 | [Fact]
88 | public async Task AppConfigWithOctetStreamContentType()
89 | {
90 | var configSettings = new Dictionary
91 | {
92 | { "key1", "value1" },
93 | { "key2", "value2" },
94 | { "database:connectionString", "server=localhost;database=test" },
95 | { "api:timeout", "30" }
96 | };
97 |
98 | // Create AppConfig with application/octet-stream content type (simulates Parameter Store backend)
99 | (string applicationId, string environmentId, string configProfileId) =
100 | await CreateAppConfigResourcesAsync("OctetStreamTest", configSettings, "application/octet-stream");
101 |
102 | try
103 | {
104 | var builder = new ConfigurationBuilder()
105 | .AddAppConfig(applicationId, environmentId, configProfileId,
106 | new AWSOptions { Region = RegionEndpoint.USWest2 },
107 | TimeSpan.FromSeconds(5));
108 |
109 | var configuration = builder.Build();
110 |
111 | // Verify configuration loads correctly despite application/octet-stream content type
112 | Assert.Equal("value1", configuration["key1"]);
113 | Assert.Equal("value2", configuration["key2"]);
114 | Assert.Equal("server=localhost;database=test", configuration["database:connectionString"]);
115 | Assert.Equal("30", configuration["api:timeout"]);
116 | }
117 | finally
118 | {
119 | await CleanupAppConfigResourcesAsync(applicationId, environmentId, configProfileId);
120 | }
121 | }
122 |
123 | private async Task CleanupAppConfigResourcesAsync(string applicationId, string environmentId, string configProfileId)
124 | {
125 | await _appConfigClient.DeleteEnvironmentAsync(new DeleteEnvironmentRequest {ApplicationId = applicationId, EnvironmentId = environmentId });
126 |
127 | var listHostConfigResponse = await _appConfigClient.ListHostedConfigurationVersionsAsync(new ListHostedConfigurationVersionsRequest
128 | {
129 | ApplicationId = applicationId,
130 | ConfigurationProfileId = configProfileId
131 | });
132 |
133 | foreach(var item in listHostConfigResponse.Items)
134 | {
135 | await _appConfigClient.DeleteHostedConfigurationVersionAsync(new DeleteHostedConfigurationVersionRequest
136 | {
137 | ApplicationId = item.ApplicationId,
138 | ConfigurationProfileId = item.ConfigurationProfileId,
139 | VersionNumber = item.VersionNumber
140 | });
141 | }
142 |
143 | await _appConfigClient.DeleteConfigurationProfileAsync(new DeleteConfigurationProfileRequest
144 | {
145 | ApplicationId = applicationId,
146 | ConfigurationProfileId = configProfileId
147 | });
148 |
149 | await _appConfigClient.DeleteApplicationAsync(new DeleteApplicationRequest {ApplicationId = applicationId });
150 | }
151 |
152 |
153 | private async Task<(string applicationId, string environmentId, string configProfileId)> CreateAppConfigResourcesAsync(string seedName, IDictionary configs, string contentType = "application/json")
154 | {
155 | var nameSuffix = DateTime.Now.Ticks;
156 |
157 | var createAppResponse = await _appConfigClient.CreateApplicationAsync(new CreateApplicationRequest { Name = seedName + "-" + nameSuffix });
158 |
159 | var createConfigResponse = await _appConfigClient.CreateConfigurationProfileAsync(new CreateConfigurationProfileRequest
160 | {
161 | ApplicationId = createAppResponse.Id,
162 | Name = seedName + "-" + nameSuffix,
163 | LocationUri = "hosted"
164 | });
165 |
166 | var createEnvironmentResponse = await _appConfigClient.CreateEnvironmentAsync(new CreateEnvironmentRequest
167 | {
168 | ApplicationId = createAppResponse.Id,
169 | Name = seedName + "-" + nameSuffix
170 | });
171 |
172 | var versionNumber = await CreateNewHostedConfig(createAppResponse.Id, createConfigResponse.Id, configs, contentType);
173 | await PerformDeploymentAsync(createAppResponse.Id, createEnvironmentResponse.Id, createConfigResponse.Id, versionNumber);
174 |
175 | return (createAppResponse.Id, createEnvironmentResponse.Id, createConfigResponse.Id);
176 | }
177 |
178 | private async Task CreateNewHostedConfig(string applicationId, string configProfileId, IDictionary configs, string contentType = "application/json")
179 | {
180 | var json = JsonSerializer.Serialize(configs);
181 |
182 | var createHostedresponse = await _appConfigClient.CreateHostedConfigurationVersionAsync(new CreateHostedConfigurationVersionRequest
183 | {
184 | ApplicationId = applicationId,
185 | ConfigurationProfileId = configProfileId,
186 | ContentType = contentType,
187 | Content = new MemoryStream(UTF8Encoding.UTF8.GetBytes(json))
188 | });
189 |
190 | return createHostedresponse.VersionNumber.ToString();
191 | }
192 |
193 | private async Task PerformDeploymentAsync(string applicationId, string environmentId, string configProfileId, string configVersionNumber, bool waitForDeployment = true)
194 | {
195 | var deploymentStrategyId = await GetDeploymentStrategyId();
196 | var deploymentResponse = await _appConfigClient.StartDeploymentAsync(new StartDeploymentRequest
197 | {
198 | ApplicationId = applicationId,
199 | EnvironmentId = environmentId,
200 | ConfigurationProfileId = configProfileId,
201 | ConfigurationVersion = configVersionNumber,
202 | DeploymentStrategyId = deploymentStrategyId
203 | });
204 |
205 | if(waitForDeployment)
206 | {
207 | await WaitForDeploymentAsync(applicationId, environmentId);
208 | }
209 | }
210 |
211 | private async Task WaitForDeploymentAsync(string applicationId, string environmentId)
212 | {
213 | var getRequest = new GetEnvironmentRequest {ApplicationId = applicationId, EnvironmentId = environmentId };
214 | GetEnvironmentResponse getResponse;
215 | do
216 | {
217 | await Task.Delay(2000);
218 | getResponse = await _appConfigClient.GetEnvironmentAsync(getRequest);
219 | } while (getResponse.State == EnvironmentState.DEPLOYING);
220 | }
221 |
222 | private async Task GetDeploymentStrategyId()
223 | {
224 | const string integTestDeploymentStrategyName = "IntegTestFast";
225 |
226 | var paginator = _appConfigClient.Paginators.ListDeploymentStrategies(new ListDeploymentStrategiesRequest());
227 | await foreach(var response in paginator.Responses)
228 | {
229 | var strategy = response.Items.FirstOrDefault(x => string.Equals(x.Name, integTestDeploymentStrategyName));
230 |
231 | if (strategy != null)
232 | {
233 | return strategy.Id;
234 | }
235 | }
236 |
237 | var createResponse = await _appConfigClient.CreateDeploymentStrategyAsync(new CreateDeploymentStrategyRequest
238 | {
239 | Name = integTestDeploymentStrategyName,
240 | DeploymentDurationInMinutes = 1,
241 | FinalBakeTimeInMinutes = 0,
242 | GrowthFactor = 100,
243 | ReplicateTo = ReplicateTo.NONE
244 | });
245 |
246 | return createResponse.Id;
247 | }
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/src/Amazon.Extensions.Configuration.SystemsManager/SystemsManagerExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | using System;
17 | using Amazon.Extensions.Configuration.SystemsManager;
18 | using Amazon.Extensions.Configuration.SystemsManager.Internal;
19 | using Amazon.Extensions.NETCore.Setup;
20 |
21 | // ReSharper disable once CheckNamespace
22 | namespace Microsoft.Extensions.Configuration
23 | {
24 | ///
25 | /// Extension methods for registering with .
26 | ///
27 | public static class SystemsManagerExtensions
28 | {
29 | ///
30 | /// Adds an that reads configuration values from AWS Systems Manager Parameter Store with a specified path.
31 | ///
32 | /// The to add to.
33 | /// used to create an AWS Systems Manager Client connection
34 | /// The path that variable names must start with. The path will be removed from the variable names.
35 | /// Whether the AWS Systems Manager Parameters are optional.
36 | /// Initiate reload after TimeSpan
37 | /// cannot be null
38 | /// cannot be null
39 | /// The .
40 | public static IConfigurationBuilder AddSystemsManager(this IConfigurationBuilder builder, string path, AWSOptions awsOptions, bool optional, TimeSpan reloadAfter)
41 | {
42 | if (path == null) throw new ArgumentNullException(nameof(path));
43 | if (awsOptions == null) throw new ArgumentNullException(nameof(awsOptions));
44 |
45 | return builder.AddSystemsManager(ConfigureSource(path, awsOptions, optional, reloadAfter));
46 | }
47 |
48 | ///
49 | /// Adds an that reads configuration values from AWS Systems Manager Parameter Store with a specified path.
50 | ///
51 | /// The to add to.
52 | /// used to create an AWS Systems Manager Client connection
53 | /// The path that variable names must start with. The path will be removed from the variable names.
54 | /// Whether the AWS Systems Manager Parameters are optional.
55 | /// cannot be null
56 | /// cannot be null
57 | /// The .
58 | public static IConfigurationBuilder AddSystemsManager(this IConfigurationBuilder builder, string path, AWSOptions awsOptions, bool optional)
59 | {
60 | if (path == null) throw new ArgumentNullException(nameof(path));
61 | if (awsOptions == null) throw new ArgumentNullException(nameof(awsOptions));
62 |
63 | return builder.AddSystemsManager(ConfigureSource(path, awsOptions, optional));
64 | }
65 |
66 | ///
67 | /// Adds an that reads configuration values from AWS Systems Manager Parameter Store with a specified path.
68 | ///
69 | /// The to add to.
70 | /// used to create an AWS Systems Manager Client connection
71 | /// The path that variable names must start with. The path will be removed from the variable names.
72 | /// Initiate reload after TimeSpan
73 | /// cannot be null
74 | /// cannot be null
75 | /// The .
76 | public static IConfigurationBuilder AddSystemsManager(this IConfigurationBuilder builder, string path, AWSOptions awsOptions, TimeSpan reloadAfter)
77 | {
78 | if (path == null) throw new ArgumentNullException(nameof(path));
79 | if (awsOptions == null) throw new ArgumentNullException(nameof(awsOptions));
80 |
81 | return builder.AddSystemsManager(ConfigureSource(path, awsOptions, reloadAfter: reloadAfter));
82 | }
83 | ///
84 | /// Adds an that reads configuration values from AWS Systems Manager Parameter Store with a specified path.
85 | ///
86 | /// The to add to.
87 | /// used to create an AWS Systems Manager Client connection
88 | /// The path that variable names must start with. The path will be removed from the variable names.
89 | /// cannot be null
90 | /// cannot be null
91 | /// The .
92 | public static IConfigurationBuilder AddSystemsManager(this IConfigurationBuilder builder, string path, AWSOptions awsOptions)
93 | {
94 | if (path == null) throw new ArgumentNullException(nameof(path));
95 | if (awsOptions == null) throw new ArgumentNullException(nameof(awsOptions));
96 |
97 | return builder.AddSystemsManager(ConfigureSource(path, awsOptions));
98 | }
99 |
100 | ///
101 | /// Adds an that reads configuration values from AWS Systems Manager Parameter Store with a specified path.
102 | ///
103 | /// The to add to.
104 | /// The path that variable names must start with. The path will be removed from the variable names.
105 | /// Whether the AWS Systems Manager Parameters are optional.
106 | /// Initiate reload after TimeSpan
107 | /// cannot be null
108 | /// The .
109 | public static IConfigurationBuilder AddSystemsManager(this IConfigurationBuilder builder, string path, bool optional, TimeSpan reloadAfter)
110 | {
111 | if (path == null) throw new ArgumentNullException(nameof(path));
112 |
113 | return builder.AddSystemsManager(ConfigureSource(path, null, optional, reloadAfter));
114 | }
115 |
116 | ///
117 | /// Adds an that reads configuration values from AWS Systems Manager Parameter Store with a specified path.
118 | ///
119 | /// The to add to.
120 | /// The path that variable names must start with. The path will be removed from the variable names.
121 | /// Whether the AWS Systems Manager Parameters are optional.
122 | /// cannot be null
123 | /// The .
124 | public static IConfigurationBuilder AddSystemsManager(this IConfigurationBuilder builder, string path, bool optional)
125 | {
126 | if (path == null) throw new ArgumentNullException(nameof(path));
127 |
128 | return builder.AddSystemsManager(ConfigureSource(path, null, optional));
129 | }
130 |
131 | ///
132 | /// Adds an that reads configuration values from AWS Systems Manager Parameter Store with a specified path.
133 | ///
134 | /// The to add to.
135 | /// The path that variable names must start with. The path will be removed from the variable names.
136 | /// Initiate reload after TimeSpan
137 | /// cannot be null
138 | /// The .
139 | public static IConfigurationBuilder AddSystemsManager(this IConfigurationBuilder builder, string path, TimeSpan reloadAfter)
140 | {
141 | if (path == null) throw new ArgumentNullException(nameof(path));
142 |
143 | return builder.AddSystemsManager(ConfigureSource(path, null, reloadAfter: reloadAfter));
144 | }
145 |
146 | ///
147 | /// Adds an that reads configuration values from AWS Systems Manager Parameter Store with a specified path.
148 | ///
149 | /// The to add to.
150 | /// The path that variable names must start with. The path will be removed from the variable names.
151 | /// cannot be null
152 | /// The .
153 | public static IConfigurationBuilder AddSystemsManager(this IConfigurationBuilder builder, string path)
154 | {
155 | if (path == null) throw new ArgumentNullException(nameof(path));
156 |
157 | return builder.AddSystemsManager(ConfigureSource(path, null));
158 | }
159 |
160 | ///
161 | /// Adds an that reads configuration values from AWS Systems Manager Parameter variables with a specified path.
162 | ///
163 | /// The to add to.
164 | /// Configures the source.
165 | /// cannot be null
166 | /// . cannot be null
167 | /// The .
168 | public static IConfigurationBuilder AddSystemsManager(this IConfigurationBuilder builder, Action configureSource)
169 | {
170 | if (configureSource == null) throw new ArgumentNullException(nameof(configureSource));
171 |
172 | var source = new SystemsManagerConfigurationSource();
173 | configureSource(source);
174 |
175 | if (string.IsNullOrWhiteSpace(source.Path)) throw new ArgumentNullException(nameof(source.Path));
176 | if (source.AwsOptions != null) return builder.Add(source);
177 |
178 | source.AwsOptions = AwsOptionsProvider.GetAwsOptions(builder);
179 | return builder.Add(source);
180 | }
181 |
182 | private static Action ConfigureSource(string path, AWSOptions awsOptions, bool optional = false, TimeSpan? reloadAfter = null)
183 | {
184 | return configurationSource =>
185 | {
186 | configurationSource.Path = path;
187 | configurationSource.AwsOptions = awsOptions;
188 | configurationSource.Optional = optional;
189 | configurationSource.ReloadAfter = reloadAfter;
190 | };
191 | }
192 | }
193 | }
--------------------------------------------------------------------------------