feat: Initialize Zastava Webhook service with TLS and Authority authentication

- Added Program.cs to set up the web application with Serilog for logging, health check endpoints, and a placeholder admission endpoint.
- Configured Kestrel server to use TLS 1.3 and handle client certificates appropriately.
- Created StellaOps.Zastava.Webhook.csproj with necessary dependencies including Serilog and Polly.
- Documented tasks in TASKS.md for the Zastava Webhook project, outlining current work and exit criteria for each task.
This commit is contained in:
master
2025-10-19 18:36:22 +03:00
parent 2062da7a8b
commit d099a90f9b
966 changed files with 91038 additions and 1850 deletions

View File

@@ -0,0 +1,44 @@
using System.Diagnostics;
using System.IO;
namespace Mongo2Go.Helper
{
public class FileSystem : IFileSystem
{
public void CreateFolder(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
public void DeleteFolder(string path)
{
if (Directory.Exists(path))
{
Directory.Delete(path, true);
}
}
public void DeleteFile(string fullFileName)
{
if (File.Exists(fullFileName))
{
File.Delete(fullFileName);
}
}
public void MakeFileExecutable (string path)
{
//when on linux or osx we must set the executeble flag on mongo binarys
var p = Process.Start("chmod", $"+x {path}");
p.WaitForExit();
if (p.ExitCode != 0)
{
throw new IOException($"Could not set executable bit for {path}");
}
}
}
}

View File

@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Mongo2Go.Helper
{
public static class FolderSearch
{
private static readonly char[] _separators = { Path.DirectorySeparatorChar };
public static string CurrentExecutingDirectory()
{
string filePath = new Uri(typeof(FolderSearch).GetTypeInfo().Assembly.CodeBase).LocalPath;
return Path.GetDirectoryName(filePath);
}
public static string FindFolder(this string startPath, string searchPattern)
{
if (startPath == null || searchPattern == null)
{
return null;
}
string currentPath = startPath;
foreach (var part in searchPattern.Split(_separators, StringSplitOptions.None))
{
if (!Directory.Exists(currentPath))
{
return null;
}
string[] matchesDirectory = Directory.GetDirectories(currentPath, part);
if (!matchesDirectory.Any())
{
return null;
}
if (matchesDirectory.Length > 1)
{
currentPath = MatchVersionToAssemblyVersion(matchesDirectory)
?? matchesDirectory.OrderBy(x => x).Last();
}
else
{
currentPath = matchesDirectory.First();
}
}
return currentPath;
}
public static string FindFolderUpwards(this string startPath, string searchPattern)
{
if (string.IsNullOrEmpty(startPath))
{
return null;
}
string matchingFolder = startPath.FindFolder(searchPattern);
return matchingFolder ?? startPath.RemoveLastPart().FindFolderUpwards(searchPattern);
}
internal static string RemoveLastPart(this string path)
{
if (!path.Contains(Path.DirectorySeparatorChar))
{
return null;
}
List<string> parts = path.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).ToList();
parts.RemoveAt(parts.Count() - 1);
return string.Join(Path.DirectorySeparatorChar.ToString(), parts.ToArray());
}
/// <summary>
/// Absolute path stays unchanged, relative path will be relative to current executing directory (usually the /bin folder)
/// </summary>
public static string FinalizePath(string fileName)
{
string finalPath;
if (Path.IsPathRooted(fileName))
{
finalPath = fileName;
}
else
{
finalPath = Path.Combine(CurrentExecutingDirectory(), fileName);
finalPath = Path.GetFullPath(finalPath);
}
return finalPath;
}
private static string MatchVersionToAssemblyVersion(string[] folders)
{
var version = typeof(FolderSearch).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
foreach (var folder in folders)
{
var lastFolder = new DirectoryInfo(folder).Name;
if (lastFolder == version)
return folder;
}
return null;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Mongo2Go.Helper
{
public interface IFileSystem
{
void CreateFolder(string path);
void DeleteFolder(string path);
void DeleteFile(string fullFileName);
void MakeFileExecutable (string path );
}
}

View File

@@ -0,0 +1,7 @@
namespace Mongo2Go.Helper
{
public interface IMongoBinaryLocator
{
string Directory { get; }
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
namespace Mongo2Go.Helper
{
public interface IMongoDbProcess : IDisposable
{
IEnumerable<string> StandardOutput { get; }
IEnumerable<string> ErrorOutput { get; }
}
}

View File

@@ -0,0 +1,11 @@
using Microsoft.Extensions.Logging;
namespace Mongo2Go.Helper
{
public interface IMongoDbProcessStarter
{
IMongoDbProcess Start(string binariesDirectory, string dataDirectory, int port, bool singleNodeReplSet, string additionalMongodArguments, ushort singleNodeReplSetWaitTimeout = MongoDbDefaults.SingleNodeReplicaSetWaitTimeout, ILogger logger = null);
IMongoDbProcess Start(string binariesDirectory, string dataDirectory, int port, bool doNotKill, bool singleNodeReplSet, string additionalMongodArguments, ushort singleNodeReplSetWaitTimeout = MongoDbDefaults.SingleNodeReplicaSetWaitTimeout, ILogger logger = null);
}
}

View File

@@ -0,0 +1,10 @@
namespace Mongo2Go.Helper
{
public interface IPortPool
{
/// <summary>
/// Returns and reserves a new port
/// </summary>
int GetNextOpenPort();
}
}

View File

@@ -0,0 +1,8 @@
namespace Mongo2Go.Helper
{
public interface IPortWatcher
{
int FindOpenPort();
bool IsPortAvailable(int portNumber);
}
}

View File

@@ -0,0 +1,7 @@
namespace Mongo2Go.Helper
{
public interface IProcessWatcher
{
bool IsProcessRunning(string processName);
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Mongo2Go.Helper
{
public class MongoBinaryLocator : IMongoBinaryLocator
{
private readonly string _nugetPrefix = Path.Combine("packages", "Mongo2Go*");
private readonly string _nugetCachePrefix = Path.Combine("packages", "mongo2go", "*");
private readonly string _nugetCacheBasePrefix = Path.Combine("mongo2go", "*");
public const string DefaultWindowsSearchPattern = @"tools\mongodb-windows*\bin";
public const string DefaultLinuxSearchPattern = "tools/mongodb-linux*/bin";
public const string DefaultOsxSearchPattern = "tools/mongodb-macos*/bin";
public const string WindowsNugetCacheLocation = @"%USERPROFILE%\.nuget\packages";
public static readonly string OsxAndLinuxNugetCacheLocation = Environment.GetEnvironmentVariable("HOME") + "/.nuget/packages";
private string _binFolder = string.Empty;
private readonly string _searchPattern;
private readonly string _nugetCacheDirectory;
private readonly string _additionalSearchDirectory;
public MongoBinaryLocator(string searchPatternOverride, string additionalSearchDirectory)
{
_additionalSearchDirectory = additionalSearchDirectory;
_nugetCacheDirectory = Environment.GetEnvironmentVariable("NUGET_PACKAGES");
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
_searchPattern = DefaultOsxSearchPattern;
_nugetCacheDirectory = _nugetCacheDirectory ?? OsxAndLinuxNugetCacheLocation;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
_searchPattern = DefaultLinuxSearchPattern;
_nugetCacheDirectory = _nugetCacheDirectory ?? OsxAndLinuxNugetCacheLocation;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_searchPattern = DefaultWindowsSearchPattern;
_nugetCacheDirectory = _nugetCacheDirectory ?? Environment.ExpandEnvironmentVariables(WindowsNugetCacheLocation);
}
else
{
throw new MonogDbBinariesNotFoundException($"Unknown OS: {RuntimeInformation.OSDescription}");
}
if (!string.IsNullOrEmpty(searchPatternOverride))
{
_searchPattern = searchPatternOverride;
}
}
public string Directory {
get {
if (string.IsNullOrEmpty(_binFolder)){
return _binFolder = ResolveBinariesDirectory ();
} else {
return _binFolder;
}
}
}
private string ResolveBinariesDirectory()
{
var searchDirectories = new[]
{
// First search from the additional search directory, if provided
_additionalSearchDirectory,
// Then search from the project directory
FolderSearch.CurrentExecutingDirectory(),
// Finally search from the nuget cache directory
_nugetCacheDirectory
};
return FindBinariesDirectory(searchDirectories.Where(x => !string.IsNullOrWhiteSpace(x)).ToList());
}
private string FindBinariesDirectory(IList<string> searchDirectories)
{
foreach (var directory in searchDirectories)
{
var binaryFolder =
// First try just the search pattern
directory.FindFolderUpwards(_searchPattern) ??
// Next try the search pattern with nuget installation prefix
directory.FindFolderUpwards(Path.Combine(_nugetPrefix, _searchPattern)) ??
// Finally try the search pattern with the nuget cache prefix
directory.FindFolderUpwards(Path.Combine(_nugetCachePrefix, _searchPattern)) ??
// Finally try the search pattern with the basic nuget cache prefix
directory.FindFolderUpwards(Path.Combine(_nugetCacheBasePrefix, _searchPattern));
if (binaryFolder != null) return binaryFolder;
}
throw new MonogDbBinariesNotFoundException(
$"Could not find Mongo binaries using the search patterns \"{_searchPattern}\", \"{Path.Combine(_nugetPrefix, _searchPattern)}\", \"{Path.Combine(_nugetCachePrefix, _searchPattern)}\", and \"{Path.Combine(_nugetCacheBasePrefix, _searchPattern)}\". " +
$"You can override the search pattern and directory when calling MongoDbRunner.Start. We have detected the OS as {RuntimeInformation.OSDescription}.\n" +
$"We walked up to root directory from the following locations.\n {string.Join("\n", searchDirectories)}");
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
namespace Mongo2Go.Helper
{
// IDisposable and friends
public partial class MongoDbProcess
{
~MongoDbProcess()
{
Dispose(false);
}
public bool Disposed { get; private set; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (Disposed)
{
return;
}
if (disposing)
{
// we have no "managed resources" - but we leave this switch to avoid an FxCop CA1801 warnig
}
if (_process == null)
{
return;
}
if (_process.DoNotKill)
{
return;
}
if (!_process.HasExited)
{
_process.Kill();
_process.WaitForExit();
}
_process.Dispose();
_process = null;
Disposed = true;
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Mongo2Go.Helper
{
public partial class MongoDbProcess : IMongoDbProcess
{
private WrappedProcess _process;
public IEnumerable<string> ErrorOutput { get; set; }
public IEnumerable<string> StandardOutput { get; set; }
internal MongoDbProcess(WrappedProcess process)
{
_process = process;
}
}
}

View File

@@ -0,0 +1,92 @@
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Core.Servers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace Mongo2Go.Helper
{
public class MongoDbProcessStarter : IMongoDbProcessStarter
{
private const string ProcessReadyIdentifier = "waiting for connections";
private const string Space = " ";
private const string ReplicaSetName = "singleNodeReplSet";
private const string ReplicaSetReadyIdentifier = "transition to primary complete; database writes are now permitted";
/// <summary>
/// Starts a new process. Process can be killed
/// </summary>
public IMongoDbProcess Start(string binariesDirectory, string dataDirectory, int port, bool singleNodeReplSet, string additionalMongodArguments, ushort singleNodeReplSetWaitTimeout = MongoDbDefaults.SingleNodeReplicaSetWaitTimeout, ILogger logger = null)
{
return Start(binariesDirectory, dataDirectory, port, false, singleNodeReplSet, additionalMongodArguments, singleNodeReplSetWaitTimeout, logger);
}
/// <summary>
/// Starts a new process.
/// </summary>
public IMongoDbProcess Start(string binariesDirectory, string dataDirectory, int port, bool doNotKill, bool singleNodeReplSet, string additionalMongodArguments, ushort singleNodeReplSetWaitTimeout = MongoDbDefaults.SingleNodeReplicaSetWaitTimeout, ILogger logger = null)
{
string fileName = @"{0}{1}{2}".Formatted(binariesDirectory, System.IO.Path.DirectorySeparatorChar.ToString(), MongoDbDefaults.MongodExecutable);
string arguments = (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) ?
@"--dbpath ""{0}"" --port {1} --bind_ip 127.0.0.1".Formatted(dataDirectory, port) :
@"--tlsMode disabled --dbpath ""{0}"" --port {1} --bind_ip 127.0.0.1".Formatted(dataDirectory, port);
arguments = singleNodeReplSet ? arguments + Space + "--replSet" + Space + ReplicaSetName : arguments;
arguments += MongodArguments.GetValidAdditionalArguments(arguments, additionalMongodArguments);
WrappedProcess wrappedProcess = ProcessControl.ProcessFactory(fileName, arguments);
wrappedProcess.DoNotKill = doNotKill;
ProcessOutput output = ProcessControl.StartAndWaitForReady(wrappedProcess, 5, ProcessReadyIdentifier, logger);
if (singleNodeReplSet)
{
var replicaSetReady = false;
// subscribe to output from mongod process and check for replica set ready message
wrappedProcess.OutputDataReceived += (_, args) => replicaSetReady |= !string.IsNullOrWhiteSpace(args.Data) && args.Data.IndexOf(ReplicaSetReadyIdentifier, StringComparison.OrdinalIgnoreCase) >= 0;
MongoClient client = new MongoClient("mongodb://127.0.0.1:{0}/?directConnection=true&replicaSet={1}".Formatted(port, ReplicaSetName));
var admin = client.GetDatabase("admin");
var replConfig = new BsonDocument(new List<BsonElement>()
{
new BsonElement("_id", ReplicaSetName),
new BsonElement("members",
new BsonArray {new BsonDocument {{"_id", 0}, {"host", "127.0.0.1:{0}".Formatted(port)}}})
});
var command = new BsonDocument("replSetInitiate", replConfig);
admin.RunCommand<BsonDocument>(command);
// wait until replica set is ready or until the timeout is reached
SpinWait.SpinUntil(() => replicaSetReady, TimeSpan.FromSeconds(singleNodeReplSetWaitTimeout));
if (!replicaSetReady)
{
throw new TimeoutException($"Replica set initialization took longer than the specified timeout of {singleNodeReplSetWaitTimeout} seconds. Please consider increasing the value of {nameof(singleNodeReplSetWaitTimeout)}.");
}
// wait until transaction is ready or until the timeout is reached
SpinWait.SpinUntil(() =>
client.Cluster.Description.Servers.Any(s => s.State == ServerState.Connected && s.IsDataBearing),
TimeSpan.FromSeconds(singleNodeReplSetWaitTimeout));
if (!client.Cluster.Description.Servers.Any(s => s.State == ServerState.Connected && s.IsDataBearing))
{
throw new TimeoutException($"Cluster readiness for transactions took longer than the specified timeout of {singleNodeReplSetWaitTimeout} seconds. Please consider increasing the value of {nameof(singleNodeReplSetWaitTimeout)}.");
}
}
MongoDbProcess mongoDbProcess = new MongoDbProcess(wrappedProcess)
{
ErrorOutput = output.ErrorOutput,
StandardOutput = output.StandardOutput
};
return mongoDbProcess;
}
}
}

View File

@@ -0,0 +1,46 @@
using System.Diagnostics;
using System.IO;
namespace Mongo2Go.Helper
{
public static class MongoImportExport
{
/// <summary>
/// Input File: Absolute path stays unchanged, relative path will be relative to current executing directory (usually the /bin folder)
/// </summary>
public static ProcessOutput Import(string binariesDirectory, int port, string database, string collection, string inputFile, bool drop, string additionalMongodArguments = null)
{
string finalPath = FolderSearch.FinalizePath(inputFile);
if (!File.Exists(finalPath))
{
throw new FileNotFoundException("File not found", finalPath);
}
string fileName = Path.Combine("{0}", "{1}").Formatted(binariesDirectory, MongoDbDefaults.MongoImportExecutable);
string arguments = @"--host localhost --port {0} --db {1} --collection {2} --file ""{3}""".Formatted(port, database, collection, finalPath);
if (drop) { arguments += " --drop"; }
arguments += MongodArguments.GetValidAdditionalArguments(arguments, additionalMongodArguments);
Process process = ProcessControl.ProcessFactory(fileName, arguments);
return ProcessControl.StartAndWaitForExit(process);
}
/// <summary>
/// Output File: Absolute path stays unchanged, relative path will be relative to current executing directory (usually the /bin folder)
/// </summary>
public static ProcessOutput Export(string binariesDirectory, int port, string database, string collection, string outputFile, string additionalMongodArguments = null)
{
string finalPath = FolderSearch.FinalizePath(outputFile);
string fileName = Path.Combine("{0}", "{1}").Formatted(binariesDirectory, MongoDbDefaults.MongoExportExecutable);
string arguments = @"--host localhost --port {0} --db {1} --collection {2} --out ""{3}""".Formatted(port, database, collection, finalPath);
arguments += MongodArguments.GetValidAdditionalArguments(arguments, additionalMongodArguments);
Process process = ProcessControl.ProcessFactory(fileName, arguments);
return ProcessControl.StartAndWaitForExit(process);
}
}
}

View File

@@ -0,0 +1,77 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Mongo2Go.Helper
{
/// <summary>
/// Structure of a log generated by mongod. Used to deserialize the logs
/// and pass them to an ILogger.
/// See: https://docs.mongodb.com/manual/reference/log-messages/#json-log-output-format
/// Note: "truncated" and "size" are not parsed as we're unsure how to
/// properly parse and use them.
/// </summary>
class MongoLogStatement
{
[JsonPropertyName("t")]
public MongoDate MongoDate { get; set; }
/// <summary>
/// Severity of the logs as defined by MongoDB. Mapped to LogLevel
/// as defined by Microsoft.
/// D1-D2 mapped to Debug level. D3-D5 mapped Trace level.
/// </summary>
[JsonPropertyName("s")]
public string Severity { get; set; }
public LogLevel Level
{
get
{
if (string.IsNullOrEmpty(Severity))
return LogLevel.None;
switch (Severity)
{
case "F": return LogLevel.Critical;
case "E": return LogLevel.Error;
case "W": return LogLevel.Warning;
case "I": return LogLevel.Information;
case "D":
case "D1":
case "D2":
return LogLevel.Debug;
case "D3":
case "D4":
case "D5":
default:
return LogLevel.Trace;
}
}
}
[JsonPropertyName("c")]
public string Component { get; set; }
[JsonPropertyName("ctx")]
public string Context { get; set; }
[JsonPropertyName("id")]
public int? Id { get; set; }
[JsonPropertyName("msg")]
public string Message { get; set; }
[JsonPropertyName("tags")]
public IEnumerable<string> Tags { get; set; }
[JsonPropertyName("attr")]
public IDictionary<string, JsonElement> Attributes { get; set; }
}
class MongoDate
{
[JsonPropertyName("$date")]
public DateTime DateTime { get; set; }
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
namespace Mongo2Go.Helper
{
public static class MongodArguments
{
private const string ArgumentSeparator = "--";
private const string Space = " ";
/// <summary>
/// Returns the <paramref name="additionalMongodArguments" /> if it is verified that it does not contain any mongod argument already defined by Mongo2Go.
/// </summary>
/// <param name="existingMongodArguments">mongod arguments defined by Mongo2Go</param>
/// <param name="additionalMongodArguments">Additional mongod arguments</param>
/// <exception cref="T:System.ArgumentException"><paramref name="additionalMongodArguments" /> contains at least one mongod argument already defined by Mongo2Go</exception>
/// <returns>A string with the additional mongod arguments</returns>
public static string GetValidAdditionalArguments(string existingMongodArguments, string additionalMongodArguments)
{
if (string.IsNullOrWhiteSpace(additionalMongodArguments))
{
return string.Empty;
}
var existingMongodArgumentArray = existingMongodArguments.Trim().Split(new[] { ArgumentSeparator }, StringSplitOptions.RemoveEmptyEntries);
var existingMongodArgumentOptions = new List<string>();
for (var i = 0; i < existingMongodArgumentArray.Length; i++)
{
var argumentOptionSplit = existingMongodArgumentArray[i].Split(' ');
if (argumentOptionSplit.Length == 0
|| string.IsNullOrWhiteSpace(argumentOptionSplit[0].Trim()))
{
continue;
}
existingMongodArgumentOptions.Add(argumentOptionSplit[0].Trim());
}
var additionalMongodArgumentArray = additionalMongodArguments.Trim().Split(new[] { ArgumentSeparator }, StringSplitOptions.RemoveEmptyEntries);
var validAdditionalMongodArguments = new List<string>();
var duplicateMongodArguments = new List<string>();
for (var i = 0; i < additionalMongodArgumentArray.Length; i++)
{
var additionalArgument = additionalMongodArgumentArray[i].Trim();
var argumentOptionSplit = additionalArgument.Split(' ');
if (argumentOptionSplit.Length == 0
|| string.IsNullOrWhiteSpace(argumentOptionSplit[0].Trim()))
{
continue;
}
if (existingMongodArgumentOptions.Contains(argumentOptionSplit[0].Trim()))
{
duplicateMongodArguments.Add(argumentOptionSplit[0].Trim());
}
validAdditionalMongodArguments.Add(ArgumentSeparator + additionalArgument);
}
if (duplicateMongodArguments.Count != 0)
{
throw new ArgumentException($"mongod arguments defined by Mongo2Go ({string.Join(", ", existingMongodArgumentOptions)}) cannot be overriden. Please remove the following additional argument(s): {string.Join(", ", duplicateMongodArguments)}.");
}
return validAdditionalMongodArguments.Count == 0
? string.Empty
: Space + string.Join(" ", validAdditionalMongodArguments);
}
}
}

View File

@@ -0,0 +1,24 @@
#if NETSTANDARD2_0
using System;
namespace Mongo2Go.Helper
{
public static class NetStandard21Compatibility
{
/// <summary>
/// Returns a value indicating whether a specified string occurs within this <paramref name="string"/>, using the specified comparison rules.
/// </summary>
/// <param name="string">The string to operate on.</param>
/// <param name="value">The string to seek.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
/// <returns><see langword="true"/> if the <paramref name="value"/> parameter occurs within this string, or if <paramref name="value"/> is the empty string (""); otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/></exception>
public static bool Contains(this string @string, string value, StringComparison comparisonType)
{
if (@string == null) throw new ArgumentNullException(nameof(@string));
return @string.IndexOf(value, comparisonType) >= 0;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
using System;
namespace Mongo2Go.Helper
{
public class NoFreePortFoundException : Exception
{
public NoFreePortFoundException() { }
public NoFreePortFoundException(string message) : base(message) { }
public NoFreePortFoundException(string message, Exception inner) : base(message, inner) { }
}
}

View File

@@ -0,0 +1,37 @@
using System;
namespace Mongo2Go.Helper
{
/// <summary>
/// Intention: port numbers won't be assigned twice to avoid connection problems with integration tests
/// </summary>
public sealed class PortPool : IPortPool
{
private static readonly PortPool Instance = new PortPool();
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static PortPool()
{
}
// Singleton
private PortPool()
{
}
public static PortPool GetInstance
{
get { return Instance; }
}
/// <summary>
/// Returns and reserves a new port
/// </summary>
public int GetNextOpenPort()
{
IPortWatcher portWatcher = PortWatcherFactory.CreatePortWatcher();
return portWatcher.FindOpenPort();
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
namespace Mongo2Go.Helper
{
public class PortWatcher : IPortWatcher
{
public int FindOpenPort()
{
// Locate a free port on the local machine by binding a socket to
// an IPEndPoint using IPAddress.Any and port 0. The socket will
// select a free port.
int listeningPort = 0;
Socket portSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
IPEndPoint socketEndPoint = new IPEndPoint(IPAddress.Any, 0);
portSocket.Bind(socketEndPoint);
socketEndPoint = (IPEndPoint)portSocket.LocalEndPoint;
listeningPort = socketEndPoint.Port;
}
finally
{
portSocket.Close();
}
return listeningPort;
}
public bool IsPortAvailable(int portNumber)
{
IPEndPoint[] tcpConnInfoArray = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners();
return tcpConnInfoArray.All(endpoint => endpoint.Port != portNumber);
}
}
}

View File

@@ -0,0 +1,14 @@
using System.Runtime.InteropServices;
namespace Mongo2Go.Helper
{
public class PortWatcherFactory
{
public static IPortWatcher CreatePortWatcher()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
? (IPortWatcher) new UnixPortWatcher()
: new PortWatcher();
}
}
}

View File

@@ -0,0 +1,163 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
using System.Threading;
namespace Mongo2Go.Helper
{
public static class ProcessControl
{
public static WrappedProcess ProcessFactory(string fileName, string arguments)
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
WrappedProcess process = new WrappedProcess { StartInfo = startInfo };
return process;
}
public static ProcessOutput StartAndWaitForExit(Process process)
{
List<string> errorOutput = new List<string>();
List<string> standardOutput = new List<string>();
process.ErrorDataReceived += (sender, args) => errorOutput.Add(args.Data);
process.OutputDataReceived += (sender, args) => standardOutput.Add(args.Data);
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelErrorRead();
process.CancelOutputRead();
return new ProcessOutput(errorOutput, standardOutput);
}
/// <summary>
/// Reads from Output stream to determine if process is ready
/// </summary>
public static ProcessOutput StartAndWaitForReady(Process process, int timeoutInSeconds, string processReadyIdentifier, ILogger logger = null)
{
if (timeoutInSeconds < 1 ||
timeoutInSeconds > 10)
{
throw new ArgumentOutOfRangeException("timeoutInSeconds", "The amount in seconds should have a value between 1 and 10.");
}
// Determine when the process is ready, and store the error and standard outputs
// to eventually return them.
List<string> errorOutput = new List<string>();
List<string> standardOutput = new List<string>();
bool processReady = false;
void OnProcessOnErrorDataReceived(object sender, DataReceivedEventArgs args) => errorOutput.Add(args.Data);
void OnProcessOnOutputDataReceived(object sender, DataReceivedEventArgs args)
{
standardOutput.Add(args.Data);
if (!string.IsNullOrEmpty(args.Data) && args.Data.IndexOf(processReadyIdentifier, StringComparison.OrdinalIgnoreCase) >= 0)
{
processReady = true;
}
}
process.ErrorDataReceived += OnProcessOnErrorDataReceived;
process.OutputDataReceived += OnProcessOnOutputDataReceived;
if (logger == null)
WireLogsToConsoleAndDebugOutput(process);
else
WireLogsToLogger(process, logger);
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
int lastResortCounter = 0;
int timeOut = timeoutInSeconds * 10;
while (!processReady)
{
Thread.Sleep(100);
if (++lastResortCounter > timeOut)
{
// we waited X seconds.
// for any reason the detection did not worked, eg. the identifier changed
// lets assume everything is still ok
break;
}
}
//unsubscribing writing to list - to prevent memory overflow.
process.ErrorDataReceived -= OnProcessOnErrorDataReceived;
process.OutputDataReceived -= OnProcessOnOutputDataReceived;
return new ProcessOutput(errorOutput, standardOutput);
}
/// <summary>
/// Send the mongod process logs to .NET's console and debug outputs.
/// </summary>
/// <param name="process"></param>
private static void WireLogsToConsoleAndDebugOutput(Process process)
{
void DebugOutputHandler(object sender, DataReceivedEventArgs args) => Debug.WriteLine(args.Data);
void ConsoleOutputHandler(object sender, DataReceivedEventArgs args) => Console.WriteLine(args.Data);
//Writing to debug trace & console to enable test runners to capture the output
process.ErrorDataReceived += DebugOutputHandler;
process.ErrorDataReceived += ConsoleOutputHandler;
process.OutputDataReceived += DebugOutputHandler;
process.OutputDataReceived += ConsoleOutputHandler;
}
/// <summary>
/// Parses and redirects mongod logs to ILogger.
/// </summary>
/// <param name="process"></param>
/// <param name="logger"></param>
private static void WireLogsToLogger(Process process, ILogger logger)
{
// Parse the structured log and wire it to logger
void OnReceivingLogFromMongod(object sender, DataReceivedEventArgs args)
{
if (string.IsNullOrWhiteSpace(args.Data))
return;
try
{
var log = JsonSerializer.Deserialize<MongoLogStatement>(args.Data);
logger.Log(log.Level,
"{message} - {attributes} - {date} - {component} - {context} - {id} - {tags}",
log.Message, log.Attributes, log.MongoDate.DateTime, log.Component, log.Context, log.Id, log.Tags);
}
catch (Exception ex) when (ex is JsonException || ex is NotSupportedException)
{
logger.LogWarning(ex,
"Failed parsing the mongod logs {log}. It could be that the format has changed. " +
"See: https://docs.mongodb.com/manual/reference/log-messages/#std-label-log-message-json-output-format",
args.Data);
}
catch (Exception)
{
// Nothing else to do. Swallow the exception and do not wire the logs.
}
};
process.ErrorDataReceived += OnReceivingLogFromMongod;
process.OutputDataReceived += OnReceivingLogFromMongod;
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Mongo2Go.Helper
{
public class ProcessOutput
{
public ProcessOutput(IEnumerable<string> errorOutput, IEnumerable<string> standardOutput)
{
StandardOutput = standardOutput;
ErrorOutput = errorOutput;
}
public IEnumerable<string> StandardOutput { get; private set; }
public IEnumerable<string> ErrorOutput { get; private set; }
}
}

View File

@@ -0,0 +1,13 @@
using System.Diagnostics;
using System.Linq;
namespace Mongo2Go.Helper
{
public class ProcessWatcher : IProcessWatcher
{
public bool IsProcessRunning(string processName)
{
return Process.GetProcessesByName(processName).Any();
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Globalization;
namespace Mongo2Go.Helper
{
/// <summary>
/// saves about 40 keystrokes
/// </summary>
public static class StringFormatExtension
{
/// <summary>
/// Populates the template using the provided arguments and the invariant culture
/// </summary>
public static string Formatted(this string template, params object[] args)
{
return template.Formatted(CultureInfo.InvariantCulture, args);
}
/// <summary>
/// Populates the template using the provided arguments using the provided formatter
/// </summary>
public static string Formatted(this string template, IFormatProvider formatter, params object[] args)
{
return string.IsNullOrEmpty(template) ? string.Empty : string.Format(formatter, template, args);
}
}
}

View File

@@ -0,0 +1,46 @@
using System.Net;
using System.Net.Sockets;
namespace Mongo2Go.Helper
{
public class UnixPortWatcher : IPortWatcher
{
public int FindOpenPort ()
{
// Locate a free port on the local machine by binding a socket to
// an IPEndPoint using IPAddress.Any and port 0. The socket will
// select a free port.
int listeningPort = 0;
Socket portSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
IPEndPoint socketEndPoint = new IPEndPoint(IPAddress.Any, 0);
portSocket.Bind(socketEndPoint);
socketEndPoint = (IPEndPoint)portSocket.LocalEndPoint;
listeningPort = socketEndPoint.Port;
}
finally
{
portSocket.Close();
}
return listeningPort;
}
public bool IsPortAvailable (int portNumber)
{
TcpListener tcpListener = new TcpListener (IPAddress.Loopback, portNumber);
try {
tcpListener.Start ();
return true;
}
catch (SocketException) {
return false;
} finally
{
tcpListener.Stop ();
}
}
}
}

View File

@@ -0,0 +1,9 @@
using System.Diagnostics;
namespace Mongo2Go.Helper
{
public class WrappedProcess : Process
{
public bool DoNotKill { get; set; }
}
}

View File

@@ -0,0 +1,92 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;netstandard2.1</TargetFrameworks>
<Authors>Johannes Hoppe and many contributors</Authors>
<Description>Mongo2Go is a managed wrapper around MongoDB binaries. It targets .NET Framework 4.7.2 and .NET Standard 2.1.
This Nuget package contains the executables of mongod, mongoimport and mongoexport v4.4.4 for Windows, Linux and macOS.
Mongo2Go has two use cases:
1. Providing multiple, temporary and isolated MongoDB databases for integration tests
2. Providing a quick to set up MongoDB database for a local developer environment</Description>
<Company>HAUS HOPPE - ITS</Company>
<Copyright>Copyright © 2012-2025 Johannes Hoppe and many ❤️ contributors</Copyright>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageIcon>icon.png</PackageIcon>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/Mongo2Go/Mongo2Go</PackageProjectUrl>
<PackageReleaseNotes>https://github.com/Mongo2Go/Mongo2Go/releases</PackageReleaseNotes>
<PackageTags>MongoDB Mongo unit test integration runner</PackageTags>
<RepositoryUrl>https://github.com/Mongo2Go/Mongo2Go</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<AssemblyTitle>Mongo2Go</AssemblyTitle>
<AssemblyDescription>Mongo2Go is a managed wrapper around MongoDB binaries.</AssemblyDescription>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.1|AnyCPU'">
<WarningLevel>4</WarningLevel>
<NoWarn>1701;1702;1591;1573</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.1|AnyCPU'">
<WarningLevel>4</WarningLevel>
<NoWarn>1701;1702;1591;1573</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net48|AnyCPU'">
<NoWarn>1701;1702;1591;1573</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net48|AnyCPU'">
<NoWarn>1701;1702;1591;1573</NoWarn>
</PropertyGroup>
<PropertyGroup Label="Restoring">
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<RestoreLockedMode Condition="$(ContinuousIntegrationBuild) == 'true'">true</RestoreLockedMode>
</PropertyGroup>
<PropertyGroup Label="SourceLink">
<DebugType>embedded</DebugType>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>
<PropertyGroup Label="MinVer">
<MinVerTagPrefix>v</MinVerTagPrefix>
</PropertyGroup>
<ItemGroup>
<None Update="packages.lock.json" Visible="false" />
<None Include="../mongo2go_200_200.png" Visible="false">
<Pack>true</Pack>
<PackagePath>icon.png</PackagePath>
</None>
<None Include="../../tools/mongodb*/**" Visible="false">
<Pack>true</Pack>
<PackagePath>tools</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="all" />
<PackageReference Include="MinVer" Version="2.5.0" PrivateAssets="all" />
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
<PackageReference Include="System.Text.Json" Version="6.0.10" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Mongo2GoTests" />
</ItemGroup>
<Target Name="PrintPackageVersionForGitHubActions" AfterTargets="Pack">
<Message Importance="high" Text="version=$(PackageVersion)" />
<Message Importance="high" Text="nupkg-filename=$(PackageId).$(PackageVersion).nupkg" />
</Target>
</Project>

View File

@@ -0,0 +1,22 @@
namespace Mongo2Go
{
public static class MongoDbDefaults
{
public const string ProcessName = "mongod";
public const string MongodExecutable = "mongod";
public const string MongoExportExecutable = "mongoexport";
public const string MongoImportExecutable = "mongoimport";
public const int DefaultPort = 27017;
// but we don't want to get in trouble with productive systems
public const int TestStartPort = 27018;
public const string Lockfile = "mongod.lock";
public const int SingleNodeReplicaSetWaitTimeout = 10;
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Mongo2Go
{
public class MongoDbPortAlreadyTakenException : Exception
{
public MongoDbPortAlreadyTakenException() { }
public MongoDbPortAlreadyTakenException(string message) : base(message) { }
public MongoDbPortAlreadyTakenException(string message, Exception inner) : base(message, inner) { }
}
}

View File

@@ -0,0 +1,54 @@
using System;
namespace Mongo2Go
{
// IDisposable and friends
public partial class MongoDbRunner
{
~MongoDbRunner()
{
Dispose(false);
}
public bool Disposed { get; private set; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (Disposed)
{
return;
}
if (State != State.Running)
{
return;
}
if (disposing)
{
// we have no "managed resources" - but we leave this switch to avoid an FxCop CA1801 warnig
}
if (_mongoDbProcess != null)
{
_mongoDbProcess.Dispose();
}
// will be null if we are working in debugging mode (single instance)
if (_dataDirectoryWithPort != null)
{
// finally clean up the data directory we created previously
_fileSystem.DeleteFolder(_dataDirectoryWithPort);
}
Disposed = true;
State = State.Stopped;
}
}
}

View File

@@ -0,0 +1,221 @@
using Microsoft.Extensions.Logging;
using Mongo2Go.Helper;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Mongo2Go
{
/// <summary>
/// Mongo2Go main entry point
/// </summary>
public partial class MongoDbRunner : IDisposable
{
private readonly IMongoDbProcess _mongoDbProcess;
private readonly IFileSystem _fileSystem;
private readonly string _dataDirectoryWithPort;
private readonly int _port;
private readonly IMongoBinaryLocator _mongoBin;
/// <summary>
/// State of the current MongoDB instance
/// </summary>
public State State { get; private set; }
/// <summary>
/// Connections string that should be used to establish a connection the MongoDB instance
/// </summary>
public string ConnectionString { get; private set; }
/// <summary>
/// Starts Multiple MongoDB instances with each call
/// On dispose: kills them and deletes their data directory
/// </summary>
/// <param name="logger">(Optional) If null, mongod logs are wired to .NET's Console and Debug output (provided you haven't added the --quiet additional argument).
/// If not null, mongod logs are parsed and wired to the provided logger.</param>
/// <remarks>Should be used for integration tests</remarks>
public static MongoDbRunner Start(string dataDirectory = null, string binariesSearchPatternOverride = null, string binariesSearchDirectory = null, bool singleNodeReplSet = false, string additionalMongodArguments = null, ushort singleNodeReplSetWaitTimeout = MongoDbDefaults.SingleNodeReplicaSetWaitTimeout, ILogger logger = null)
{
if (dataDirectory == null) {
dataDirectory = GetTemporaryDataDirectory();
}
// this is required to support multiple instances to run in parallel
dataDirectory += Guid.NewGuid().ToString().Replace("-", "").Substring(0, 20);
return new MongoDbRunner(
PortPool.GetInstance,
new FileSystem(),
new MongoDbProcessStarter(),
new MongoBinaryLocator(binariesSearchPatternOverride, binariesSearchDirectory),
dataDirectory,
singleNodeReplSet,
additionalMongodArguments,
singleNodeReplSetWaitTimeout,
logger);
}
/// <summary>
/// !!!
/// This method is only used for an internal unit test. Use MongoDbRunner.Start() instead.
/// But if you find it to be useful (eg. to change every aspect on your own) feel free to implement the interfaces on your own!
/// </summary>
/// <remarks>see https://github.com/Mongo2Go/Mongo2Go/issues/41 </remarks>
[Obsolete("Use MongoDbRunner.Start() if possible.")]
public static MongoDbRunner StartUnitTest(
IPortPool portPool,
IFileSystem fileSystem,
IMongoDbProcessStarter processStarter,
IMongoBinaryLocator mongoBin,
string dataDirectory = null,
string additionalMongodArguments = null)
{
return new MongoDbRunner(
portPool,
fileSystem,
processStarter,
mongoBin,
dataDirectory,
additionalMongodArguments: additionalMongodArguments);
}
/// <summary>
/// Only starts one single MongoDB instance (even on multiple calls), does not kill it, does not delete data
/// </summary>
/// <remarks>
/// Should be used for local debugging only
/// WARNING: one single instance on one single machine is not a suitable setup for productive environments!!!
/// </remarks>
public static MongoDbRunner StartForDebugging(string dataDirectory = null, string binariesSearchPatternOverride = null, string binariesSearchDirectory = null, bool singleNodeReplSet = false, int port = MongoDbDefaults.DefaultPort, string additionalMongodArguments = null, ushort singleNodeReplSetWaitTimeout = MongoDbDefaults.SingleNodeReplicaSetWaitTimeout)
{
return new MongoDbRunner(
new ProcessWatcher(),
new PortWatcher(),
new FileSystem(),
new MongoDbProcessStarter(),
new MongoBinaryLocator(binariesSearchPatternOverride, binariesSearchDirectory), port, dataDirectory, singleNodeReplSet, additionalMongodArguments, singleNodeReplSetWaitTimeout);
}
/// <summary>
/// !!!
/// This method is only used for an internal unit test. Use MongoDbRunner.StartForDebugging() instead.
/// But if you find it to be useful (eg. to change every aspect on your own) feel free to implement the interfaces on your own!
/// </summary>
/// <remarks>see https://github.com/Mongo2Go/Mongo2Go/issues/41 </remarks>
[Obsolete("Use MongoDbRunner.StartForDebugging() if possible.")]
public static MongoDbRunner StartForDebuggingUnitTest(
IProcessWatcher processWatcher,
IPortWatcher portWatcher,
IFileSystem fileSystem,
IMongoDbProcessStarter processStarter,
IMongoBinaryLocator mongoBin,
string dataDirectory = null,
string additionalMongodArguments = null)
{
return new MongoDbRunner(
processWatcher,
portWatcher,
fileSystem,
processStarter,
mongoBin,
MongoDbDefaults.DefaultPort,
dataDirectory,
additionalMongodArguments: additionalMongodArguments);
}
/// <summary>
/// Executes Mongoimport on the associated MongoDB Instace
/// </summary>
public void Import(string database, string collection, string inputFile, bool drop, string additionalMongodArguments = null)
{
MongoImportExport.Import(_mongoBin.Directory, _port, database, collection, inputFile, drop, additionalMongodArguments);
}
/// <summary>
/// Executes Mongoexport on the associated MongoDB Instace
/// </summary>
public void Export(string database, string collection, string outputFile, string additionalMongodArguments = null)
{
MongoImportExport.Export(_mongoBin.Directory, _port, database, collection, outputFile, additionalMongodArguments);
}
/// <summary>
/// usage: local debugging
/// </summary>
private MongoDbRunner(IProcessWatcher processWatcher, IPortWatcher portWatcher, IFileSystem fileSystem, IMongoDbProcessStarter processStarter, IMongoBinaryLocator mongoBin, int port, string dataDirectory = null, bool singleNodeReplSet = false, string additionalMongodArguments = null, ushort singleNodeReplSetWaitTimeout = MongoDbDefaults.SingleNodeReplicaSetWaitTimeout)
{
_fileSystem = fileSystem;
_mongoBin = mongoBin;
_port = port;
MakeMongoBinarysExecutable();
ConnectionString = singleNodeReplSet
? "mongodb://127.0.0.1:{0}/?directConnection=true&replicaSet=singleNodeReplSet&readPreference=primary".Formatted(_port)
: "mongodb://127.0.0.1:{0}/".Formatted(_port);
if (processWatcher.IsProcessRunning(MongoDbDefaults.ProcessName) && !portWatcher.IsPortAvailable(_port))
{
State = State.AlreadyRunning;
return;
}
if (!portWatcher.IsPortAvailable(_port))
{
throw new MongoDbPortAlreadyTakenException("MongoDB can't be started. The TCP port {0} is already taken.".Formatted(_port));
}
if (dataDirectory == null) {
dataDirectory = GetTemporaryDataDirectory();
}
_fileSystem.CreateFolder(dataDirectory);
_fileSystem.DeleteFile("{0}{1}{2}".Formatted(dataDirectory, Path.DirectorySeparatorChar.ToString(), MongoDbDefaults.Lockfile));
_mongoDbProcess = processStarter.Start(_mongoBin.Directory, dataDirectory, _port, true, singleNodeReplSet, additionalMongodArguments, singleNodeReplSetWaitTimeout);
State = State.Running;
}
/// <summary>
/// usage: integration tests
/// </summary>
private MongoDbRunner(IPortPool portPool, IFileSystem fileSystem, IMongoDbProcessStarter processStarter, IMongoBinaryLocator mongoBin, string dataDirectory = null, bool singleNodeReplSet = false, string additionalMongodArguments = null, ushort singleNodeReplSetWaitTimeout = MongoDbDefaults.SingleNodeReplicaSetWaitTimeout, ILogger logger = null)
{
_fileSystem = fileSystem;
_port = portPool.GetNextOpenPort();
_mongoBin = mongoBin;
if (dataDirectory == null) {
dataDirectory = GetTemporaryDataDirectory();
}
MakeMongoBinarysExecutable();
ConnectionString = singleNodeReplSet
? "mongodb://127.0.0.1:{0}/?directConnection=true&replicaSet=singleNodeReplSet&readPreference=primary".Formatted(_port)
: "mongodb://127.0.0.1:{0}/".Formatted(_port);
_dataDirectoryWithPort = "{0}_{1}".Formatted(dataDirectory, _port);
_fileSystem.CreateFolder(_dataDirectoryWithPort);
_fileSystem.DeleteFile("{0}{1}{2}".Formatted(_dataDirectoryWithPort, Path.DirectorySeparatorChar.ToString(), MongoDbDefaults.Lockfile));
_mongoDbProcess = processStarter.Start(_mongoBin.Directory, _dataDirectoryWithPort, _port, singleNodeReplSet, additionalMongodArguments, singleNodeReplSetWaitTimeout, logger);
State = State.Running;
}
private void MakeMongoBinarysExecutable()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
_fileSystem.MakeFileExecutable(Path.Combine(_mongoBin.Directory, MongoDbDefaults.MongodExecutable));
_fileSystem.MakeFileExecutable(Path.Combine(_mongoBin.Directory, MongoDbDefaults.MongoExportExecutable));
_fileSystem.MakeFileExecutable(Path.Combine(_mongoBin.Directory, MongoDbDefaults.MongoImportExecutable));
}
}
private static string GetTemporaryDataDirectory() => Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Mongo2Go
{
public class MonogDbBinariesNotFoundException : Exception
{
public MonogDbBinariesNotFoundException() { }
public MonogDbBinariesNotFoundException(string message) : base(message) { }
public MonogDbBinariesNotFoundException(string message, Exception inner) : base(message, inner) { }
}
}

View File

@@ -0,0 +1,9 @@
namespace Mongo2Go
{
public enum State
{
Stopped,
Running,
AlreadyRunning
}
}

View File

@@ -0,0 +1,478 @@
{
"version": 1,
"dependencies": {
".NETFramework,Version=v4.7.2": {
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Direct",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4"
}
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net472": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[1.0.0, )",
"resolved": "1.0.0",
"contentHash": "aZyGyGg2nFSxix+xMkPmlmZSsnGQ3w+mIG23LTxJZHN+GPwTQ5FpPgDo7RMOq+Kcf5D4hFWfXkGhoGstawX13Q==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.0.0",
"Microsoft.SourceLink.Common": "1.0.0"
}
},
"MinVer": {
"type": "Direct",
"requested": "[2.5.0, )",
"resolved": "2.5.0",
"contentHash": "+vgY+COxnu93nZEVYScloRuboNRIYkElokxTdtKLt6isr/f6GllPt0oLfrHj7fzxgj7SC5xMZg5c2qvd6qyHDQ=="
},
"MongoDB.Driver": {
"type": "Direct",
"requested": "[3.1.0, )",
"resolved": "3.1.0",
"contentHash": "+O7lKaIl7VUHptE0hqTd7UY1G5KDp/o8S4upG7YL4uChMNKD/U6tz9i17nMGHaD/L2AiPLgaJcaDe2XACsegGA==",
"dependencies": {
"DnsClient": "1.6.1",
"Microsoft.Extensions.Logging.Abstractions": "2.0.0",
"MongoDB.Bson": "3.1.0",
"SharpCompress": "0.30.1",
"Snappier": "1.0.0",
"System.Buffers": "4.5.1",
"System.Net.Http": "4.3.4",
"System.Runtime.InteropServices.RuntimeInformation": "4.3.0",
"ZstdSharp.Port": "0.7.3"
}
},
"System.Text.Json": {
"type": "Direct",
"requested": "[6.0.10, )",
"resolved": "6.0.10",
"contentHash": "NSB0kDipxn2ychp88NXWfFRFlmi1bst/xynOutbnpEfRCT9JZkZ7KOmF/I/hNKo2dILiMGnqblm+j1sggdLB9g==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "6.0.0",
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4",
"System.Numerics.Vectors": "4.5.0",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encodings.Web": "6.0.0",
"System.Threading.Tasks.Extensions": "4.5.4",
"System.ValueTuple": "4.5.0"
}
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.6.1",
"contentHash": "4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0",
"System.Buffers": "4.5.1"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "z2fpmmt+1Jfl+ZnBki9nSP08S1/tbEOxFdsK1rSR+LBehIJz1Xv9/6qOOoGNqlwnAGGVGis1Oj6S8Kt9COEYlQ=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net472": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "0E7evZXHXaDYYiLRfpyXvCh+yzM2rNTyuZDI+ZO7UUqSc6GfjePiXTdqJGtgIKUwdI81tzQKmaWprnUiPj9hAw=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "G8DuQY8/DK5NN+3jm5wcMcd9QYD90UV7MiLmdljSJixi3U/vNaeBKmmXUqI4DJCOeWizIUEh4ALhSt58mR+5eg=="
},
"Microsoft.Win32.Registry": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==",
"dependencies": {
"System.Security.AccessControl": "5.0.0",
"System.Security.Principal.Windows": "5.0.0"
}
},
"MongoDB.Bson": {
"type": "Transitive",
"resolved": "3.1.0",
"contentHash": "3dhaZhz18B5vUoEP13o2j8A6zQfkHdZhwBvLZEjDJum4BTLLv1/Z8bt25UQEtpqvYwLgde4R6ekWZ7XAYUMxuw==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "5.0.0"
}
},
"SharpCompress": {
"type": "Transitive",
"resolved": "0.30.1",
"contentHash": "XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==",
"dependencies": {
"System.Memory": "4.5.4",
"System.Text.Encoding.CodePages": "5.0.0"
}
},
"Snappier": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==",
"dependencies": {
"System.Memory": "4.5.4",
"System.Runtime.CompilerServices.Unsafe": "4.7.1",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
},
"System.IO": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.5.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.Net.Http": {
"type": "Transitive",
"resolved": "4.3.4",
"contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==",
"dependencies": {
"System.Security.Cryptography.X509Certificates": "4.3.0"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
},
"System.Runtime": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Runtime.InteropServices.RuntimeInformation": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw=="
},
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==",
"dependencies": {
"System.Security.Principal.Windows": "5.0.0"
}
},
"System.Security.Cryptography.Algorithms": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==",
"dependencies": {
"System.IO": "4.3.0",
"System.Runtime": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0"
}
},
"System.Security.Cryptography.Encoding": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw=="
},
"System.Security.Cryptography.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg=="
},
"System.Security.Cryptography.X509Certificates": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==",
"dependencies": {
"System.Security.Cryptography.Algorithms": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0"
}
},
"System.Security.Principal.Windows": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA=="
},
"System.Text.Encoding.CodePages": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "5.0.0"
}
},
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.ValueTuple": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ=="
},
"ZstdSharp.Port": {
"type": "Transitive",
"resolved": "0.7.3",
"contentHash": "U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"System.Memory": "4.5.5"
}
}
},
".NETStandard,Version=v2.1": {
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Direct",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4"
}
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[1.0.0, )",
"resolved": "1.0.0",
"contentHash": "aZyGyGg2nFSxix+xMkPmlmZSsnGQ3w+mIG23LTxJZHN+GPwTQ5FpPgDo7RMOq+Kcf5D4hFWfXkGhoGstawX13Q==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.0.0",
"Microsoft.SourceLink.Common": "1.0.0"
}
},
"MinVer": {
"type": "Direct",
"requested": "[2.5.0, )",
"resolved": "2.5.0",
"contentHash": "+vgY+COxnu93nZEVYScloRuboNRIYkElokxTdtKLt6isr/f6GllPt0oLfrHj7fzxgj7SC5xMZg5c2qvd6qyHDQ=="
},
"MongoDB.Driver": {
"type": "Direct",
"requested": "[3.1.0, )",
"resolved": "3.1.0",
"contentHash": "+O7lKaIl7VUHptE0hqTd7UY1G5KDp/o8S4upG7YL4uChMNKD/U6tz9i17nMGHaD/L2AiPLgaJcaDe2XACsegGA==",
"dependencies": {
"DnsClient": "1.6.1",
"Microsoft.Extensions.Logging.Abstractions": "2.0.0",
"MongoDB.Bson": "3.1.0",
"SharpCompress": "0.30.1",
"Snappier": "1.0.0",
"System.Buffers": "4.5.1",
"ZstdSharp.Port": "0.7.3"
}
},
"System.Text.Json": {
"type": "Direct",
"requested": "[6.0.10, )",
"resolved": "6.0.10",
"contentHash": "NSB0kDipxn2ychp88NXWfFRFlmi1bst/xynOutbnpEfRCT9JZkZ7KOmF/I/hNKo2dILiMGnqblm+j1sggdLB9g==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "6.0.0",
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4",
"System.Numerics.Vectors": "4.5.0",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encodings.Web": "6.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.6.1",
"contentHash": "4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "z2fpmmt+1Jfl+ZnBki9nSP08S1/tbEOxFdsK1rSR+LBehIJz1Xv9/6qOOoGNqlwnAGGVGis1Oj6S8Kt9COEYlQ=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "G8DuQY8/DK5NN+3jm5wcMcd9QYD90UV7MiLmdljSJixi3U/vNaeBKmmXUqI4DJCOeWizIUEh4ALhSt58mR+5eg=="
},
"Microsoft.Win32.Registry": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4",
"System.Security.AccessControl": "5.0.0",
"System.Security.Principal.Windows": "5.0.0"
}
},
"MongoDB.Bson": {
"type": "Transitive",
"resolved": "3.1.0",
"contentHash": "3dhaZhz18B5vUoEP13o2j8A6zQfkHdZhwBvLZEjDJum4BTLLv1/Z8bt25UQEtpqvYwLgde4R6ekWZ7XAYUMxuw==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "5.0.0"
}
},
"SharpCompress": {
"type": "Transitive",
"resolved": "0.30.1",
"contentHash": "XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==",
"dependencies": {
"System.Text.Encoding.CodePages": "5.0.0"
}
},
"Snappier": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.7.1"
}
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.4.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==",
"dependencies": {
"System.Security.Principal.Windows": "5.0.0"
}
},
"System.Security.Principal.Windows": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA=="
},
"System.Text.Encoding.CodePages": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "5.0.0"
}
},
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"ZstdSharp.Port": {
"type": "Transitive",
"resolved": "0.7.3",
"contentHash": "U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
}
}
}
}