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 Data Explorer Kusto emulator module #963

Merged
merged 21 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1c6701a
Implemented Azure Data Explorer Kusto emulator module
JonasBenz Jul 31, 2023
7c612b8
re-arrange solution file, so that Kusto project follow the order
JonasBenz Aug 2, 2023
44573b1
Added Unit Test for Kusto container
JonasBenz Aug 2, 2023
f8cedde
Fixed mistake in solution file.
JonasBenz Aug 2, 2023
889d32a
Umcommented documentation of KustoBuilder.DockerResourceConfiguration
JonasBenz Aug 2, 2023
b87c836
Merge branch 'develop' into kustainer
JonasBenz Aug 2, 2023
0a2dba4
Used UriBuilder to build connection string
JonasBenz Aug 3, 2023
4ff5f5c
Moved all usings to global Usings.cs
JonasBenz Aug 3, 2023
23f3c75
Extended HttpWaitStrategy with possibilty to provide the content (bod…
JonasBenz Aug 3, 2023
ad9635f
Replaced custom WaitStrategy with HttpWaitStrategy
JonasBenz Aug 3, 2023
6f7f28a
chore: Minor cleanup (apply project wide formatting styles)
HofmeisterAn Aug 3, 2023
2da80f5
fix: Suggest different wait strategy (parse console output)
HofmeisterAn Aug 3, 2023
ca78500
chore: Order usings
HofmeisterAn Aug 3, 2023
d004366
chore: Align namespace
HofmeisterAn Aug 6, 2023
09b0b06
HttpWaitStrategy.WithContent: Made sure to handle disposal of HttpCon…
JonasBenz Aug 7, 2023
1e18fde
KustoBuilder: Switched to HttpWaitStrategy again (with new contentFac…
JonasBenz Aug 7, 2023
55018dc
HttpWaitStrategy: Prevent null reference ex, when _contentFactory is …
JonasBenz Aug 7, 2023
244a873
HttpWaitStrategy: Initialized _contentFactory in constructor
JonasBenz Aug 7, 2023
bc84c72
Merge branch 'kustainer' of github.com:JonasBenz/testcontainers-dotne…
HofmeisterAn Aug 7, 2023
4e5ed89
chore: Remove unnecessary using: use default encoding
HofmeisterAn Aug 7, 2023
b665b1f
chore: Rename _contentFactory to _httpContentCallback
HofmeisterAn Aug 7, 2023
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 @@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Kafka", "src
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Keycloak", "src\Testcontainers.Keycloak\Testcontainers.Keycloak.csproj", "{AA8834A3-82A7-4E83-8E4C-88D37F74056A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Kusto", "src\Testcontainers.Kusto\Testcontainers.Kusto.csproj", "{FCF59758-2403-4EC9-9EAE-4EC69A3F27AF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.LocalStack", "src\Testcontainers.LocalStack\Testcontainers.LocalStack.csproj", "{3792268A-EF08-4569-8118-991E08FD61C4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MariaDb", "src\Testcontainers.MariaDb\Testcontainers.MariaDb.csproj", "{4B204EB3-C478-422E-9B6F-62DF3871291A}"
Expand Down Expand Up @@ -91,6 +93,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Kafka.Tests"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Keycloak.Tests", "tests\Testcontainers.Keycloak.Tests\Testcontainers.Keycloak.Tests.csproj", "{4827D606-89D5-4E00-8341-47A6E95817BA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Kusto.Tests", "tests\Testcontainers.Kusto.Tests\Testcontainers.Kusto.Tests.csproj", "{FA59D75A-8D3A-412C-92E6-4A56033162DD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.LocalStack.Tests", "tests\Testcontainers.LocalStack.Tests\Testcontainers.LocalStack.Tests.csproj", "{728CBE16-1D52-4F84-AF01-7229E6013512}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MariaDb.Tests", "tests\Testcontainers.MariaDb.Tests\Testcontainers.MariaDb.Tests.csproj", "{7F0AE083-9DB8-4BD4-91F7-C199DCC7301D}"
Expand Down Expand Up @@ -182,6 +186,10 @@ Global
{AA8834A3-82A7-4E83-8E4C-88D37F74056A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA8834A3-82A7-4E83-8E4C-88D37F74056A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA8834A3-82A7-4E83-8E4C-88D37F74056A}.Release|Any CPU.Build.0 = Release|Any CPU
{FCF59758-2403-4EC9-9EAE-4EC69A3F27AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FCF59758-2403-4EC9-9EAE-4EC69A3F27AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FCF59758-2403-4EC9-9EAE-4EC69A3F27AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FCF59758-2403-4EC9-9EAE-4EC69A3F27AF}.Release|Any CPU.Build.0 = Release|Any CPU
{3792268A-EF08-4569-8118-991E08FD61C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3792268A-EF08-4569-8118-991E08FD61C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3792268A-EF08-4569-8118-991E08FD61C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -294,6 +302,10 @@ Global
{4827D606-89D5-4E00-8341-47A6E95817BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4827D606-89D5-4E00-8341-47A6E95817BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4827D606-89D5-4E00-8341-47A6E95817BA}.Release|Any CPU.Build.0 = Release|Any CPU
{FA59D75A-8D3A-412C-92E6-4A56033162DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA59D75A-8D3A-412C-92E6-4A56033162DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA59D75A-8D3A-412C-92E6-4A56033162DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA59D75A-8D3A-412C-92E6-4A56033162DD}.Release|Any CPU.Build.0 = Release|Any CPU
{728CBE16-1D52-4F84-AF01-7229E6013512}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{728CBE16-1D52-4F84-AF01-7229E6013512}.Debug|Any CPU.Build.0 = Debug|Any CPU
{728CBE16-1D52-4F84-AF01-7229E6013512}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -383,6 +395,7 @@ Global
{111B840F-9DB0-4166-83E6-0580FD418F07} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{E93E40CE-59AA-4561-9B4C-E7B0A686326E} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{AA8834A3-82A7-4E83-8E4C-88D37F74056A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{FCF59758-2403-4EC9-9EAE-4EC69A3F27AF} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{3792268A-EF08-4569-8118-991E08FD61C4} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{4B204EB3-C478-422E-9B6F-62DF3871291A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{1266E1E6-5CEF-4161-8B45-83282455746E} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
Expand Down Expand Up @@ -411,6 +424,7 @@ Global
{F0F40AE2-70FF-4191-ADDA-26A19E0D1A0F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{6F2AEE03-629A-4B49-BD5B-25CA3C61FFB7} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{4827D606-89D5-4E00-8341-47A6E95817BA} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{FA59D75A-8D3A-412C-92E6-4A56033162DD} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{728CBE16-1D52-4F84-AF01-7229E6013512} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{7F0AE083-9DB8-4BD4-91F7-C199DCC7301D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{5DB1F35F-B714-4B62-84BE-16A33084D3E1} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.Kusto/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
75 changes: 75 additions & 0 deletions src/Testcontainers.Kusto/KustoBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
namespace Testcontainers.Kusto;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
/// <remarks>
/// Builds a container running the Azure Data Explorer Kusto emulator:
/// https://learn.microsoft.com/azure/data-explorer/kusto-emulator-overview.
/// </remarks>
[PublicAPI]
public sealed class KustoBuilder : ContainerBuilder<KustoBuilder, KustoContainer, KustoConfiguration>
{
public const string KustoImage = "mcr.microsoft.com/azuredataexplorer/kustainer-linux:latest";

public const ushort KustoPort = 8080;

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

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

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

/// <inheritdoc />
public override KustoContainer Build()
{
Validate();
return new KustoContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);
}

/// <inheritdoc />
protected override KustoBuilder Init()
{
return base.Init()
.WithImage(KustoImage)
.WithPortBinding(KustoPort, true)
.WithEnvironment("ACCEPT_EULA", "Y")
.WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => request
.WithMethod(HttpMethod.Post)
.ForPort(KustoPort)
.ForPath("/v1/rest/mgmt")
.WithContent(() => new StringContent("{\"csl\":\".show cluster\"}", Encoding.UTF8, "application/json"))));
}

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

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

/// <inheritdoc />
protected override KustoBuilder Merge(KustoConfiguration oldValue, KustoConfiguration newValue)
{
return new KustoBuilder(new KustoConfiguration(oldValue, newValue));
}
}
53 changes: 53 additions & 0 deletions src/Testcontainers.Kusto/KustoConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace Testcontainers.Kusto;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class KustoConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="KustoConfiguration" /> class.
/// </summary>
public KustoConfiguration()
{
}

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

/// <summary>
/// Initializes a new instance of the <see cref="KustoConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public KustoConfiguration(KustoConfiguration oldValue, KustoConfiguration newValue)
: base(oldValue, newValue)
{
}
}
25 changes: 25 additions & 0 deletions src/Testcontainers.Kusto/KustoContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Testcontainers.Kusto;

/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class KustoContainer : DockerContainer
{
/// <summary>
/// Initializes a new instance of the <see cref="KustoContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
/// <param name="logger">The logger.</param>
public KustoContainer(KustoConfiguration configuration, ILogger logger)
: base(configuration, logger)
{
}

/// <summary>
/// Gets the Kusto connection string.
/// </summary>
/// <returns>The Kusto connection string.</returns>
public string GetConnectionString()
{
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(KustoBuilder.KustoPort)).ToString();
}
}
13 changes: 13 additions & 0 deletions src/Testcontainers.Kusto/Testcontainers.Kusto.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)src/Testcontainers/Testcontainers.csproj"/>
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions src/Testcontainers.Kusto/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
global using System;
global using System.Net.Http;
global using System.Text;
global using Docker.DotNet.Models;
global using DotNet.Testcontainers.Builders;
global using DotNet.Testcontainers.Configurations;
global using DotNet.Testcontainers.Containers;
global using JetBrains.Annotations;
global using Microsoft.Extensions.Logging;
111 changes: 65 additions & 46 deletions src/Testcontainers/Configurations/WaitStrategies/HttpWaitStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public sealed class HttpWaitStrategy : IWaitUntil

private HttpMessageHandler _httpMessageHandler;

private Func<HttpContent> _contentFactory = () => null;
HofmeisterAn marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Initializes a new instance of the <see cref="HttpWaitStrategy" /> class.
/// </summary>
Expand Down Expand Up @@ -75,51 +77,54 @@ public async Task<bool> UntilAsync(IContainer container)
httpRequestMessage.Headers.Add(httpHeader.Key, httpHeader.Value);
}

HttpResponseMessage httpResponseMessage;

try
{
httpResponseMessage = await httpClient.SendAsync(httpRequestMessage)
.ConfigureAwait(false);
}
catch (HttpRequestException)
{
return false;
}

Predicate<HttpStatusCode> predicate;

if (!_httpStatusCodes.Any() && _httpStatusCodePredicate == null)
using (httpRequestMessage.Content = _contentFactory())
HofmeisterAn marked this conversation as resolved.
Show resolved Hide resolved
{
predicate = statusCode => HttpStatusCode.OK.Equals(statusCode);
}
else if (_httpStatusCodes.Any() && _httpStatusCodePredicate == null)
{
predicate = statusCode => _httpStatusCodes.Contains(statusCode);
}
else if (_httpStatusCodes.Any())
{
predicate = statusCode => _httpStatusCodes.Contains(statusCode) || _httpStatusCodePredicate.Invoke(statusCode);
}
else
{
predicate = _httpStatusCodePredicate;
}

try
{
var responseMessagePredicate = await _httpResponseMessagePredicate.Invoke(httpResponseMessage)
.ConfigureAwait(false);

return responseMessagePredicate && predicate.Invoke(httpResponseMessage.StatusCode);
}
catch
{
return false;
}
finally
{
httpResponseMessage.Dispose();
HttpResponseMessage httpResponseMessage;

try
{
httpResponseMessage = await httpClient.SendAsync(httpRequestMessage)
.ConfigureAwait(false);
}
catch (HttpRequestException)
{
return false;
}

Predicate<HttpStatusCode> predicate;

if (!_httpStatusCodes.Any() && _httpStatusCodePredicate == null)
{
predicate = statusCode => HttpStatusCode.OK.Equals(statusCode);
}
else if (_httpStatusCodes.Any() && _httpStatusCodePredicate == null)
{
predicate = statusCode => _httpStatusCodes.Contains(statusCode);
}
else if (_httpStatusCodes.Any())
{
predicate = statusCode => _httpStatusCodes.Contains(statusCode) || _httpStatusCodePredicate.Invoke(statusCode);
}
else
{
predicate = _httpStatusCodePredicate;
}

try
{
var responseMessagePredicate = await _httpResponseMessagePredicate.Invoke(httpResponseMessage)
.ConfigureAwait(false);

return responseMessagePredicate && predicate.Invoke(httpResponseMessage.StatusCode);
}
catch
{
return false;
}
finally
{
httpResponseMessage.Dispose();
}
}
}
}
Expand Down Expand Up @@ -198,9 +203,9 @@ public HttpWaitStrategy UsingTls(bool tlsEnabled = true)
}

