85 lines
2.8 KiB
C#
85 lines
2.8 KiB
C#
using System.Formats.Tar;
|
|
using System.IO.Compression;
|
|
|
|
namespace ControlPlane.Api.Services;
|
|
|
|
/// <summary>
|
|
/// Creates a gzipped tar stream from a directory, respecting .dockerignore rules.
|
|
/// Used to supply the Docker build context to the Docker SDK.
|
|
/// </summary>
|
|
internal static class TarHelper
|
|
{
|
|
private static readonly string[] DefaultIgnore =
|
|
[
|
|
".git", ".vs", ".vscode", "node_modules", "bin", "obj",
|
|
"VaultData", "*.user", "*.suo",
|
|
];
|
|
|
|
public static void Pack(string root, Stream destination)
|
|
{
|
|
var ignorePatterns = LoadDockerIgnore(root);
|
|
|
|
using var gz = new GZipStream(destination, CompressionLevel.Fastest, leaveOpen: true);
|
|
using var tar = new TarWriter(gz, TarEntryFormat.Gnu, leaveOpen: false);
|
|
|
|
foreach (var file in Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories))
|
|
{
|
|
var relative = Path.GetRelativePath(root, file).Replace('\\', '/');
|
|
|
|
if (ShouldIgnore(relative, ignorePatterns))
|
|
continue;
|
|
|
|
var entry = new GnuTarEntry(TarEntryType.RegularFile, relative)
|
|
{
|
|
DataStream = File.OpenRead(file),
|
|
};
|
|
tar.WriteEntry(entry);
|
|
}
|
|
}
|
|
|
|
private static List<string> LoadDockerIgnore(string root)
|
|
{
|
|
var path = Path.Combine(root, ".dockerignore");
|
|
var patterns = new List<string>(DefaultIgnore);
|
|
if (!File.Exists(path)) return patterns;
|
|
|
|
foreach (var line in File.ReadAllLines(path))
|
|
{
|
|
var trimmed = line.Trim();
|
|
if (!string.IsNullOrEmpty(trimmed) && !trimmed.StartsWith('#'))
|
|
patterns.Add(trimmed);
|
|
}
|
|
return patterns;
|
|
}
|
|
|
|
private static bool ShouldIgnore(string relativePath, List<string> patterns)
|
|
{
|
|
var segments = relativePath.Split('/');
|
|
|
|
foreach (var pattern in patterns)
|
|
{
|
|
var p = pattern.TrimStart('/').TrimEnd('/');
|
|
|
|
// Glob suffix match (e.g. *.user)
|
|
if (p.StartsWith('*'))
|
|
{
|
|
if (relativePath.EndsWith(p[1..], StringComparison.OrdinalIgnoreCase))
|
|
return true;
|
|
continue;
|
|
}
|
|
|
|
// Exact full-path match or root-anchored prefix (e.g. .git, .vs)
|
|
if (relativePath.Equals(p, StringComparison.OrdinalIgnoreCase))
|
|
return true;
|
|
if (relativePath.StartsWith(p + "/", StringComparison.OrdinalIgnoreCase))
|
|
return true;
|
|
|
|
// Match any path segment so that nested bin/, obj/, node_modules/ etc. are caught
|
|
// regardless of which project subdirectory they live in.
|
|
if (segments.Any(seg => seg.Equals(p, StringComparison.OrdinalIgnoreCase)))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|