Skip to content

Commit

Permalink
[#218] #IMPLEMENT 'assemblyName: DotNet.Testcontainers; function: Doc…
Browse files Browse the repository at this point in the history
…kerIgnoreFile'

{Add .dockerignore support.}
  • Loading branch information
HofmeisterAn committed Jul 17, 2020
1 parent 87ca21e commit f6689ca
Show file tree
Hide file tree
Showing 24 changed files with 499 additions and 88 deletions.
2 changes: 1 addition & 1 deletion .cake-scripts/version.cake
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ internal sealed class BuildInformation

branch = branch.Replace("refs/heads/", string.Empty);

var version = context.ParseAssemblyInfo("src/SolutionInfo.cs").AssemblyVersion;
var version = context.XmlPeek("src/Shared.msbuild", "/Project/PropertyGroup[1]/Version/text()");

var isLocalBuild = context.BuildSystem().IsLocalBuild;

Expand Down
2 changes: 1 addition & 1 deletion build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Task("Sonar-Begin")
Organization = param.SonarQubeCredentials.Organization,
Branch = param.IsPullRequest ? null : param.Branch, // A pull request analysis can not have the branch analysis parameter 'sonar.branch.name'.
Silent = true,
Version = param.Sha,
Version = param.Version.Substring(5),
PullRequestProvider = "GitHub",
PullRequestGithubEndpoint = "https://api.github.com/",
PullRequestGithubRepository = "HofmeisterAn/dotnet-testcontainers",
Expand Down
1 change: 1 addition & 0 deletions src/DotNet.Testcontainers.Tests/Assets/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/*.md
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<PackageReference Include="coverlet.msbuild" Version="2.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="MyCouch" Version="7.2.1" />
<PackageReference Include="MySql.Data" Version="8.0.20" />
<PackageReference Include="MySql.Data" Version="8.0.21" />
<PackageReference Include="Npgsql" Version="4.1.3.1" />
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="2.19.80" />
<PackageReference Include="RabbitMQ.Client" Version="6.1.0" />
Expand Down
42 changes: 42 additions & 0 deletions src/DotNet.Testcontainers.Tests/Fixtures/IgnoreFileTestFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace DotNet.Testcontainers.Tests.Fixtures
{
using DotNet.Testcontainers.Images.Archives;
using Xunit;

internal class IgnoreFileTestFixture : TheoryData<IgnoreFile, string, bool>
{
private static readonly IgnoreFile IgnoreNonRecursiveFiles = new IgnoreFile("*/temp*");

private static readonly IgnoreFile IgnoreNonRecursiveNestedFiles = new IgnoreFile("*/*/temp*");

private static readonly IgnoreFile IgnoreRecursiveFiles = new IgnoreFile("**/*.txt");

private static readonly IgnoreFile IgnoreSingleCharacterFiles = new IgnoreFile("temp?");

private static readonly IgnoreFile IgnoreExceptionFiles = new IgnoreFile("*.md", "!README*.md", "README-secret.md");

public IgnoreFileTestFixture()
{
this.Add(IgnoreNonRecursiveFiles, "lipsum/temp", false);
this.Add(IgnoreNonRecursiveFiles, "lipsum/temp.txt", false);
this.Add(IgnoreNonRecursiveFiles, "lipsum/lorem/temp", true);
this.Add(IgnoreNonRecursiveNestedFiles, "lipsum/lorem/temp", false);
this.Add(IgnoreNonRecursiveNestedFiles, "lipsum/lorem/temp.txt", false);
this.Add(IgnoreNonRecursiveNestedFiles, "lipsum/temp", true);
this.Add(IgnoreNonRecursiveNestedFiles, "lipsum/lorem/lipsum/temp", true);
this.Add(IgnoreRecursiveFiles, "lipsum.txt", false);
this.Add(IgnoreRecursiveFiles, "lorem/lipsum/lorem/lipsum.txt", false);
this.Add(IgnoreRecursiveFiles, "lorem/lipsum/lorem.txt", false);
this.Add(IgnoreRecursiveFiles, "lorem/lipsum.txt", false);
this.Add(IgnoreRecursiveFiles, "lorem/lipsum/lipsum.config", true);
this.Add(IgnoreRecursiveFiles, "lorem/lipsum.config", true);
this.Add(IgnoreSingleCharacterFiles, "temp", false);
this.Add(IgnoreSingleCharacterFiles, "temp1", false);
this.Add(IgnoreSingleCharacterFiles, "temp12", true);
this.Add(IgnoreSingleCharacterFiles, "lipsum/temp", true);
this.Add(IgnoreExceptionFiles, "CODE_OF_CONDUCT.md", false);
this.Add(IgnoreExceptionFiles, "README-secret.md", false);
this.Add(IgnoreExceptionFiles, "README.md", true);
}
}
}
16 changes: 16 additions & 0 deletions src/DotNet.Testcontainers.Tests/Unit/Images/IgnoreFileTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace DotNet.Testcontainers.Tests.Unit.Images
{
using DotNet.Testcontainers.Images.Archives;
using DotNet.Testcontainers.Tests.Fixtures;
using Xunit;

public class IgnoreFileTest
{
[Theory]
[ClassData(typeof(IgnoreFileTestFixture))]
internal void AcceptOrDenyNonRecursivePatterns(IgnoreFile ignoreFile, string path, bool expected)
{
Assert.Equal(expected, ignoreFile.Accepts(path));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace DotNet.Testcontainers.Tests.Unit.Images
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using DotNet.Testcontainers.Images;
using DotNet.Testcontainers.Images.Archives;
using DotNet.Testcontainers.Images.Builders;
using ICSharpCode.SharpZipLib.Tar;
Expand All @@ -15,11 +16,11 @@ public class ImageFromDockerfileTest
public void DockerfileArchiveTar()
{
// Given
var expected = new List<string> { "Dockerfile", "setup", "setup/setup.sh" };
var expected = new SortedSet<string> { "Dockerfile", "setup/setup.sh" };

var actual = new List<string>();
var actual = new SortedSet<string>();

var dockerFileArchive = new DockerfileArchive("./Assets");
var dockerFileArchive = new DockerfileArchive("Assets", "Dockerfile", new DockerImage("Testcontainers", "Test", "1.0.0"));

// When
using (var tarOut = new FileInfo(dockerFileArchive.Tar()).OpenRead())
Expand Down
7 changes: 7 additions & 0 deletions src/DotNet.Testcontainers.sln
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AF017206-CE20-4DDF-8301-CAC68CED1BE6}"
ProjectSection(SolutionItems) = preProject
..\CODE_OF_CONDUCT.md = ..\CODE_OF_CONDUCT.md
..\CONTRIBUTING.md = ..\CONTRIBUTING.md
..\README.md = ..\README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.Testcontainers", "DotNet.Testcontainers\DotNet.Testcontainers.csproj", "{A64895AC-71FF-417C-AC4B-C4C58064656A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet.Testcontainers.Tests", "DotNet.Testcontainers.Tests\DotNet.Testcontainers.Tests.csproj", "{9AFD8864-3E9F-4C7A-974D-95F3D4435871}"
Expand Down
2 changes: 1 addition & 1 deletion src/DotNet.Testcontainers/Clients/DockerApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace DotNet.Testcontainers.Clients
using System.Collections.Concurrent;
using Docker.DotNet;

internal class DockerApiClient
internal abstract class DockerApiClient
{
private static readonly ConcurrentDictionary<Uri, IDockerClient> Clients = new ConcurrentDictionary<Uri, IDockerClient>();

Expand Down
12 changes: 2 additions & 10 deletions src/DotNet.Testcontainers/Clients/DockerApiEndpoint.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
namespace DotNet.Testcontainers.Clients
{
using System;
using System.Runtime.InteropServices;
using DotNet.Testcontainers.Services;

internal static class DockerApiEndpoint
{
#pragma warning disable S1075

private static readonly Uri Unix = new Uri("unix:/var/run/docker.sock");

private static readonly Uri Windows = new Uri("npipe://./pipe/docker_engine");

#pragma warning restore S1075

public static readonly Uri Local = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows : Unix;
public static Uri Local => TestcontainersHostService.GetService<IOperatingSystem>().DockerApiEndpoint;
}
}
2 changes: 1 addition & 1 deletion src/DotNet.Testcontainers/Clients/DockerImageOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public Task DeleteAsync(IDockerImage image, CancellationToken ct = default)

public async Task<string> BuildAsync(IImageFromDockerfileConfiguration config, CancellationToken ct = default)
{
var dockerFileArchive = new DockerfileArchive(config.DockerfileDirectory);
var dockerFileArchive = new DockerfileArchive(config.DockerfileDirectory, config.Dockerfile, config.Image);

var imageExists = await this.ExistsWithNameAsync(config.Image.FullName, ct);

Expand Down
4 changes: 2 additions & 2 deletions src/DotNet.Testcontainers/DotNet.Testcontainers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Docker.DotNet" Version="3.125.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.6" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
<PackageReference Include="Serilog" Version="2.9.0" />
Expand Down
33 changes: 33 additions & 0 deletions src/DotNet.Testcontainers/Images/Archives/DockerIgnoreFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace DotNet.Testcontainers.Images.Archives
{
using System.IO;
using System.Linq;

/// <summary>
/// A implementation of <see cref="IgnoreFile" /> that uses the patterns of the .dockerignore file.
/// </summary>
internal sealed class DockerIgnoreFile : IgnoreFile
{
/// <summary>
/// Creates an instance of <see cref="DockerIgnoreFile" /> from the specified directory and ignore files.
/// </summary>
/// <param name="dockerIgnoreFileDirectory">Directory that contains all docker configuration files.</param>
/// <param name="dockerIgnoreFile">.dockerignore file name.</param>
public DockerIgnoreFile(string dockerIgnoreFileDirectory, string dockerIgnoreFile)
: this(new DirectoryInfo(dockerIgnoreFileDirectory), dockerIgnoreFile)
{
}

/// <summary>
/// Creates an instance of <see cref="DockerIgnoreFile" /> from the specified directory and ignore files.
/// </summary>
/// <param name="dockerIgnoreFileDirectory">Directory that contains all docker configuration files.</param>
/// <param name="dockerIgnoreFile">.dockerignore file name.</param>
public DockerIgnoreFile(DirectoryInfo dockerIgnoreFileDirectory, string dockerIgnoreFile)
: base(dockerIgnoreFileDirectory.GetFiles(dockerIgnoreFile, SearchOption.TopDirectoryOnly).Any()
? File.ReadLines(Path.Combine(dockerIgnoreFileDirectory.FullName, dockerIgnoreFile)).Concat(new[] { dockerIgnoreFile }).ToArray()
: new[] { dockerIgnoreFile })
{
}
}
}
108 changes: 71 additions & 37 deletions src/DotNet.Testcontainers/Images/Archives/DockerfileArchive.cs
Original file line number Diff line number Diff line change
@@ -1,75 +1,109 @@
namespace DotNet.Testcontainers.Images.Archives
namespace DotNet.Testcontainers.Images.Archives
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using DotNet.Testcontainers.Services;
using ICSharpCode.SharpZipLib.Tar;

/// <summary>
/// Generates a tar archive with docker configuration files. The generated tar archive can be used to build a docker image.
/// </summary>
internal sealed class DockerfileArchive : ITarArchive
{
private static readonly char[] TrimLeadingChars = { '/' };
private static readonly IOperatingSystem os;

public DockerfileArchive(string baseDirectory) : this(new DirectoryInfo(baseDirectory))
private readonly DirectoryInfo dockerfileDirectory;

private readonly IDockerImage image;

static DockerfileArchive()
{
os = new Unix();
}

public DockerfileArchive(DirectoryInfo baseDirectory)
/// <summary>
/// Creates an instance of <see cref="DockerfileArchive" /> for the specified parameters.
/// </summary>
/// <param name="dockerfileDirectory">Directory to docker configuration files.</param>
/// <param name="dockerfile">Name of the dockerfile, which is necessary to start the docker build.</param>
/// <param name="image">Docker image information to create the tar archive for.</param>
public DockerfileArchive(string dockerfileDirectory, string dockerfile, IDockerImage image)
: this(new DirectoryInfo(dockerfileDirectory), dockerfile, image)
{
this.BaseDirectory = baseDirectory;

this.DockerfileArchiveFile = new FileInfo($"{Path.GetTempPath()}/Dockerfile.tar");
}

if (!this.BaseDirectory.Exists)
/// <summary>
/// Creates an instance of <see cref="DockerfileArchive" /> for the specified parameters.
/// </summary>
/// <param name="dockerfileDirectory">Directory to docker configuration files.</param>
/// <param name="dockerfile">Name of the dockerfile, which is necessary to start the docker build.</param>
/// <param name="image">Docker image information to create the tar archive for.</param>
/// <exception cref="ArgumentException">Will be thrown if the dockerfile directory does not exist or the directory does not contain a dockerfile.</exception>
public DockerfileArchive(DirectoryInfo dockerfileDirectory, string dockerfile, IDockerImage image)
{
if (!dockerfileDirectory.Exists)
{
throw new ArgumentException($"Directory '{this.BaseDirectory.FullName}' does not exist.");
throw new ArgumentException($"Directory '{dockerfileDirectory.FullName}' does not exist.");
}

if (!this.BaseDirectory.GetFiles().Any(file => "Dockerfile".Equals(file.Name)))
if (!dockerfileDirectory.GetFiles(dockerfile, SearchOption.TopDirectoryOnly).Any())
{
throw new ArgumentException($"Dockerfile does not exist in '{this.BaseDirectory.FullName}'.");
throw new ArgumentException($"{dockerfile} does not exist in '{dockerfileDirectory.FullName}'.");
}
}

public DirectoryInfo BaseDirectory { get; }

public FileInfo DockerfileArchiveFile { get; }
this.dockerfileDirectory = dockerfileDirectory;
this.image = image;
}

/// <inheritdoc />
public string Tar()
{
using (var dockerfileArchiveStream = File.Create(this.DockerfileArchiveFile.FullName))
var dockerfileArchiveName = Regex.Replace(this.image.FullName, "[^a-zA-Z0-9]", "-").ToLowerInvariant();

var dockerfileArchivePath = Path.Combine(Path.GetTempPath(), $"{dockerfileArchiveName}.tar");

var dockerIgnoreFile = new DockerIgnoreFile(this.dockerfileDirectory.FullName, ".dockerignore");

using (var stream = new FileStream(dockerfileArchivePath, FileMode.Create))
{
using (var dockerfileArchive = TarArchive.CreateOutputTarArchive(dockerfileArchiveStream))
using (var tarArchive = TarArchive.CreateOutputTarArchive(stream))
{
dockerfileArchive.RootPath = this.BaseDirectory.FullName;
tarArchive.RootPath = os.NormalizePath(this.dockerfileDirectory.FullName);

void Tar(string baseDirectory)
foreach (var file in GetFiles(this.dockerfileDirectory.FullName))
{
baseDirectory = baseDirectory.Replace('\\', '/');

void WriteEntry(string entry)
{
entry = entry.Replace('\\', '/');

var tarEntry = TarEntry.CreateEntryFromFile(entry);
tarEntry.Name = entry.Replace(dockerfileArchive.RootPath, string.Empty).TrimStart(TrimLeadingChars);
dockerfileArchive.WriteEntry(tarEntry, File.Exists(entry));
}
var relativePath = file.Substring(tarArchive.RootPath.Length + 1);

if (!dockerfileArchive.RootPath.Equals(baseDirectory))
if (dockerIgnoreFile.Denies(relativePath))
{
WriteEntry(baseDirectory);
continue;
}

Directory.GetFiles(baseDirectory).ToList().ForEach(WriteEntry);

Directory.GetDirectories(baseDirectory).ToList().ForEach(Tar);
var tarEntry = TarEntry.CreateEntryFromFile(file);
tarEntry.Name = relativePath;
tarArchive.WriteEntry(tarEntry, true);
}

Tar(this.BaseDirectory.FullName);
}
}

return this.DockerfileArchiveFile.FullName;
return dockerfileArchivePath;
}

/// <summary>
/// Gets all accepted docker archive files.
/// </summary>
/// <param name="directory">Directory to docker configuration files.</param>
/// <returns>Returns a list with all accepted docker archive files.</returns>
private static IEnumerable<string> GetFiles(string directory)
{
return Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories)
.AsParallel()
.Select(Path.GetFullPath)
.Select(os.NormalizePath)
.ToArray();
}
}
}
7 changes: 7 additions & 0 deletions src/DotNet.Testcontainers/Images/Archives/ITarArchive.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
namespace DotNet.Testcontainers.Images.Archives
{
/// <summary>
/// Collects files into one tar archive file.
/// </summary>
internal interface ITarArchive
{
/// <summary>
/// Creates a tar archive file.
/// </summary>
/// <returns>Path to the created archive file.</returns>
string Tar();
}
}
Loading

0 comments on commit f6689ca

Please sign in to comment.