Skip to content

Commit

Permalink
feat: Add K3s module (#835)
Browse files Browse the repository at this point in the history
Co-authored-by: Andre Hofmeister <[email protected]>
  • Loading branch information
adar2 and HofmeisterAn authored Mar 22, 2023
1 parent 7a5cc2e commit 710987a
Show file tree
Hide file tree
Showing 14 changed files with 284 additions and 2 deletions.
1 change: 1 addition & 0 deletions Testcontainers.dic
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ httpd
identitytoken
initdb
isready
kubeconfig
lipsum
ltsc
memopt
Expand Down
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Elasticsearc
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventStoreDb", "src\Testcontainers.EventStoreDb\Testcontainers.EventStoreDb.csproj", "{84D707E0-C9FA-4327-85DC-0AFEBEA73572}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.K3s", "src\Testcontainers.K3s\Testcontainers.K3s.csproj", "{111B840F-9DB0-4166-83E6-0580FD418F07}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Kafka", "src\Testcontainers.Kafka\Testcontainers.Kafka.csproj", "{E93E40CE-59AA-4561-9B4C-E7B0A686326E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.LocalStack", "src\Testcontainers.LocalStack\Testcontainers.LocalStack.csproj", "{3792268A-EF08-4569-8118-991E08FD61C4}"
Expand Down Expand Up @@ -73,6 +75,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Elasticsearc
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventStoreDb.Tests", "tests\Testcontainers.EventStoreDb.Tests\Testcontainers.EventStoreDb.Tests.csproj", "{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.K3s.Tests", "tests\Testcontainers.K3s.Tests\Testcontainers.K3s.Tests.csproj", "{F0F40AE2-70FF-4191-ADDA-26A19E0D1A0F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Kafka.Tests", "tests\Testcontainers.Kafka.Tests\Testcontainers.Kafka.Tests.csproj", "{6F2AEE03-629A-4B49-BD5B-25CA3C61FFB7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.LocalStack.Tests", "tests\Testcontainers.LocalStack.Tests\Testcontainers.LocalStack.Tests.csproj", "{728CBE16-1D52-4F84-AF01-7229E6013512}"
Expand Down Expand Up @@ -146,6 +150,10 @@ Global
{84D707E0-C9FA-4327-85DC-0AFEBEA73572}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84D707E0-C9FA-4327-85DC-0AFEBEA73572}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84D707E0-C9FA-4327-85DC-0AFEBEA73572}.Release|Any CPU.Build.0 = Release|Any CPU
{111B840F-9DB0-4166-83E6-0580FD418F07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{111B840F-9DB0-4166-83E6-0580FD418F07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{111B840F-9DB0-4166-83E6-0580FD418F07}.Release|Any CPU.ActiveCfg = Release|Any CPU
{111B840F-9DB0-4166-83E6-0580FD418F07}.Release|Any CPU.Build.0 = Release|Any CPU
{E93E40CE-59AA-4561-9B4C-E7B0A686326E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E93E40CE-59AA-4561-9B4C-E7B0A686326E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E93E40CE-59AA-4561-9B4C-E7B0A686326E}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -242,6 +250,10 @@ Global
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0}.Release|Any CPU.Build.0 = Release|Any CPU
{F0F40AE2-70FF-4191-ADDA-26A19E0D1A0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F0F40AE2-70FF-4191-ADDA-26A19E0D1A0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F0F40AE2-70FF-4191-ADDA-26A19E0D1A0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F0F40AE2-70FF-4191-ADDA-26A19E0D1A0F}.Release|Any CPU.Build.0 = Release|Any CPU
{6F2AEE03-629A-4B49-BD5B-25CA3C61FFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F2AEE03-629A-4B49-BD5B-25CA3C61FFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F2AEE03-629A-4B49-BD5B-25CA3C61FFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -330,6 +342,7 @@ Global
{2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{641DDEA5-B6E0-41E6-BA11-7A28C0913127} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{84D707E0-C9FA-4327-85DC-0AFEBEA73572} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{111B840F-9DB0-4166-83E6-0580FD418F07} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{E93E40CE-59AA-4561-9B4C-E7B0A686326E} = {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}
Expand All @@ -354,6 +367,7 @@ Global
{101515E6-74C1-40F9-85C8-871F742A378D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{DD5B3678-468F-4D73-AECE-705E3D66CD43} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{F0F40AE2-70FF-4191-ADDA-26A19E0D1A0F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{6F2AEE03-629A-4B49-BD5B-25CA3C61FFB7} = {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}
Expand Down
1 change: 1 addition & 0 deletions Testcontainers.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=identitytoken/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=initdb/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=isready/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=kubeconfig/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=lipsum/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ltsc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=memopt/@EntryIndexedValue">True</s:Boolean>
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.K3s/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
88 changes: 88 additions & 0 deletions src/Testcontainers.K3s/K3sBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
namespace Testcontainers.K3s;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class K3sBuilder : ContainerBuilder<K3sBuilder, K3sContainer, K3sConfiguration>
{
public const string RancherImage = "rancher/k3s:v1.26.2-k3s1";

public const ushort KubeSecurePort = 6443;

public const ushort RancherWebhookPort = 8443;

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

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

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

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

/// <inheritdoc />
protected override K3sBuilder Init()
{
return base.Init()
.WithImage(RancherImage)
.WithPrivileged(true)
.WithPortBinding(KubeSecurePort, true)
.WithPortBinding(RancherWebhookPort, true)
.WithBindMount("/sys/fs/cgroup", "/sys/fs/cgroup", AccessMode.ReadWrite)
.WithTmpfsMount("/run")
.WithTmpfsMount("/var/run")
.WithCommand("server", "--disable=traefik")
.WithCreateParameterModifier(parameterModifier => parameterModifier.HostConfig.CgroupnsMode = "host")
.WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil()));
}

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

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

/// <inheritdoc />
protected override K3sBuilder Merge(K3sConfiguration oldValue, K3sConfiguration newValue)
{
return new K3sBuilder(new K3sConfiguration(oldValue, newValue));
}

/// <inheritdoc cref="IWaitUntil" />
private sealed class WaitUntil : IWaitUntil
{
/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
var (_, stderr) = await container.GetLogsAsync(timestampsEnabled: false)
.ConfigureAwait(false);

return stderr.Contains("Node controller sync successful");
}
}
}
53 changes: 53 additions & 0 deletions src/Testcontainers.K3s/K3sConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace Testcontainers.K3s;

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

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

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

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

/// <summary>
/// Gets the Kubeconfig.
/// </summary>
/// <returns>Task that completes when the Kubeconfig has been read.</returns>
public async Task<string> GetKubeconfigAsync()
{
var kubeconfigBytes = await ReadFileAsync("/etc/rancher/k3s/k3s.yaml")
.ConfigureAwait(false);

var kubeconfig = Encoding.Default.GetString(kubeconfigBytes);

var server = new UriBuilder(Uri.UriSchemeHttps, Hostname, GetMappedPublicPort(K3sBuilder.KubeSecurePort)).ToString();

return Regex.Replace(kubeconfig, "server:\\s?[:/\\.\\d\\w]+", "server: " + server, RegexOptions.None, TimeSpan.FromSeconds(1));
}
}
12 changes: 12 additions & 0 deletions src/Testcontainers.K3s/Testcontainers.K3s.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)src/Testcontainers/Testcontainers.csproj"/>
</ItemGroup>
</Project>
10 changes: 10 additions & 0 deletions src/Testcontainers.K3s/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
global using System;
global using System.Text;
global using System.Text.RegularExpressions;
global using System.Threading.Tasks;
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;
1 change: 1 addition & 0 deletions tests/Testcontainers.K3s.Tests/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
42 changes: 42 additions & 0 deletions tests/Testcontainers.K3s.Tests/K3sContainerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace Testcontainers.K3s;

public sealed class K3sContainerTest : IAsyncLifetime
{
private readonly K3sContainer _k3sConainter = new K3sBuilder().Build();

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

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

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task CreateNamespaceReturnsHttpStatusCodeCreated()
{
// Given
using var kubeconfigStream = new MemoryStream();

var kubeconfig = await _k3sConainter.GetKubeconfigAsync()
.ConfigureAwait(false);

await kubeconfigStream.WriteAsync(Encoding.Default.GetBytes(kubeconfig))
.ConfigureAwait(false);

var clientConfiguration = await KubernetesClientConfiguration.BuildConfigFromConfigFileAsync(kubeconfigStream)
.ConfigureAwait(false);

using var client = new Kubernetes(clientConfiguration);

// When
using var response = await client.CoreV1.CreateNamespaceWithHttpMessagesAsync(new V1Namespace(metadata: new V1ObjectMeta(name: Guid.NewGuid().ToString("D"))))
.ConfigureAwait(false);

// Then
Assert.Equal(HttpStatusCode.Created, response.Response.StatusCode);
}
}
18 changes: 18 additions & 0 deletions tests/Testcontainers.K3s.Tests/Testcontainers.K3s.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsPublishable>false</IsPublishable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"/>
<PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="KubernetesClient" Version="10.1.4"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)src/Testcontainers.K3s/Testcontainers.K3s.csproj"/>
<ProjectReference Include="$(SolutionDir)tests/Testcontainers.Commons/Testcontainers.Commons.csproj"/>
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions tests/Testcontainers.K3s.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
global using System;
global using System.IO;
global using System.Net;
global using System.Text;
global using System.Threading.Tasks;
global using DotNet.Testcontainers.Commons;
global using k8s;
global using k8s.Models;
global using Xunit;
Original file line number Diff line number Diff line change
Expand Up @@ -461,8 +461,8 @@ public async Task ParameterModifiers()
var testcontainersBuilder = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("alpine")
.WithEntrypoint(CommonCommands.SleepInfinity)
.WithCreateParameterModifier(parameters => parameters.Name = "placeholder")
.WithCreateParameterModifier(parameters => parameters.Name = name);
.WithCreateParameterModifier(parameterModifier => parameterModifier.Name = "placeholder")
.WithCreateParameterModifier(parameterModifier => parameterModifier.Name = name);

// Then
await using (ITestcontainersContainer testcontainer = testcontainersBuilder.Build())
Expand Down

0 comments on commit 710987a

Please sign in to comment.