Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add azure app configuration module #1200

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ActiveMq", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ArangoDb", "src\Testcontainers.ArangoDb\Testcontainers.ArangoDb.csproj", "{AB9C1563-07C7-4685-BACD-BB1FF64B3611}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.AzureAppConfiguration", "src\Testcontainers.AzureAppConfiguration\Testcontainers.AzureAppConfiguration.csproj", "{5C03BAF1-8CC7-421D-A457-9FBEF923E06E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Azurite", "src\Testcontainers.Azurite\Testcontainers.Azurite.csproj", "{3F2E254F-C203-43FD-A078-DC3E2CBC0F9F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.BigQuery", "src\Testcontainers.BigQuery\Testcontainers.BigQuery.csproj", "{A9FF9C7F-BBA0-4B44-90B7-48A60F9E00F3}"
Expand Down Expand Up @@ -105,6 +107,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ActiveMq.Tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ArangoDb.Tests", "tests\Testcontainers.ArangoDb.Tests\Testcontainers.ArangoDb.Tests.csproj", "{8E1E0A6D-EEBB-4455-B8E8-A55AF9B2062C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.AzureAppConfiguration.Tests", "tests\Testcontainers.AzureAppConfiguration.Tests\Testcontainers.AzureAppConfiguration.Tests.csproj", "{A7C87D4E-540F-4AD2-8A42-C7F86700688D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Azurite.Tests", "tests\Testcontainers.Azurite.Tests\Testcontainers.Azurite.Tests.csproj", "{B272FDDE-5E01-425D-B9E1-10FF883DDAAA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.BigQuery.Tests", "tests\Testcontainers.BigQuery.Tests\Testcontainers.BigQuery.Tests.csproj", "{03E60673-078A-4508-99AD-8537CE6F78F1}"
Expand Down Expand Up @@ -580,6 +584,14 @@ Global
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU
{5C03BAF1-8CC7-421D-A457-9FBEF923E06E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5C03BAF1-8CC7-421D-A457-9FBEF923E06E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C03BAF1-8CC7-421D-A457-9FBEF923E06E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C03BAF1-8CC7-421D-A457-9FBEF923E06E}.Release|Any CPU.Build.0 = Release|Any CPU
{A7C87D4E-540F-4AD2-8A42-C7F86700688D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7C87D4E-540F-4AD2-8A42-C7F86700688D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7C87D4E-540F-4AD2-8A42-C7F86700688D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7C87D4E-540F-4AD2-8A42-C7F86700688D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
Expand Down Expand Up @@ -675,5 +687,7 @@ Global
{1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{5C03BAF1-8CC7-421D-A457-9FBEF923E06E} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{A7C87D4E-540F-4AD2-8A42-C7F86700688D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions src/Testcontainers.AzureAppConfiguration/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
namespace Testcontainers.AzureAppConfiguration;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class AzureAppConfigurationBuilder : ContainerBuilder<AzureAppConfigurationBuilder, AzureAppConfigurationContainer, AzureAppConfigurationConfiguration>
{
public const string AzureAppConfigurationImage = "tnc1997/azure-app-configuration-emulator:1.0";

public const ushort AzureAppConfigurationPort = 8080;

public const string DefaultCredential = "abcd";

public const string DefaultSecret = "c2VjcmV0";

/// <summary>
/// Initializes a new instance of the <see cref="AzureAppConfigurationBuilder" /> class.
/// </summary>
public AzureAppConfigurationBuilder()
: this(new AzureAppConfigurationConfiguration())
{
DockerResourceConfiguration = Init().DockerResourceConfiguration;
}

/// <summary>
/// Initializes a new instance of the <see cref="AzureAppConfigurationBuilder" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
private AzureAppConfigurationBuilder(AzureAppConfigurationConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
DockerResourceConfiguration = resourceConfiguration;
}

/// <inheritdoc />
protected override AzureAppConfigurationConfiguration DockerResourceConfiguration { get; }

/// <summary>
/// Sets the Azure App Configuration credential.
/// </summary>
/// <param name="credential">The Azure App Configuration credential.</param>
/// <returns>A configured instance of <see cref="AzureAppConfigurationBuilder" />.</returns>
public AzureAppConfigurationBuilder WithCredential(string credential)
{
return Merge(DockerResourceConfiguration, new AzureAppConfigurationConfiguration(credential: credential))
.WithEnvironment("Authentication__Schemes__Hmac__Credential", credential);
}

/// <summary>
/// Sets the Azure App Configuration secret.
/// </summary>
/// <param name="secret">The Azure App Configuration secret.</param>
/// <returns>A configured instance of <see cref="AzureAppConfigurationBuilder" />.</returns>
public AzureAppConfigurationBuilder WithSecret(string secret)
{
return Merge(DockerResourceConfiguration, new AzureAppConfigurationConfiguration(secret: secret))
.WithEnvironment("Authentication__Schemes__Hmac__Secret", secret);
}

/// <inheritdoc />
public override AzureAppConfigurationContainer Build()
{
Validate();
return new AzureAppConfigurationContainer(DockerResourceConfiguration);
}

/// <inheritdoc />
protected override AzureAppConfigurationBuilder Init()
{
return base.Init()
.WithImage(AzureAppConfigurationImage)
.WithPortBinding(AzureAppConfigurationPort, true)
.WithCredential(DefaultCredential)
.WithSecret(DefaultSecret)
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("Now listening on"));
}

/// <inheritdoc />
protected override void Validate()
{
base.Validate();

_ = Guard.Argument(DockerResourceConfiguration.Credential, nameof(DockerResourceConfiguration.Credential))
.NotNull()
.NotEmpty();

_ = Guard.Argument(DockerResourceConfiguration.Secret, nameof(DockerResourceConfiguration.Secret))
.NotNull()
.NotEmpty();
}

/// <inheritdoc />
protected override AzureAppConfigurationBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new AzureAppConfigurationConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override AzureAppConfigurationBuilder Clone(IContainerConfiguration resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new AzureAppConfigurationConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override AzureAppConfigurationBuilder Merge(AzureAppConfigurationConfiguration oldValue, AzureAppConfigurationConfiguration newValue)
{
return new AzureAppConfigurationBuilder(new AzureAppConfigurationConfiguration(oldValue, newValue));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace Testcontainers.AzureAppConfiguration;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class AzureAppConfigurationConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="AzureAppConfigurationConfiguration" /> class.
/// </summary>
/// <param name="credential">The Azure App Configuration credential.</param>
/// <param name="secret">The Azure App Configuration secret.</param>
public AzureAppConfigurationConfiguration(string credential = null, string secret = null)
{
Credential = credential;
Secret = secret;
}

/// <summary>
/// Initializes a new instance of the <see cref="AzureAppConfigurationConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public AzureAppConfigurationConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="AzureAppConfigurationConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public AzureAppConfigurationConfiguration(IContainerConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="AzureAppConfigurationConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public AzureAppConfigurationConfiguration(AzureAppConfigurationConfiguration resourceConfiguration)
: this(new AzureAppConfigurationConfiguration(), resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="AzureAppConfigurationConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public AzureAppConfigurationConfiguration(AzureAppConfigurationConfiguration oldValue, AzureAppConfigurationConfiguration newValue)
: base(oldValue, newValue)
{
Credential = BuildConfiguration.Combine(oldValue.Credential, newValue.Credential);
Secret = BuildConfiguration.Combine(oldValue.Secret, newValue.Secret);
}

/// <summary>
/// Gets the Azure App Configuration credential.
/// </summary>
public string Credential { get; }

/// <summary>
/// Gets the Azure App Configuration secret.
/// </summary>
public string Secret { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Testcontainers.AzureAppConfiguration;

/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class AzureAppConfigurationContainer : DockerContainer
{
private readonly AzureAppConfigurationConfiguration _configuration;

/// <summary>
/// Initializes a new instance of the <see cref="AzureAppConfigurationContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
public AzureAppConfigurationContainer(AzureAppConfigurationConfiguration configuration)
: base(configuration)
{
_configuration = configuration;
}

/// <summary>
/// Gets the Azure App Configuration connection string.
/// </summary>
/// <returns>The Azure App Configuration connection string.</returns>
public string GetConnectionString()
{
var properties = new Dictionary<string, string>();
properties.Add("Endpoint", new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(AzureAppConfigurationBuilder.AzureAppConfigurationPort)).ToString());
properties.Add("Id", _configuration.Credential);
properties.Add("Secret", _configuration.Secret);
return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net8.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" VersionOverride="2023.3.0" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Testcontainers/Testcontainers.csproj"/>
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions src/Testcontainers.AzureAppConfiguration/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using Docker.DotNet.Models;
global using DotNet.Testcontainers;
global using DotNet.Testcontainers.Builders;
global using DotNet.Testcontainers.Configurations;
global using DotNet.Testcontainers.Containers;
global using JetBrains.Annotations;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Testcontainers.AzureAppConfiguration;

public sealed class AzureAppConfigurationContainerTest : IAsyncLifetime
{
private readonly AzureAppConfigurationContainer _azureAppConfigurationContainer = new AzureAppConfigurationBuilder().Build();

public Task InitializeAsync()
{
return _azureAppConfigurationContainer.StartAsync();
}

public Task DisposeAsync()
{
return _azureAppConfigurationContainer.DisposeAsync().AsTask();
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task GetConfigurationSettingReturnsSetConfigurationSetting()
{
// Given
var client = new ConfigurationClient(_azureAppConfigurationContainer.GetConnectionString());

// When
await client.SetConfigurationSettingAsync(nameof(ConfigurationSetting.Key), nameof(ConfigurationSetting.Value))
.ConfigureAwait(true);

var response = await client.GetConfigurationSettingAsync(nameof(ConfigurationSetting.Key))
.ConfigureAwait(true);

// Then
Assert.Equal(nameof(ConfigurationSetting.Value), response.Value.Value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsPublishable>false</IsPublishable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="coverlet.collector"/>
<PackageReference Include="xunit.runner.visualstudio"/>
<PackageReference Include="xunit"/>
<PackageReference Include="Azure.Data.AppConfiguration" Version="1.4.1"/>
tnc1997 marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../src/Testcontainers.AzureAppConfiguration/Testcontainers.AzureAppConfiguration.csproj"/>
<ProjectReference Include="../Testcontainers.Commons/Testcontainers.Commons.csproj"/>
</ItemGroup>
</Project>
4 changes: 4 additions & 0 deletions tests/Testcontainers.AzureAppConfiguration.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
global using System.Threading.Tasks;
global using Azure.Data.AppConfiguration;
global using DotNet.Testcontainers.Commons;
global using Xunit;
Loading