Skip to content

Commit c396748

Browse files
feat: Add Grafana module (#1509)
Co-authored-by: Andre Hofmeister <[email protected]>
1 parent c56984f commit c396748

File tree

16 files changed

+406
-2
lines changed

16 files changed

+406
-2
lines changed

Testcontainers.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Firestore",
6262
EndProject
6363
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.GCloud", "src\Testcontainers.GCloud\Testcontainers.GCloud.csproj", "{D7CE8744-E58B-4277-BBB7-6840A4FF2049}"
6464
EndProject
65+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Grafana", "src\Testcontainers.Grafana\Testcontainers.Grafana.csproj", "{A2E5D4B5-7F2C-4E8D-9B3A-6C2D8F9E1A3B}"
66+
EndProject
6567
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.InfluxDb", "src\Testcontainers.InfluxDb\Testcontainers.InfluxDb.csproj", "{8F483B83-7BD4-4BD5-9F03-DFC26E1CE678}"
6668
EndProject
6769
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.JanusGraph", "src\Testcontainers.JanusGraph\Testcontainers.JanusGraph.csproj", "{C5AF86A8-2F11-41B6-BB01-325AD9016B94}"
@@ -180,6 +182,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.FirebirdSql.
180182
EndProject
181183
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Firestore.Tests", "tests\Testcontainers.Firestore.Tests\Testcontainers.Firestore.Tests.csproj", "{2F0D7CD6-7EA9-46FC-B8F2-25D55699525F}"
182184
EndProject
185+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Grafana.Tests", "tests\Testcontainers.Grafana.Tests\Testcontainers.Grafana.Tests.csproj", "{C3F5E8D2-9A1B-4F7E-8D6C-5B9A2E4F7C8D}"
186+
EndProject
183187
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.InfluxDb.Tests", "tests\Testcontainers.InfluxDb.Tests\Testcontainers.InfluxDb.Tests.csproj", "{B45B0EF2-5852-4ED3-904A-8FC62A3253D7}"
184188
EndProject
185189
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.JanusGraph.Tests", "tests\Testcontainers.JanusGraph.Tests\Testcontainers.JanusGraph.Tests.csproj", "{BEFC4109-4511-4FBD-AC4F-3D3B388B8CAD}"
@@ -358,6 +362,10 @@ Global
358362
{D7CE8744-E58B-4277-BBB7-6840A4FF2049}.Debug|Any CPU.Build.0 = Debug|Any CPU
359363
{D7CE8744-E58B-4277-BBB7-6840A4FF2049}.Release|Any CPU.ActiveCfg = Release|Any CPU
360364
{D7CE8744-E58B-4277-BBB7-6840A4FF2049}.Release|Any CPU.Build.0 = Release|Any CPU
365+
{A2E5D4B5-7F2C-4E8D-9B3A-6C2D8F9E1A3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
366+
{A2E5D4B5-7F2C-4E8D-9B3A-6C2D8F9E1A3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
367+
{A2E5D4B5-7F2C-4E8D-9B3A-6C2D8F9E1A3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
368+
{A2E5D4B5-7F2C-4E8D-9B3A-6C2D8F9E1A3B}.Release|Any CPU.Build.0 = Release|Any CPU
361369
{8F483B83-7BD4-4BD5-9F03-DFC26E1CE678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
362370
{8F483B83-7BD4-4BD5-9F03-DFC26E1CE678}.Debug|Any CPU.Build.0 = Debug|Any CPU
363371
{8F483B83-7BD4-4BD5-9F03-DFC26E1CE678}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -594,6 +602,10 @@ Global
594602
{2F0D7CD6-7EA9-46FC-B8F2-25D55699525F}.Debug|Any CPU.Build.0 = Debug|Any CPU
595603
{2F0D7CD6-7EA9-46FC-B8F2-25D55699525F}.Release|Any CPU.ActiveCfg = Release|Any CPU
596604
{2F0D7CD6-7EA9-46FC-B8F2-25D55699525F}.Release|Any CPU.Build.0 = Release|Any CPU
605+
{C3F5E8D2-9A1B-4F7E-8D6C-5B9A2E4F7C8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
606+
{C3F5E8D2-9A1B-4F7E-8D6C-5B9A2E4F7C8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
607+
{C3F5E8D2-9A1B-4F7E-8D6C-5B9A2E4F7C8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
608+
{C3F5E8D2-9A1B-4F7E-8D6C-5B9A2E4F7C8D}.Release|Any CPU.Build.0 = Release|Any CPU
597609
{B45B0EF2-5852-4ED3-904A-8FC62A3253D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
598610
{B45B0EF2-5852-4ED3-904A-8FC62A3253D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
599611
{B45B0EF2-5852-4ED3-904A-8FC62A3253D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -797,6 +809,7 @@ Global
797809
{31BAF2C4-0608-4C0F-845A-14FE7C0A1670} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
798810
{B3CC460D-0DFD-48A8-9502-54E9828B7B05} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
799811
{D7CE8744-E58B-4277-BBB7-6840A4FF2049} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
812+
{A2E5D4B5-7F2C-4E8D-9B3A-6C2D8F9E1A3B} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
800813
{8F483B83-7BD4-4BD5-9F03-DFC26E1CE678} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
801814
{C5AF86A8-2F11-41B6-BB01-325AD9016B94} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
802815
{111B840F-9DB0-4166-83E6-0580FD418F07} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
@@ -856,6 +869,7 @@ Global
856869
{9F27AA1B-C25D-400C-BCB0-6B0BF1A1DCEA} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
857870
{E39095AC-9B34-4178-A486-04C902B6FD33} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
858871
{2F0D7CD6-7EA9-46FC-B8F2-25D55699525F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
872+
{C3F5E8D2-9A1B-4F7E-8D6C-5B9A2E4F7C8D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
859873
{B45B0EF2-5852-4ED3-904A-8FC62A3253D7} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
860874
{BEFC4109-4511-4FBD-AC4F-3D3B388B8CAD} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
861875
{F0F40AE2-70FF-4191-ADDA-26A19E0D1A0F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}

docs/modules/grafana.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Grafana
2+
3+
[Grafana](https://grafana.com/) is an open-source platform for monitoring, visualization, and analytics. It allows you to query, visualize, alert on, and explore metrics, logs, and traces from various data sources through customizable dashboards.
4+
5+
Add the following dependency to your project file:
6+
7+
```shell title="NuGet"
8+
dotnet add package Testcontainers.Grafana
9+
```
10+
11+
You can start a Grafana container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations.
12+
13+
=== "Create Container Instance"
14+
```csharp
15+
--8<-- "tests/Testcontainers.Grafana.Tests/GrafanaContainerTest.cs:CreateGrafanaContainer"
16+
```
17+
18+
This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method.
19+
20+
=== "Usage Example"
21+
```csharp
22+
--8<-- "tests/Testcontainers.Grafana.Tests/GrafanaContainerTest.cs:UseGrafanaContainer"
23+
```
24+
25+
The test example queries the Grafana API endpoint `GET /api/org/` to retrieve the current organization. This API endpoint requires authentication, so the HTTP client's authorization header is set with Basic authentication using the username and password configured through the Grafana builder. The default configuration uses the username `admin` and password `admin`.
26+
27+
The test example uses the following NuGet dependencies:
28+
29+
=== "Package References"
30+
```xml
31+
--8<-- "tests/Testcontainers.Grafana.Tests/Testcontainers.Grafana.Tests.csproj:PackageReferences"
32+
```
33+
34+
To execute the tests, use the command `dotnet test` from a terminal.
35+
36+
--8<-- "docs/modules/_call_out_test_projects.txt"
37+
38+
## Enable anonymous access
39+
40+
Developers can enable anonymous access using the Grafana builder API `WithAnonymousAccessEnabled()`. This will enable anonymous access and no authentication is necessary to access Grafana:
41+
42+
```csharp
43+
GrafanaContainer _grafanaContainer = new GrafanaBuilder().WithAnonymousAccessEnabled().Build();
44+
```

docs/modules/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ await moduleNameContainer.StartAsync();
4242
| FakeGcsServer | `fsouza/fake-gcs-server:1.47` | [NuGet](https://www.nuget.org/packages/Testcontainers.FakeGcsServer) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.FakeGcsServer) |
4343
| Firebird | `jacobalberty/firebird:v4.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.FirebirdSql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.FirebirdSql) |
4444
| Firestore | `gcr.io/google.com/cloudsdktool/google-cloud-cli:446.0.1-emulators` | [NuGet](https://www.nuget.org/packages/Testcontainers.Firestore) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Firestore) |
45+
| Grafana | `grafana/grafana:12.2` | [NuGet](https://www.nuget.org/packages/Testcontainers.Grafana) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Grafana) |
4546
| InfluxDB | `influxdb:2.7` | [NuGet](https://www.nuget.org/packages/Testcontainers.InfluxDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.InfluxDb) |
4647
| JanusGraph | `janusgraph/janusgraph:1.0.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.JanusGraph) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.JanusGraph) |
4748
| K3s | `rancher/k3s:v1.26.2-k3s1` | [NuGet](https://www.nuget.org/packages/Testcontainers.K3s) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.K3s) |

docs/modules/pulsar.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ string pulsarServiceUrl = _pulsarContainer.GetHttpServiceUrl();
4848
If you need to use token authentication, use the following builder configuration to enable authentication:
4949

5050
```csharp
51-
PulsarContainer _pulsarContainer = PulsarBuilder().WithTokenAuthentication().Build();
51+
PulsarContainer _pulsarContainer = new PulsarBuilder().WithTokenAuthentication().Build();
5252
```
5353

5454
Start the container and obtain an authentication token with a specified expiration time
@@ -70,5 +70,5 @@ var authToken = await container.CreateAuthenticationTokenAsync(Timeout.InfiniteT
7070
If you need to use Pulsar Functions, use the following builder configuration to enable it:
7171

7272
```csharp
73-
PulsarContainer _pulsarContainer = PulsarBuilder().WithFunctions().Build();
73+
PulsarContainer _pulsarContainer = new PulsarBuilder().WithFunctions().Build();
7474
```

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ nav:
5656
- modules/clickhouse.md
5757
- modules/db2.md
5858
- modules/elasticsearch.md
59+
- modules/grafana.md
5960
- modules/mongodb.md
6061
- modules/mssql.md
6162
- modules/neo4j.md
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
root = true
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
namespace Testcontainers.Grafana;
2+
3+
/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
4+
[PublicAPI]
5+
public sealed class GrafanaBuilder : ContainerBuilder<GrafanaBuilder, GrafanaContainer, GrafanaConfiguration>
6+
{
7+
public const string GrafanaImage = "grafana/grafana:12.2";
8+
9+
public const ushort GrafanaPort = 3000;
10+
11+
public const string DefaultUsername = "admin";
12+
13+
public const string DefaultPassword = "admin";
14+
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="GrafanaBuilder" /> class.
17+
/// </summary>
18+
public GrafanaBuilder()
19+
: this(new GrafanaConfiguration())
20+
{
21+
DockerResourceConfiguration = Init().DockerResourceConfiguration;
22+
}
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="GrafanaBuilder" /> class.
26+
/// </summary>
27+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
28+
private GrafanaBuilder(GrafanaConfiguration resourceConfiguration)
29+
: base(resourceConfiguration)
30+
{
31+
DockerResourceConfiguration = resourceConfiguration;
32+
}
33+
34+
/// <inheritdoc />
35+
protected override GrafanaConfiguration DockerResourceConfiguration { get; }
36+
37+
/// <summary>
38+
/// Sets the Grafana username.
39+
/// </summary>
40+
/// <param name="username">The Grafana username.</param>
41+
/// <returns>A configured instance of <see cref="GrafanaBuilder" />.</returns>
42+
public GrafanaBuilder WithUsername(string username)
43+
{
44+
return Merge(DockerResourceConfiguration, new GrafanaConfiguration(username: username))
45+
.WithEnvironment("GF_SECURITY_ADMIN_USER", username);
46+
}
47+
48+
/// <summary>
49+
/// Sets the Grafana password.
50+
/// </summary>
51+
/// <param name="password">The Grafana password.</param>
52+
/// <returns>A configured instance of <see cref="GrafanaBuilder" />.</returns>
53+
public GrafanaBuilder WithPassword(string password)
54+
{
55+
return Merge(DockerResourceConfiguration, new GrafanaConfiguration(password: password))
56+
.WithEnvironment("GF_SECURITY_ADMIN_PASSWORD", password);
57+
}
58+
59+
/// <summary>
60+
/// Enables the anonymous access.
61+
/// </summary>
62+
/// <returns>A configured instance of <see cref="GrafanaBuilder" />.</returns>
63+
public GrafanaBuilder WithAnonymousAccessEnabled()
64+
{
65+
return WithEnvironment("GF_AUTH_ANONYMOUS_ENABLED", "true")
66+
.WithEnvironment("GF_AUTH_ANONYMOUS_ORG_ROLE", "Admin");
67+
}
68+
69+
/// <inheritdoc />
70+
public override GrafanaContainer Build()
71+
{
72+
Validate();
73+
return new GrafanaContainer(DockerResourceConfiguration);
74+
}
75+
76+
/// <inheritdoc />
77+
protected override GrafanaBuilder Init()
78+
{
79+
return base.Init()
80+
.WithImage(GrafanaImage)
81+
.WithPortBinding(GrafanaPort, true)
82+
.WithUsername(DefaultUsername)
83+
.WithPassword(DefaultPassword)
84+
.WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request =>
85+
request.ForPath("/api/health").ForPort(GrafanaPort)));
86+
}
87+
88+
/// <inheritdoc />
89+
protected override GrafanaBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
90+
{
91+
return Merge(DockerResourceConfiguration, new GrafanaConfiguration(resourceConfiguration));
92+
}
93+
94+
/// <inheritdoc />
95+
protected override GrafanaBuilder Clone(IContainerConfiguration resourceConfiguration)
96+
{
97+
return Merge(DockerResourceConfiguration, new GrafanaConfiguration(resourceConfiguration));
98+
}
99+
100+
/// <inheritdoc />
101+
protected override GrafanaBuilder Merge(GrafanaConfiguration oldValue, GrafanaConfiguration newValue)
102+
{
103+
return new GrafanaBuilder(new GrafanaConfiguration(oldValue, newValue));
104+
}
105+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
namespace Testcontainers.Grafana;
2+
3+
/// <inheritdoc cref="ContainerConfiguration" />
4+
[PublicAPI]
5+
public sealed class GrafanaConfiguration : ContainerConfiguration
6+
{
7+
/// <summary>
8+
/// Initializes a new instance of the <see cref="GrafanaConfiguration" /> class.
9+
/// </summary>
10+
/// <param name="username">The Grafana username.</param>
11+
/// <param name="password">The Grafana password.</param>
12+
public GrafanaConfiguration(
13+
string username = null,
14+
string password = null)
15+
{
16+
Username = username;
17+
Password = password;
18+
}
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="GrafanaConfiguration" /> class.
22+
/// </summary>
23+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
24+
public GrafanaConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
25+
: base(resourceConfiguration)
26+
{
27+
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
28+
}
29+
30+
/// <summary>
31+
/// Initializes a new instance of the <see cref="GrafanaConfiguration" /> class.
32+
/// </summary>
33+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
34+
public GrafanaConfiguration(IContainerConfiguration resourceConfiguration)
35+
: base(resourceConfiguration)
36+
{
37+
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
38+
}
39+
40+
/// <summary>
41+
/// Initializes a new instance of the <see cref="GrafanaConfiguration" /> class.
42+
/// </summary>
43+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
44+
public GrafanaConfiguration(GrafanaConfiguration resourceConfiguration)
45+
: this(new GrafanaConfiguration(), resourceConfiguration)
46+
{
47+
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
48+
}
49+
50+
/// <summary>
51+
/// Initializes a new instance of the <see cref="GrafanaConfiguration" /> class.
52+
/// </summary>
53+
/// <param name="oldValue">The old Docker resource configuration.</param>
54+
/// <param name="newValue">The new Docker resource configuration.</param>
55+
public GrafanaConfiguration(GrafanaConfiguration oldValue, GrafanaConfiguration newValue)
56+
: base(oldValue, newValue)
57+
{
58+
Username = BuildConfiguration.Combine(oldValue.Username, newValue.Username);
59+
Password = BuildConfiguration.Combine(oldValue.Password, newValue.Password);
60+
}
61+
62+
/// <summary>
63+
/// Gets the Grafana username.
64+
/// </summary>
65+
public string Username { get; }
66+
67+
/// <summary>
68+
/// Gets the Grafana password.
69+
/// </summary>
70+
public string Password { get; }
71+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Testcontainers.Grafana;
2+
3+
/// <inheritdoc cref="DockerContainer" />
4+
[PublicAPI]
5+
public sealed class GrafanaContainer : DockerContainer
6+
{
7+
private readonly GrafanaConfiguration _configuration;
8+
9+
/// <summary>
10+
/// Initializes a new instance of the <see cref="GrafanaContainer" /> class.
11+
/// </summary>
12+
/// <param name="configuration">The container configuration.</param>
13+
public GrafanaContainer(GrafanaConfiguration configuration)
14+
: base(configuration)
15+
{
16+
_configuration = configuration;
17+
}
18+
19+
/// <summary>
20+
/// Gets the Grafana base address.
21+
/// </summary>
22+
/// <returns>The Grafana base address.</returns>
23+
public string GetBaseAddress()
24+
{
25+
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(GrafanaBuilder.GrafanaPort)).ToString();
26+
}
27+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworks>net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
4+
<LangVersion>latest</LangVersion>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<PackageReference Include="JetBrains.Annotations" VersionOverride="2023.3.0" PrivateAssets="All"/>
8+
</ItemGroup>
9+
<ItemGroup>
10+
<ProjectReference Include="../Testcontainers/Testcontainers.csproj"/>
11+
</ItemGroup>
12+
</Project>

0 commit comments

Comments
 (0)