/// <summary>
/// Defines a custom <see cref="HttpMessageHandler"/> which should be used by the internal <see cref="HttpClient"/>.
/// Defines a custom <see cref="HttpMessageHandler" /> which should be used by the internal <see cref="HttpClient"/>.
/// </summary>
/// <param name="handler">The handler to pass to the <see cref="HttpClient"/> when it is created.</param>
/// <param name="handler">The handler to pass to the <see cref="HttpClient" /> when it is created.</param>
/// <returns>A configured instance of <see cref="HttpWaitStrategy" />.</returns>
public HttpWaitStrategy UsingHttpMessageHandler(HttpMessageHandler handler)
{
Expand Down Expand Up @@ -254,5 +259,19 @@ public HttpWaitStrategy WithHeaders(IReadOnlyDictionary<string, string> headers)
{
return headers.Aggregate(this, (httpWaitStrategy, header) => httpWaitStrategy.WithHeader(header.Key, header.Value));
}

/// <summary>
/// Sets the HTTP message body of the HTTP request.
/// </summary>
/// <param name="contentFactory">
/// A factory to create the desired content.
/// Make sure to provide a new instance on every call, because it will be disposed on retries.
/// </param>
/// <returns>A configured instance of <see cref="HttpWaitStrategy" />.</returns>
public HttpWaitStrategy WithContent(Func<HttpContent> contentFactory)
{
_contentFactory = contentFactory;
return this;
}
}
}
1 change: 1 addition & 0 deletions tests/Testcontainers.Kusto.Tests/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
Loading