refactor: use file scoped namespaces (#398)
This commit is contained in:
Родитель
fee6fb8f1e
Коммит
0f3fa8a844
|
@ -2,41 +2,40 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public static class AsyncExecution
|
||||
{
|
||||
public static class AsyncExecution
|
||||
public static async Task<T> ExecuteWithTimeoutAsync<T>(Func<Task<T>> toExecute, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
public static async Task<T> ExecuteWithTimeoutAsync<T>(Func<Task<T>> toExecute, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
if (toExecute == null)
|
||||
{
|
||||
if (toExecute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(toExecute));
|
||||
}
|
||||
|
||||
var work = Task.Run(toExecute);
|
||||
|
||||
var completedInTime = await Task.Run(() => work.Wait(timeout));
|
||||
if (!completedInTime)
|
||||
{
|
||||
throw new TimeoutException($"The execution did not complete in the alotted time ({timeout.TotalSeconds} seconds) and has been terminated prior to completion");
|
||||
}
|
||||
|
||||
return await work;
|
||||
throw new ArgumentNullException(nameof(toExecute));
|
||||
}
|
||||
|
||||
public static async Task ExecuteVoidWithTimeoutAsync(Action toExecute, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (toExecute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(toExecute));
|
||||
}
|
||||
var work = Task.Run(toExecute);
|
||||
|
||||
var work = Task.Run(toExecute);
|
||||
var completedInTime = await Task.Run(() => work.Wait(timeout));
|
||||
if (!completedInTime)
|
||||
{
|
||||
throw new TimeoutException($"The execution did not complete in the alotted time ({timeout.TotalSeconds} seconds) and has been terminated prior to completion");
|
||||
}
|
||||
var completedInTime = await Task.Run(() => work.Wait(timeout));
|
||||
if (!completedInTime)
|
||||
{
|
||||
throw new TimeoutException($"The execution did not complete in the alotted time ({timeout.TotalSeconds} seconds) and has been terminated prior to completion");
|
||||
}
|
||||
|
||||
return await work;
|
||||
}
|
||||
|
||||
public static async Task ExecuteVoidWithTimeoutAsync(Action toExecute, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (toExecute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(toExecute));
|
||||
}
|
||||
|
||||
var work = Task.Run(toExecute);
|
||||
var completedInTime = await Task.Run(() => work.Wait(timeout));
|
||||
if (!completedInTime)
|
||||
{
|
||||
throw new TimeoutException($"The execution did not complete in the alotted time ({timeout.TotalSeconds} seconds) and has been terminated prior to completion");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public class Column
|
||||
{
|
||||
public class Column
|
||||
{
|
||||
public int Width { get; set; }
|
||||
public int Width { get; set; }
|
||||
|
||||
public string Header { get; set; }
|
||||
public string Header { get; set; }
|
||||
|
||||
public string Format { get; set; }
|
||||
}
|
||||
public string Format { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
@ -10,149 +10,148 @@ using System.Threading.Tasks;
|
|||
using Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[Export(typeof(ICommandLineInvocationService))]
|
||||
public class CommandLineInvocationService : ICommandLineInvocationService
|
||||
{
|
||||
[Export(typeof(ICommandLineInvocationService))]
|
||||
public class CommandLineInvocationService : ICommandLineInvocationService
|
||||
private readonly IDictionary<string, string> commandLocatableCache = new ConcurrentDictionary<string, string>();
|
||||
|
||||
public async Task<bool> CanCommandBeLocated(string command, IEnumerable<string> additionalCandidateCommands = null, DirectoryInfo workingDirectory = null, params string[] parameters)
|
||||
{
|
||||
private readonly IDictionary<string, string> commandLocatableCache = new ConcurrentDictionary<string, string>();
|
||||
|
||||
public async Task<bool> CanCommandBeLocated(string command, IEnumerable<string> additionalCandidateCommands = null, DirectoryInfo workingDirectory = null, params string[] parameters)
|
||||
additionalCandidateCommands ??= Enumerable.Empty<string>();
|
||||
parameters ??= new string[0];
|
||||
var allCommands = new[] { command }.Concat(additionalCandidateCommands);
|
||||
if (!this.commandLocatableCache.TryGetValue(command, out var validCommand))
|
||||
{
|
||||
additionalCandidateCommands ??= Enumerable.Empty<string>();
|
||||
parameters ??= new string[0];
|
||||
var allCommands = new[] { command }.Concat(additionalCandidateCommands);
|
||||
if (!this.commandLocatableCache.TryGetValue(command, out var validCommand))
|
||||
foreach (var commandToTry in allCommands)
|
||||
{
|
||||
foreach (var commandToTry in allCommands)
|
||||
using var record = new CommandLineInvocationTelemetryRecord();
|
||||
|
||||
var joinedParameters = string.Join(" ", parameters);
|
||||
try
|
||||
{
|
||||
using var record = new CommandLineInvocationTelemetryRecord();
|
||||
var result = await RunProcessAsync(commandToTry, joinedParameters, workingDirectory);
|
||||
record.Track(result, commandToTry, joinedParameters);
|
||||
|
||||
var joinedParameters = string.Join(" ", parameters);
|
||||
try
|
||||
if (result.ExitCode == 0)
|
||||
{
|
||||
var result = await RunProcessAsync(commandToTry, joinedParameters, workingDirectory);
|
||||
record.Track(result, commandToTry, joinedParameters);
|
||||
|
||||
if (result.ExitCode == 0)
|
||||
{
|
||||
this.commandLocatableCache[command] = validCommand = commandToTry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is Win32Exception || ex is FileNotFoundException || ex is PlatformNotSupportedException)
|
||||
{
|
||||
// When we get an exception indicating the command cannot be found.
|
||||
record.Track(ex, commandToTry, joinedParameters);
|
||||
this.commandLocatableCache[command] = validCommand = commandToTry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !string.IsNullOrWhiteSpace(validCommand);
|
||||
}
|
||||
|
||||
public async Task<CommandLineExecutionResult> ExecuteCommand(string command, IEnumerable<string> additionalCandidateCommands = null, DirectoryInfo workingDirectory = null, params string[] parameters)
|
||||
{
|
||||
var isCommandLocatable = await this.CanCommandBeLocated(command, additionalCandidateCommands);
|
||||
if (!isCommandLocatable)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(this.ExecuteCommand)} was called with a command that could not be located: `{command}`!");
|
||||
}
|
||||
|
||||
if (workingDirectory != null && !Directory.Exists(workingDirectory.FullName))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(this.ExecuteCommand)} was called with a working directory that could not be located: `{workingDirectory.FullName}`");
|
||||
}
|
||||
|
||||
using var record = new CommandLineInvocationTelemetryRecord();
|
||||
|
||||
var pathToRun = this.commandLocatableCache[command];
|
||||
var joinedParameters = string.Join(" ", parameters);
|
||||
try
|
||||
{
|
||||
var result = await RunProcessAsync(pathToRun, joinedParameters, workingDirectory);
|
||||
record.Track(result, pathToRun, joinedParameters);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
record.Track(ex, pathToRun, joinedParameters);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCommandLineExecution()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> CanCommandBeLocated(string command, IEnumerable<string> additionalCandidateCommands = null, params string[] parameters)
|
||||
{
|
||||
return await this.CanCommandBeLocated(command, additionalCandidateCommands, workingDirectory: null, parameters);
|
||||
}
|
||||
|
||||
public async Task<CommandLineExecutionResult> ExecuteCommand(string command, IEnumerable<string> additionalCandidateCommands = null, params string[] parameters)
|
||||
{
|
||||
return await this.ExecuteCommand(command, additionalCandidateCommands, workingDirectory: null, parameters);
|
||||
}
|
||||
|
||||
private static Task<CommandLineExecutionResult> RunProcessAsync(string fileName, string parameters, DirectoryInfo workingDirectory = null)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<CommandLineExecutionResult>();
|
||||
|
||||
if (fileName.EndsWith(".cmd") || fileName.EndsWith(".bat"))
|
||||
{
|
||||
// If a script attempts to find its location using "%dp0", that can return the wrong path (current
|
||||
// working directory) unless the script is run via "cmd /C". An example is "ant.bat".
|
||||
parameters = $"/C {fileName} {parameters}";
|
||||
fileName = "cmd.exe";
|
||||
}
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo =
|
||||
catch (Exception ex) when (ex is Win32Exception || ex is FileNotFoundException || ex is PlatformNotSupportedException)
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = parameters,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
},
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
|
||||
if (workingDirectory != null)
|
||||
{
|
||||
process.StartInfo.WorkingDirectory = workingDirectory.FullName;
|
||||
// When we get an exception indicating the command cannot be found.
|
||||
record.Track(ex, commandToTry, joinedParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errorText = string.Empty;
|
||||
var stdOutText = string.Empty;
|
||||
return !string.IsNullOrWhiteSpace(validCommand);
|
||||
}
|
||||
|
||||
var t1 = new Task(() =>
|
||||
{
|
||||
errorText = process.StandardError.ReadToEnd();
|
||||
});
|
||||
var t2 = new Task(() =>
|
||||
{
|
||||
stdOutText = process.StandardOutput.ReadToEnd();
|
||||
});
|
||||
public async Task<CommandLineExecutionResult> ExecuteCommand(string command, IEnumerable<string> additionalCandidateCommands = null, DirectoryInfo workingDirectory = null, params string[] parameters)
|
||||
{
|
||||
var isCommandLocatable = await this.CanCommandBeLocated(command, additionalCandidateCommands);
|
||||
if (!isCommandLocatable)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(this.ExecuteCommand)} was called with a command that could not be located: `{command}`!");
|
||||
}
|
||||
|
||||
process.Exited += (sender, args) =>
|
||||
{
|
||||
Task.WaitAll(t1, t2);
|
||||
tcs.SetResult(new CommandLineExecutionResult { ExitCode = process.ExitCode, StdErr = errorText, StdOut = stdOutText });
|
||||
process.Dispose();
|
||||
};
|
||||
if (workingDirectory != null && !Directory.Exists(workingDirectory.FullName))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(this.ExecuteCommand)} was called with a working directory that could not be located: `{workingDirectory.FullName}`");
|
||||
}
|
||||
|
||||
process.Start();
|
||||
t1.Start();
|
||||
t2.Start();
|
||||
using var record = new CommandLineInvocationTelemetryRecord();
|
||||
|
||||
return tcs.Task;
|
||||
var pathToRun = this.commandLocatableCache[command];
|
||||
var joinedParameters = string.Join(" ", parameters);
|
||||
try
|
||||
{
|
||||
var result = await RunProcessAsync(pathToRun, joinedParameters, workingDirectory);
|
||||
record.Track(result, pathToRun, joinedParameters);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
record.Track(ex, pathToRun, joinedParameters);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCommandLineExecution()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> CanCommandBeLocated(string command, IEnumerable<string> additionalCandidateCommands = null, params string[] parameters)
|
||||
{
|
||||
return await this.CanCommandBeLocated(command, additionalCandidateCommands, workingDirectory: null, parameters);
|
||||
}
|
||||
|
||||
public async Task<CommandLineExecutionResult> ExecuteCommand(string command, IEnumerable<string> additionalCandidateCommands = null, params string[] parameters)
|
||||
{
|
||||
return await this.ExecuteCommand(command, additionalCandidateCommands, workingDirectory: null, parameters);
|
||||
}
|
||||
|
||||
private static Task<CommandLineExecutionResult> RunProcessAsync(string fileName, string parameters, DirectoryInfo workingDirectory = null)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<CommandLineExecutionResult>();
|
||||
|
||||
if (fileName.EndsWith(".cmd") || fileName.EndsWith(".bat"))
|
||||
{
|
||||
// If a script attempts to find its location using "%dp0", that can return the wrong path (current
|
||||
// working directory) unless the script is run via "cmd /C". An example is "ant.bat".
|
||||
parameters = $"/C {fileName} {parameters}";
|
||||
fileName = "cmd.exe";
|
||||
}
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo =
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = parameters,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
},
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
|
||||
if (workingDirectory != null)
|
||||
{
|
||||
process.StartInfo.WorkingDirectory = workingDirectory.FullName;
|
||||
}
|
||||
|
||||
var errorText = string.Empty;
|
||||
var stdOutText = string.Empty;
|
||||
|
||||
var t1 = new Task(() =>
|
||||
{
|
||||
errorText = process.StandardError.ReadToEnd();
|
||||
});
|
||||
var t2 = new Task(() =>
|
||||
{
|
||||
stdOutText = process.StandardOutput.ReadToEnd();
|
||||
});
|
||||
|
||||
process.Exited += (sender, args) =>
|
||||
{
|
||||
Task.WaitAll(t1, t2);
|
||||
tcs.SetResult(new CommandLineExecutionResult { ExitCode = process.ExitCode, StdErr = errorText, StdOut = stdOutText });
|
||||
process.Dispose();
|
||||
};
|
||||
|
||||
process.Start();
|
||||
t1.Start();
|
||||
t2.Start();
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.ComponentDetection.Contracts.TypedComponent;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
{
|
||||
public class ComponentComparer : EqualityComparer<TypedComponent>
|
||||
{
|
||||
public override bool Equals(TypedComponent t0, TypedComponent t1)
|
||||
{
|
||||
return t0.Id.Equals(t1.Id);
|
||||
}
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public override int GetHashCode(TypedComponent typedComponent)
|
||||
{
|
||||
return typedComponent.Id.GetHashCode();
|
||||
}
|
||||
public class ComponentComparer : EqualityComparer<TypedComponent>
|
||||
{
|
||||
public override bool Equals(TypedComponent t0, TypedComponent t1)
|
||||
{
|
||||
return t0.Id.Equals(t1.Id);
|
||||
}
|
||||
|
||||
public override int GetHashCode(TypedComponent typedComponent)
|
||||
{
|
||||
return typedComponent.Id.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
using System.IO;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public class ComponentStream : IComponentStream
|
||||
{
|
||||
public class ComponentStream : IComponentStream
|
||||
{
|
||||
public Stream Stream { get; set; }
|
||||
public Stream Stream { get; set; }
|
||||
|
||||
public string Pattern { get; set; }
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public string Location { get; set; }
|
||||
}
|
||||
public string Location { get; set; }
|
||||
}
|
||||
|
|
|
@ -4,63 +4,62 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public class ComponentStreamEnumerable : IEnumerable<IComponentStream>
|
||||
{
|
||||
public class ComponentStreamEnumerable : IEnumerable<IComponentStream>
|
||||
public ComponentStreamEnumerable(IEnumerable<MatchedFile> fileEnumerable, ILogger logger)
|
||||
{
|
||||
public ComponentStreamEnumerable(IEnumerable<MatchedFile> fileEnumerable, ILogger logger)
|
||||
this.ToEnumerate = fileEnumerable;
|
||||
this.Logger = logger;
|
||||
}
|
||||
|
||||
private IEnumerable<MatchedFile> ToEnumerate { get; }
|
||||
|
||||
private ILogger Logger { get; }
|
||||
|
||||
public IEnumerator<IComponentStream> GetEnumerator()
|
||||
{
|
||||
foreach (var filePairing in this.ToEnumerate)
|
||||
{
|
||||
this.ToEnumerate = fileEnumerable;
|
||||
this.Logger = logger;
|
||||
if (!filePairing.File.Exists)
|
||||
{
|
||||
this.Logger.LogWarning($"File {filePairing.File.FullName} does not exist on disk.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
using var stream = this.SafeOpenFile(filePairing.File);
|
||||
|
||||
if (stream == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return new ComponentStream { Stream = stream, Pattern = filePairing.Pattern, Location = filePairing.File.FullName };
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<MatchedFile> ToEnumerate { get; }
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
private ILogger Logger { get; }
|
||||
|
||||
public IEnumerator<IComponentStream> GetEnumerator()
|
||||
private Stream SafeOpenFile(FileInfo file)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var filePairing in this.ToEnumerate)
|
||||
{
|
||||
if (!filePairing.File.Exists)
|
||||
{
|
||||
this.Logger.LogWarning($"File {filePairing.File.FullName} does not exist on disk.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
using var stream = this.SafeOpenFile(filePairing.File);
|
||||
|
||||
if (stream == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return new ComponentStream { Stream = stream, Pattern = filePairing.Pattern, Location = filePairing.File.FullName };
|
||||
}
|
||||
return file.OpenRead();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
this.Logger.LogWarning($"Unauthorized access exception caught when trying to open {file.FullName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
private Stream SafeOpenFile(FileInfo file)
|
||||
catch (Exception e)
|
||||
{
|
||||
try
|
||||
{
|
||||
return file.OpenRead();
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
this.Logger.LogWarning($"Unauthorized access exception caught when trying to open {file.FullName}");
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogWarning($"Unhandled exception caught when trying to open {file.FullName}");
|
||||
this.Logger.LogException(e, isError: false);
|
||||
return null;
|
||||
}
|
||||
this.Logger.LogWarning($"Unhandled exception caught when trying to open {file.FullName}");
|
||||
this.Logger.LogException(e, isError: false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,28 +4,27 @@ using System.Composition;
|
|||
using System.IO;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[Export(typeof(IComponentStreamEnumerableFactory))]
|
||||
[Shared]
|
||||
public class ComponentStreamEnumerableFactory : IComponentStreamEnumerableFactory
|
||||
{
|
||||
[Export(typeof(IComponentStreamEnumerableFactory))]
|
||||
[Shared]
|
||||
public class ComponentStreamEnumerableFactory : IComponentStreamEnumerableFactory
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
[Import]
|
||||
public IPathUtilityService PathUtilityService { get; set; }
|
||||
|
||||
public IEnumerable<IComponentStream> GetComponentStreams(DirectoryInfo directory, IEnumerable<string> searchPatterns, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true)
|
||||
{
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
var enumerable = new SafeFileEnumerable(directory, searchPatterns, this.Logger, this.PathUtilityService, directoryExclusionPredicate, recursivelyScanDirectories);
|
||||
return new ComponentStreamEnumerable(enumerable, this.Logger);
|
||||
}
|
||||
|
||||
[Import]
|
||||
public IPathUtilityService PathUtilityService { get; set; }
|
||||
|
||||
public IEnumerable<IComponentStream> GetComponentStreams(DirectoryInfo directory, IEnumerable<string> searchPatterns, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true)
|
||||
{
|
||||
var enumerable = new SafeFileEnumerable(directory, searchPatterns, this.Logger, this.PathUtilityService, directoryExclusionPredicate, recursivelyScanDirectories);
|
||||
return new ComponentStreamEnumerable(enumerable, this.Logger);
|
||||
}
|
||||
|
||||
public IEnumerable<IComponentStream> GetComponentStreams(DirectoryInfo directory, Func<FileInfo, bool> fileMatchingPredicate, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true)
|
||||
{
|
||||
var enumerable = new SafeFileEnumerable(directory, fileMatchingPredicate, this.Logger, this.PathUtilityService, directoryExclusionPredicate, recursivelyScanDirectories);
|
||||
return new ComponentStreamEnumerable(enumerable, this.Logger);
|
||||
}
|
||||
public IEnumerable<IComponentStream> GetComponentStreams(DirectoryInfo directory, Func<FileInfo, bool> fileMatchingPredicate, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true)
|
||||
{
|
||||
var enumerable = new SafeFileEnumerable(directory, fileMatchingPredicate, this.Logger, this.PathUtilityService, directoryExclusionPredicate, recursivelyScanDirectories);
|
||||
return new ComponentStreamEnumerable(enumerable, this.Logger);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
using System;
|
||||
using System.Composition;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[Export(typeof(IConsoleWritingService))]
|
||||
public class ConsoleWritingService : IConsoleWritingService
|
||||
{
|
||||
[Export(typeof(IConsoleWritingService))]
|
||||
public class ConsoleWritingService : IConsoleWritingService
|
||||
public void Write(string content)
|
||||
{
|
||||
public void Write(string content)
|
||||
{
|
||||
Console.Write(content);
|
||||
}
|
||||
Console.Write(content);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
@ -10,202 +10,201 @@ using Microsoft.ComponentDetection.Contracts.TypedComponent;
|
|||
|
||||
[assembly: InternalsVisibleTo("Microsoft.ComponentDetection.Common.Tests")]
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.DependencyGraph
|
||||
namespace Microsoft.ComponentDetection.Common.DependencyGraph;
|
||||
|
||||
public class ComponentRecorder : IComponentRecorder
|
||||
{
|
||||
public class ComponentRecorder : IComponentRecorder
|
||||
private readonly ILogger log;
|
||||
|
||||
private readonly ConcurrentBag<SingleFileComponentRecorder> singleFileRecorders = new ConcurrentBag<SingleFileComponentRecorder>();
|
||||
|
||||
private readonly bool enableManualTrackingOfExplicitReferences;
|
||||
|
||||
public ComponentRecorder(ILogger log = null, bool enableManualTrackingOfExplicitReferences = true)
|
||||
{
|
||||
this.log = log;
|
||||
this.enableManualTrackingOfExplicitReferences = enableManualTrackingOfExplicitReferences;
|
||||
}
|
||||
|
||||
public TypedComponent GetComponent(string componentId)
|
||||
{
|
||||
return this.singleFileRecorders.Select(x => x.GetComponent(componentId)?.Component).FirstOrDefault(x => x != null);
|
||||
}
|
||||
|
||||
public IEnumerable<DetectedComponent> GetDetectedComponents()
|
||||
{
|
||||
IEnumerable<DetectedComponent> detectedComponents;
|
||||
if (this.singleFileRecorders == null)
|
||||
{
|
||||
return Enumerable.Empty<DetectedComponent>();
|
||||
}
|
||||
|
||||
detectedComponents = this.singleFileRecorders
|
||||
.Select(singleFileRecorder => singleFileRecorder.GetDetectedComponents().Values)
|
||||
.SelectMany(x => x)
|
||||
.GroupBy(x => x.Component.Id)
|
||||
.Select(grouping =>
|
||||
{
|
||||
// We pick a winner here -- any stateful props could get lost at this point. Only stateful prop still outstanding is ContainerDetails.
|
||||
var winningDetectedComponent = grouping.First();
|
||||
foreach (var component in grouping)
|
||||
{
|
||||
foreach (var containerDetailId in component.ContainerDetailIds)
|
||||
{
|
||||
winningDetectedComponent.ContainerDetailIds.Add(containerDetailId);
|
||||
}
|
||||
}
|
||||
|
||||
return winningDetectedComponent;
|
||||
})
|
||||
.ToImmutableList();
|
||||
|
||||
return detectedComponents;
|
||||
}
|
||||
|
||||
public ISingleFileComponentRecorder CreateSingleFileComponentRecorder(string location)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(location))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(location));
|
||||
}
|
||||
|
||||
var matching = this.singleFileRecorders.FirstOrDefault(x => x.ManifestFileLocation == location);
|
||||
if (matching == null)
|
||||
{
|
||||
matching = new SingleFileComponentRecorder(location, this, this.enableManualTrackingOfExplicitReferences, this.log);
|
||||
this.singleFileRecorders.Add(matching);
|
||||
}
|
||||
|
||||
return matching;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, IDependencyGraph> GetDependencyGraphsByLocation()
|
||||
{
|
||||
return this.singleFileRecorders.Where(x => x.DependencyGraph.HasComponents())
|
||||
.ToImmutableDictionary(x => x.ManifestFileLocation, x => x.DependencyGraph as IDependencyGraph);
|
||||
}
|
||||
|
||||
internal DependencyGraph GetDependencyGraphForLocation(string location)
|
||||
{
|
||||
return this.singleFileRecorders.Single(x => x.ManifestFileLocation == location).DependencyGraph;
|
||||
}
|
||||
|
||||
public class SingleFileComponentRecorder : ISingleFileComponentRecorder
|
||||
{
|
||||
private readonly ILogger log;
|
||||
|
||||
private readonly ConcurrentBag<SingleFileComponentRecorder> singleFileRecorders = new ConcurrentBag<SingleFileComponentRecorder>();
|
||||
private readonly ConcurrentDictionary<string, DetectedComponent> detectedComponentsInternal = new ConcurrentDictionary<string, DetectedComponent>();
|
||||
|
||||
private readonly bool enableManualTrackingOfExplicitReferences;
|
||||
private readonly ComponentRecorder recorder;
|
||||
|
||||
public ComponentRecorder(ILogger log = null, bool enableManualTrackingOfExplicitReferences = true)
|
||||
private readonly object registerUsageLock = new object();
|
||||
|
||||
public SingleFileComponentRecorder(string location, ComponentRecorder recorder, bool enableManualTrackingOfExplicitReferences, ILogger log)
|
||||
{
|
||||
this.ManifestFileLocation = location;
|
||||
this.recorder = recorder;
|
||||
this.log = log;
|
||||
this.enableManualTrackingOfExplicitReferences = enableManualTrackingOfExplicitReferences;
|
||||
this.DependencyGraph = new DependencyGraph(enableManualTrackingOfExplicitReferences);
|
||||
}
|
||||
|
||||
public TypedComponent GetComponent(string componentId)
|
||||
public string ManifestFileLocation { get; }
|
||||
|
||||
IDependencyGraph ISingleFileComponentRecorder.DependencyGraph => this.DependencyGraph;
|
||||
|
||||
internal DependencyGraph DependencyGraph { get; }
|
||||
|
||||
public DetectedComponent GetComponent(string componentId)
|
||||
{
|
||||
return this.singleFileRecorders.Select(x => x.GetComponent(componentId)?.Component).FirstOrDefault(x => x != null);
|
||||
if (this.detectedComponentsInternal.TryGetValue(componentId, out var detectedComponent))
|
||||
{
|
||||
return detectedComponent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<DetectedComponent> GetDetectedComponents()
|
||||
public IReadOnlyDictionary<string, DetectedComponent> GetDetectedComponents()
|
||||
{
|
||||
IEnumerable<DetectedComponent> detectedComponents;
|
||||
if (this.singleFileRecorders == null)
|
||||
{
|
||||
return Enumerable.Empty<DetectedComponent>();
|
||||
}
|
||||
|
||||
detectedComponents = this.singleFileRecorders
|
||||
.Select(singleFileRecorder => singleFileRecorder.GetDetectedComponents().Values)
|
||||
.SelectMany(x => x)
|
||||
.GroupBy(x => x.Component.Id)
|
||||
.Select(grouping =>
|
||||
{
|
||||
// We pick a winner here -- any stateful props could get lost at this point. Only stateful prop still outstanding is ContainerDetails.
|
||||
var winningDetectedComponent = grouping.First();
|
||||
foreach (var component in grouping)
|
||||
{
|
||||
foreach (var containerDetailId in component.ContainerDetailIds)
|
||||
{
|
||||
winningDetectedComponent.ContainerDetailIds.Add(containerDetailId);
|
||||
}
|
||||
}
|
||||
|
||||
return winningDetectedComponent;
|
||||
})
|
||||
.ToImmutableList();
|
||||
|
||||
return detectedComponents;
|
||||
// Should this be immutable?
|
||||
return this.detectedComponentsInternal;
|
||||
}
|
||||
|
||||
public ISingleFileComponentRecorder CreateSingleFileComponentRecorder(string location)
|
||||
public void RegisterUsage(
|
||||
DetectedComponent detectedComponent,
|
||||
bool isExplicitReferencedDependency = false,
|
||||
string parentComponentId = null,
|
||||
bool? isDevelopmentDependency = null,
|
||||
DependencyScope? dependencyScope = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(location))
|
||||
if (detectedComponent == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(location));
|
||||
throw new ArgumentNullException(paramName: nameof(detectedComponent));
|
||||
}
|
||||
|
||||
var matching = this.singleFileRecorders.FirstOrDefault(x => x.ManifestFileLocation == location);
|
||||
if (matching == null)
|
||||
if (detectedComponent.Component == null)
|
||||
{
|
||||
matching = new SingleFileComponentRecorder(location, this, this.enableManualTrackingOfExplicitReferences, this.log);
|
||||
this.singleFileRecorders.Add(matching);
|
||||
throw new ArgumentException(Resources.MissingComponentId);
|
||||
}
|
||||
|
||||
return matching;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, IDependencyGraph> GetDependencyGraphsByLocation()
|
||||
{
|
||||
return this.singleFileRecorders.Where(x => x.DependencyGraph.HasComponents())
|
||||
.ToImmutableDictionary(x => x.ManifestFileLocation, x => x.DependencyGraph as IDependencyGraph);
|
||||
}
|
||||
|
||||
internal DependencyGraph GetDependencyGraphForLocation(string location)
|
||||
{
|
||||
return this.singleFileRecorders.Single(x => x.ManifestFileLocation == location).DependencyGraph;
|
||||
}
|
||||
|
||||
public class SingleFileComponentRecorder : ISingleFileComponentRecorder
|
||||
{
|
||||
private readonly ILogger log;
|
||||
|
||||
private readonly ConcurrentDictionary<string, DetectedComponent> detectedComponentsInternal = new ConcurrentDictionary<string, DetectedComponent>();
|
||||
|
||||
private readonly ComponentRecorder recorder;
|
||||
|
||||
private readonly object registerUsageLock = new object();
|
||||
|
||||
public SingleFileComponentRecorder(string location, ComponentRecorder recorder, bool enableManualTrackingOfExplicitReferences, ILogger log)
|
||||
{
|
||||
this.ManifestFileLocation = location;
|
||||
this.recorder = recorder;
|
||||
this.log = log;
|
||||
this.DependencyGraph = new DependencyGraph(enableManualTrackingOfExplicitReferences);
|
||||
}
|
||||
|
||||
public string ManifestFileLocation { get; }
|
||||
|
||||
IDependencyGraph ISingleFileComponentRecorder.DependencyGraph => this.DependencyGraph;
|
||||
|
||||
internal DependencyGraph DependencyGraph { get; }
|
||||
|
||||
public DetectedComponent GetComponent(string componentId)
|
||||
{
|
||||
if (this.detectedComponentsInternal.TryGetValue(componentId, out var detectedComponent))
|
||||
{
|
||||
return detectedComponent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, DetectedComponent> GetDetectedComponents()
|
||||
{
|
||||
// Should this be immutable?
|
||||
return this.detectedComponentsInternal;
|
||||
}
|
||||
|
||||
public void RegisterUsage(
|
||||
DetectedComponent detectedComponent,
|
||||
bool isExplicitReferencedDependency = false,
|
||||
string parentComponentId = null,
|
||||
bool? isDevelopmentDependency = null,
|
||||
DependencyScope? dependencyScope = null)
|
||||
{
|
||||
if (detectedComponent == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName: nameof(detectedComponent));
|
||||
}
|
||||
|
||||
if (detectedComponent.Component == null)
|
||||
{
|
||||
throw new ArgumentException(Resources.MissingComponentId);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (detectedComponent.FilePaths?.Any() ?? false)
|
||||
{
|
||||
this.log?.LogWarning("Detector should not populate DetectedComponent.FilePaths!");
|
||||
}
|
||||
if (detectedComponent.FilePaths?.Any() ?? false)
|
||||
{
|
||||
this.log?.LogWarning("Detector should not populate DetectedComponent.FilePaths!");
|
||||
}
|
||||
|
||||
if (detectedComponent.DependencyRoots?.Any() ?? false)
|
||||
{
|
||||
this.log?.LogWarning("Detector should not populate DetectedComponent.DependencyRoots!");
|
||||
}
|
||||
if (detectedComponent.DependencyRoots?.Any() ?? false)
|
||||
{
|
||||
this.log?.LogWarning("Detector should not populate DetectedComponent.DependencyRoots!");
|
||||
}
|
||||
|
||||
if (detectedComponent.DevelopmentDependency.HasValue)
|
||||
{
|
||||
this.log?.LogWarning("Detector should not populate DetectedComponent.DevelopmentDependency!");
|
||||
}
|
||||
if (detectedComponent.DevelopmentDependency.HasValue)
|
||||
{
|
||||
this.log?.LogWarning("Detector should not populate DetectedComponent.DevelopmentDependency!");
|
||||
}
|
||||
#endif
|
||||
|
||||
var componentId = detectedComponent.Component.Id;
|
||||
DetectedComponent storedComponent = null;
|
||||
lock (this.registerUsageLock)
|
||||
{
|
||||
storedComponent = this.detectedComponentsInternal.GetOrAdd(componentId, detectedComponent);
|
||||
this.AddComponentToGraph(this.ManifestFileLocation, detectedComponent, isExplicitReferencedDependency, parentComponentId, isDevelopmentDependency, dependencyScope);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAdditionalRelatedFile(string relatedFilePath)
|
||||
var componentId = detectedComponent.Component.Id;
|
||||
DetectedComponent storedComponent = null;
|
||||
lock (this.registerUsageLock)
|
||||
{
|
||||
this.DependencyGraph.AddAdditionalRelatedFile(relatedFilePath);
|
||||
storedComponent = this.detectedComponentsInternal.GetOrAdd(componentId, detectedComponent);
|
||||
this.AddComponentToGraph(this.ManifestFileLocation, detectedComponent, isExplicitReferencedDependency, parentComponentId, isDevelopmentDependency, dependencyScope);
|
||||
}
|
||||
}
|
||||
|
||||
public IList<string> GetAdditionalRelatedFiles()
|
||||
public void AddAdditionalRelatedFile(string relatedFilePath)
|
||||
{
|
||||
this.DependencyGraph.AddAdditionalRelatedFile(relatedFilePath);
|
||||
}
|
||||
|
||||
public IList<string> GetAdditionalRelatedFiles()
|
||||
{
|
||||
return this.DependencyGraph.GetAdditionalRelatedFiles().ToImmutableList();
|
||||
}
|
||||
|
||||
public IComponentRecorder GetParentComponentRecorder()
|
||||
{
|
||||
return this.recorder;
|
||||
}
|
||||
|
||||
private void AddComponentToGraph(
|
||||
string location,
|
||||
DetectedComponent detectedComponent,
|
||||
bool isExplicitReferencedDependency,
|
||||
string parentComponentId,
|
||||
bool? isDevelopmentDependency,
|
||||
DependencyScope? dependencyScope)
|
||||
{
|
||||
var componentNode = new DependencyGraph.ComponentRefNode
|
||||
{
|
||||
return this.DependencyGraph.GetAdditionalRelatedFiles().ToImmutableList();
|
||||
}
|
||||
Id = detectedComponent.Component.Id,
|
||||
IsExplicitReferencedDependency = isExplicitReferencedDependency,
|
||||
IsDevelopmentDependency = isDevelopmentDependency,
|
||||
DependencyScope = dependencyScope,
|
||||
};
|
||||
|
||||
public IComponentRecorder GetParentComponentRecorder()
|
||||
{
|
||||
return this.recorder;
|
||||
}
|
||||
|
||||
private void AddComponentToGraph(
|
||||
string location,
|
||||
DetectedComponent detectedComponent,
|
||||
bool isExplicitReferencedDependency,
|
||||
string parentComponentId,
|
||||
bool? isDevelopmentDependency,
|
||||
DependencyScope? dependencyScope)
|
||||
{
|
||||
var componentNode = new DependencyGraph.ComponentRefNode
|
||||
{
|
||||
Id = detectedComponent.Component.Id,
|
||||
IsExplicitReferencedDependency = isExplicitReferencedDependency,
|
||||
IsDevelopmentDependency = isDevelopmentDependency,
|
||||
DependencyScope = dependencyScope,
|
||||
};
|
||||
|
||||
this.DependencyGraph.AddComponent(componentNode, parentComponentId);
|
||||
}
|
||||
this.DependencyGraph.AddComponent(componentNode, parentComponentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
@ -9,190 +9,189 @@ using Microsoft.ComponentDetection.Contracts.BcdeModels;
|
|||
|
||||
[assembly: InternalsVisibleTo("Microsoft.ComponentDetection.Common.Tests")]
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.DependencyGraph
|
||||
namespace Microsoft.ComponentDetection.Common.DependencyGraph;
|
||||
|
||||
internal class DependencyGraph : IDependencyGraph
|
||||
{
|
||||
internal class DependencyGraph : IDependencyGraph
|
||||
private readonly ConcurrentDictionary<string, ComponentRefNode> componentNodes;
|
||||
|
||||
private readonly bool enableManualTrackingOfExplicitReferences;
|
||||
|
||||
public DependencyGraph(bool enableManualTrackingOfExplicitReferences)
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ComponentRefNode> componentNodes;
|
||||
this.componentNodes = new ConcurrentDictionary<string, ComponentRefNode>();
|
||||
this.enableManualTrackingOfExplicitReferences = enableManualTrackingOfExplicitReferences;
|
||||
}
|
||||
|
||||
private readonly bool enableManualTrackingOfExplicitReferences;
|
||||
internal ConcurrentDictionary<string, byte> AdditionalRelatedFiles { get; } = new ConcurrentDictionary<string, byte>();
|
||||
|
||||
public DependencyGraph(bool enableManualTrackingOfExplicitReferences)
|
||||
public void AddComponent(ComponentRefNode componentNode, string parentComponentId = null)
|
||||
{
|
||||
if (componentNode == null)
|
||||
{
|
||||
this.componentNodes = new ConcurrentDictionary<string, ComponentRefNode>();
|
||||
this.enableManualTrackingOfExplicitReferences = enableManualTrackingOfExplicitReferences;
|
||||
throw new ArgumentNullException(nameof(componentNode));
|
||||
}
|
||||
|
||||
internal ConcurrentDictionary<string, byte> AdditionalRelatedFiles { get; } = new ConcurrentDictionary<string, byte>();
|
||||
|
||||
public void AddComponent(ComponentRefNode componentNode, string parentComponentId = null)
|
||||
if (string.IsNullOrWhiteSpace(componentNode.Id))
|
||||
{
|
||||
if (componentNode == null)
|
||||
throw new ArgumentNullException(nameof(componentNode.Id));
|
||||
}
|
||||
|
||||
this.componentNodes.AddOrUpdate(componentNode.Id, componentNode, (key, currentNode) =>
|
||||
{
|
||||
currentNode.IsExplicitReferencedDependency |= componentNode.IsExplicitReferencedDependency;
|
||||
|
||||
// If incoming component has a dev dependency value, and it with whatever is in storage. Otherwise, leave storage alone.
|
||||
if (componentNode.IsDevelopmentDependency.HasValue)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(componentNode));
|
||||
currentNode.IsDevelopmentDependency = currentNode.IsDevelopmentDependency.GetValueOrDefault(true) && componentNode.IsDevelopmentDependency.Value;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(componentNode.Id))
|
||||
if (componentNode.DependencyScope.HasValue)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(componentNode.Id));
|
||||
currentNode.DependencyScope = DependencyScopeComparer.GetMergedDependencyScope(currentNode.DependencyScope, componentNode.DependencyScope);
|
||||
}
|
||||
|
||||
this.componentNodes.AddOrUpdate(componentNode.Id, componentNode, (key, currentNode) =>
|
||||
return currentNode;
|
||||
});
|
||||
|
||||
this.AddDependency(componentNode.Id, parentComponentId);
|
||||
}
|
||||
|
||||
public bool Contains(string componentId)
|
||||
{
|
||||
return this.componentNodes.ContainsKey(componentId);
|
||||
}
|
||||
|
||||
public ICollection<string> GetDependenciesForComponent(string componentId)
|
||||
{
|
||||
return this.componentNodes[componentId].DependencyIds;
|
||||
}
|
||||
|
||||
public ICollection<string> GetExplicitReferencedDependencyIds(string componentId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(componentId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(componentId));
|
||||
}
|
||||
|
||||
if (!this.componentNodes.TryGetValue(componentId, out var componentRef))
|
||||
{
|
||||
throw new ArgumentException(string.Format(Resources.MissingNodeInDependencyGraph, componentId), paramName: nameof(componentId));
|
||||
}
|
||||
|
||||
IList<string> explicitReferencedDependencyIds = new List<string>();
|
||||
|
||||
this.GetExplicitReferencedDependencies(componentRef, explicitReferencedDependencyIds, new HashSet<string>());
|
||||
|
||||
return explicitReferencedDependencyIds;
|
||||
}
|
||||
|
||||
public void AddAdditionalRelatedFile(string additionalRelatedFile)
|
||||
{
|
||||
this.AdditionalRelatedFiles.AddOrUpdate(additionalRelatedFile, 0, (notUsed, notUsed2) => 0);
|
||||
}
|
||||
|
||||
public HashSet<string> GetAdditionalRelatedFiles()
|
||||
{
|
||||
return this.AdditionalRelatedFiles.Keys.ToImmutableHashSet().ToHashSet();
|
||||
}
|
||||
|
||||
public bool HasComponents()
|
||||
{
|
||||
return this.componentNodes.Count > 0;
|
||||
}
|
||||
|
||||
public bool? IsDevelopmentDependency(string componentId)
|
||||
{
|
||||
return this.componentNodes[componentId].IsDevelopmentDependency;
|
||||
}
|
||||
|
||||
public DependencyScope? GetDependencyScope(string componentId)
|
||||
{
|
||||
return this.componentNodes[componentId].DependencyScope;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAllExplicitlyReferencedComponents()
|
||||
{
|
||||
return this.componentNodes.Values
|
||||
.Where(componentRefNode => this.IsExplicitReferencedDependency(componentRefNode))
|
||||
.Select(componentRefNode => componentRefNode.Id);
|
||||
}
|
||||
|
||||
IEnumerable<string> IDependencyGraph.GetDependenciesForComponent(string componentId)
|
||||
{
|
||||
return this.GetDependenciesForComponent(componentId).ToImmutableList();
|
||||
}
|
||||
|
||||
IEnumerable<string> IDependencyGraph.GetComponents()
|
||||
{
|
||||
return this.componentNodes.Keys.ToImmutableList();
|
||||
}
|
||||
|
||||
bool IDependencyGraph.IsComponentExplicitlyReferenced(string componentId)
|
||||
{
|
||||
return this.IsExplicitReferencedDependency(this.componentNodes[componentId]);
|
||||
}
|
||||
|
||||
private void GetExplicitReferencedDependencies(ComponentRefNode component, IList<string> explicitReferencedDependencyIds, ISet<string> visited)
|
||||
{
|
||||
if (this.IsExplicitReferencedDependency(component))
|
||||
{
|
||||
explicitReferencedDependencyIds.Add(component.Id);
|
||||
}
|
||||
|
||||
visited.Add(component.Id);
|
||||
|
||||
foreach (var parentId in component.DependedOnByIds)
|
||||
{
|
||||
if (!visited.Contains(parentId))
|
||||
{
|
||||
currentNode.IsExplicitReferencedDependency |= componentNode.IsExplicitReferencedDependency;
|
||||
|
||||
// If incoming component has a dev dependency value, and it with whatever is in storage. Otherwise, leave storage alone.
|
||||
if (componentNode.IsDevelopmentDependency.HasValue)
|
||||
{
|
||||
currentNode.IsDevelopmentDependency = currentNode.IsDevelopmentDependency.GetValueOrDefault(true) && componentNode.IsDevelopmentDependency.Value;
|
||||
}
|
||||
|
||||
if (componentNode.DependencyScope.HasValue)
|
||||
{
|
||||
currentNode.DependencyScope = DependencyScopeComparer.GetMergedDependencyScope(currentNode.DependencyScope, componentNode.DependencyScope);
|
||||
}
|
||||
|
||||
return currentNode;
|
||||
});
|
||||
|
||||
this.AddDependency(componentNode.Id, parentComponentId);
|
||||
}
|
||||
|
||||
public bool Contains(string componentId)
|
||||
{
|
||||
return this.componentNodes.ContainsKey(componentId);
|
||||
}
|
||||
|
||||
public ICollection<string> GetDependenciesForComponent(string componentId)
|
||||
{
|
||||
return this.componentNodes[componentId].DependencyIds;
|
||||
}
|
||||
|
||||
public ICollection<string> GetExplicitReferencedDependencyIds(string componentId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(componentId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(componentId));
|
||||
this.GetExplicitReferencedDependencies(this.componentNodes[parentId], explicitReferencedDependencyIds, visited);
|
||||
}
|
||||
|
||||
if (!this.componentNodes.TryGetValue(componentId, out var componentRef))
|
||||
{
|
||||
throw new ArgumentException(string.Format(Resources.MissingNodeInDependencyGraph, componentId), paramName: nameof(componentId));
|
||||
}
|
||||
|
||||
IList<string> explicitReferencedDependencyIds = new List<string>();
|
||||
|
||||
this.GetExplicitReferencedDependencies(componentRef, explicitReferencedDependencyIds, new HashSet<string>());
|
||||
|
||||
return explicitReferencedDependencyIds;
|
||||
}
|
||||
|
||||
public void AddAdditionalRelatedFile(string additionalRelatedFile)
|
||||
{
|
||||
this.AdditionalRelatedFiles.AddOrUpdate(additionalRelatedFile, 0, (notUsed, notUsed2) => 0);
|
||||
}
|
||||
|
||||
public HashSet<string> GetAdditionalRelatedFiles()
|
||||
{
|
||||
return this.AdditionalRelatedFiles.Keys.ToImmutableHashSet().ToHashSet();
|
||||
}
|
||||
|
||||
public bool HasComponents()
|
||||
{
|
||||
return this.componentNodes.Count > 0;
|
||||
}
|
||||
|
||||
public bool? IsDevelopmentDependency(string componentId)
|
||||
{
|
||||
return this.componentNodes[componentId].IsDevelopmentDependency;
|
||||
}
|
||||
|
||||
public DependencyScope? GetDependencyScope(string componentId)
|
||||
{
|
||||
return this.componentNodes[componentId].DependencyScope;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAllExplicitlyReferencedComponents()
|
||||
{
|
||||
return this.componentNodes.Values
|
||||
.Where(componentRefNode => this.IsExplicitReferencedDependency(componentRefNode))
|
||||
.Select(componentRefNode => componentRefNode.Id);
|
||||
}
|
||||
|
||||
IEnumerable<string> IDependencyGraph.GetDependenciesForComponent(string componentId)
|
||||
{
|
||||
return this.GetDependenciesForComponent(componentId).ToImmutableList();
|
||||
}
|
||||
|
||||
IEnumerable<string> IDependencyGraph.GetComponents()
|
||||
{
|
||||
return this.componentNodes.Keys.ToImmutableList();
|
||||
}
|
||||
|
||||
bool IDependencyGraph.IsComponentExplicitlyReferenced(string componentId)
|
||||
{
|
||||
return this.IsExplicitReferencedDependency(this.componentNodes[componentId]);
|
||||
}
|
||||
|
||||
private void GetExplicitReferencedDependencies(ComponentRefNode component, IList<string> explicitReferencedDependencyIds, ISet<string> visited)
|
||||
{
|
||||
if (this.IsExplicitReferencedDependency(component))
|
||||
{
|
||||
explicitReferencedDependencyIds.Add(component.Id);
|
||||
}
|
||||
|
||||
visited.Add(component.Id);
|
||||
|
||||
foreach (var parentId in component.DependedOnByIds)
|
||||
{
|
||||
if (!visited.Contains(parentId))
|
||||
{
|
||||
this.GetExplicitReferencedDependencies(this.componentNodes[parentId], explicitReferencedDependencyIds, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsExplicitReferencedDependency(ComponentRefNode component)
|
||||
{
|
||||
return (this.enableManualTrackingOfExplicitReferences && component.IsExplicitReferencedDependency) ||
|
||||
(!this.enableManualTrackingOfExplicitReferences && !component.DependedOnByIds.Any());
|
||||
}
|
||||
|
||||
private void AddDependency(string componentId, string parentComponentId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(parentComponentId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.componentNodes.TryGetValue(parentComponentId, out var parentComponentRefNode))
|
||||
{
|
||||
throw new ArgumentException(string.Format(Resources.MissingNodeInDependencyGraph, parentComponentId), nameof(parentComponentId));
|
||||
}
|
||||
|
||||
parentComponentRefNode.DependencyIds.Add(componentId);
|
||||
this.componentNodes[componentId].DependedOnByIds.Add(parentComponentId);
|
||||
}
|
||||
|
||||
internal class ComponentRefNode
|
||||
{
|
||||
internal ComponentRefNode()
|
||||
{
|
||||
this.DependencyIds = new HashSet<string>();
|
||||
this.DependedOnByIds = new HashSet<string>();
|
||||
}
|
||||
|
||||
internal bool IsExplicitReferencedDependency { get; set; }
|
||||
|
||||
internal string Id { get; set; }
|
||||
|
||||
internal ISet<string> DependencyIds { get; private set; }
|
||||
|
||||
internal ISet<string> DependedOnByIds { get; private set; }
|
||||
|
||||
internal bool? IsDevelopmentDependency { get; set; }
|
||||
|
||||
internal DependencyScope? DependencyScope { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsExplicitReferencedDependency(ComponentRefNode component)
|
||||
{
|
||||
return (this.enableManualTrackingOfExplicitReferences && component.IsExplicitReferencedDependency) ||
|
||||
(!this.enableManualTrackingOfExplicitReferences && !component.DependedOnByIds.Any());
|
||||
}
|
||||
|
||||
private void AddDependency(string componentId, string parentComponentId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(parentComponentId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.componentNodes.TryGetValue(parentComponentId, out var parentComponentRefNode))
|
||||
{
|
||||
throw new ArgumentException(string.Format(Resources.MissingNodeInDependencyGraph, parentComponentId), nameof(parentComponentId));
|
||||
}
|
||||
|
||||
parentComponentRefNode.DependencyIds.Add(componentId);
|
||||
this.componentNodes[componentId].DependedOnByIds.Add(parentComponentId);
|
||||
}
|
||||
|
||||
internal class ComponentRefNode
|
||||
{
|
||||
internal ComponentRefNode()
|
||||
{
|
||||
this.DependencyIds = new HashSet<string>();
|
||||
this.DependedOnByIds = new HashSet<string>();
|
||||
}
|
||||
|
||||
internal bool IsExplicitReferencedDependency { get; set; }
|
||||
|
||||
internal string Id { get; set; }
|
||||
|
||||
internal ISet<string> DependencyIds { get; private set; }
|
||||
|
||||
internal ISet<string> DependedOnByIds { get; private set; }
|
||||
|
||||
internal bool? IsDevelopmentDependency { get; set; }
|
||||
|
||||
internal DependencyScope? DependencyScope { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
using Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Merges dependnecy Scope in their order of Priority.
|
||||
/// Higher priority scope, as indicated by its lower enum value is given precendence.
|
||||
/// </summary>
|
||||
public class DependencyScopeComparer
|
||||
{
|
||||
public static DependencyScope? GetMergedDependencyScope(DependencyScope? scope1, DependencyScope? scope2)
|
||||
{
|
||||
if (!scope1.HasValue)
|
||||
{
|
||||
return scope2;
|
||||
}
|
||||
else if (!scope2.HasValue)
|
||||
{
|
||||
return scope1;
|
||||
}
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
return (int)scope1 < (int)scope2 ? scope1 : scope2;
|
||||
/// <summary>
|
||||
/// Merges dependnecy Scope in their order of Priority.
|
||||
/// Higher priority scope, as indicated by its lower enum value is given precendence.
|
||||
/// </summary>
|
||||
public class DependencyScopeComparer
|
||||
{
|
||||
public static DependencyScope? GetMergedDependencyScope(DependencyScope? scope1, DependencyScope? scope2)
|
||||
{
|
||||
if (!scope1.HasValue)
|
||||
{
|
||||
return scope2;
|
||||
}
|
||||
else if (!scope2.HasValue)
|
||||
{
|
||||
return scope1;
|
||||
}
|
||||
|
||||
return (int)scope1 < (int)scope2 ? scope1 : scope2;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,32 @@
|
|||
using System.Composition;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[Export(typeof(IDetectorDependencies))]
|
||||
public class DetectorDependencies : IDetectorDependencies
|
||||
{
|
||||
[Export(typeof(IDetectorDependencies))]
|
||||
public class DetectorDependencies : IDetectorDependencies
|
||||
{
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
[Import]
|
||||
public IComponentStreamEnumerableFactory ComponentStreamEnumerableFactory { get; set; }
|
||||
[Import]
|
||||
public IComponentStreamEnumerableFactory ComponentStreamEnumerableFactory { get; set; }
|
||||
|
||||
[Import]
|
||||
public IPathUtilityService PathUtilityService { get; set; }
|
||||
[Import]
|
||||
public IPathUtilityService PathUtilityService { get; set; }
|
||||
|
||||
[Import]
|
||||
public ICommandLineInvocationService CommandLineInvocationService { get; set; }
|
||||
[Import]
|
||||
public ICommandLineInvocationService CommandLineInvocationService { get; set; }
|
||||
|
||||
[Import]
|
||||
public IFileUtilityService FileUtilityService { get; set; }
|
||||
[Import]
|
||||
public IFileUtilityService FileUtilityService { get; set; }
|
||||
|
||||
[Import]
|
||||
public IObservableDirectoryWalkerFactory DirectoryWalkerFactory { get; set; }
|
||||
[Import]
|
||||
public IObservableDirectoryWalkerFactory DirectoryWalkerFactory { get; set; }
|
||||
|
||||
[Import]
|
||||
public IDockerService DockerService { get; set; }
|
||||
[Import]
|
||||
public IDockerService DockerService { get; set; }
|
||||
|
||||
[Import]
|
||||
public IEnvironmentVariableService EnvironmentVariableService { get; set; }
|
||||
}
|
||||
[Import]
|
||||
public IEnvironmentVariableService EnvironmentVariableService { get; set; }
|
||||
}
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
using System.Diagnostics;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[DebuggerDisplay("{Name}")]
|
||||
public class DirectoryItemFacade
|
||||
{
|
||||
[DebuggerDisplay("{Name}")]
|
||||
public class DirectoryItemFacade
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<DirectoryItemFacade> Directories { get; set; }
|
||||
public List<DirectoryItemFacade> Directories { get; set; }
|
||||
|
||||
public List<IComponentStream> Files { get; set; }
|
||||
}
|
||||
public List<IComponentStream> Files { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,54 +1,53 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public class DigestUtility
|
||||
{
|
||||
public class DigestUtility
|
||||
private static readonly Dictionary<string, int> AlgorithmsSizes = new Dictionary<string, int>()
|
||||
{
|
||||
private static readonly Dictionary<string, int> AlgorithmsSizes = new Dictionary<string, int>()
|
||||
{ "sha256", 32 },
|
||||
{ "sha384", 48 },
|
||||
{ "sha512", 64 },
|
||||
};
|
||||
|
||||
public static bool CheckDigest(string digest, bool throwError = true)
|
||||
{
|
||||
var indexOfColon = digest.IndexOf(':');
|
||||
if (indexOfColon < 0 ||
|
||||
indexOfColon + 1 == digest.Length ||
|
||||
!DockerRegex.AnchoredDigestRegexp.IsMatch(digest))
|
||||
{
|
||||
{ "sha256", 32 },
|
||||
{ "sha384", 48 },
|
||||
{ "sha512", 64 },
|
||||
};
|
||||
|
||||
public static bool CheckDigest(string digest, bool throwError = true)
|
||||
{
|
||||
var indexOfColon = digest.IndexOf(':');
|
||||
if (indexOfColon < 0 ||
|
||||
indexOfColon + 1 == digest.Length ||
|
||||
!DockerRegex.AnchoredDigestRegexp.IsMatch(digest))
|
||||
if (throwError)
|
||||
{
|
||||
if (throwError)
|
||||
{
|
||||
throw new InvalidDigestFormatError(digest);
|
||||
}
|
||||
|
||||
return false;
|
||||
throw new InvalidDigestFormatError(digest);
|
||||
}
|
||||
|
||||
var algorithm = digest[..indexOfColon];
|
||||
|
||||
if (!AlgorithmsSizes.ContainsKey(algorithm))
|
||||
{
|
||||
if (throwError)
|
||||
{
|
||||
throw new UnsupportedAlgorithmError(digest);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AlgorithmsSizes[algorithm] * 2 != (digest.Length - indexOfColon - 1))
|
||||
{
|
||||
if (throwError)
|
||||
{
|
||||
throw new InvalidDigestLengthError(digest);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
var algorithm = digest[..indexOfColon];
|
||||
|
||||
if (!AlgorithmsSizes.ContainsKey(algorithm))
|
||||
{
|
||||
if (throwError)
|
||||
{
|
||||
throw new UnsupportedAlgorithmError(digest);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AlgorithmsSizes[algorithm] * 2 != (digest.Length - indexOfColon - 1))
|
||||
{
|
||||
if (throwError)
|
||||
{
|
||||
throw new InvalidDigestLengthError(digest);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,120 +1,119 @@
|
|||
using System;
|
||||
|
||||
#pragma warning disable SA1402
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public class DockerReferenceException : Exception
|
||||
{
|
||||
public class DockerReferenceException : Exception
|
||||
public DockerReferenceException(string reference, string exceptionErrorMessage)
|
||||
: base($"Error while parsing docker reference {reference} : {exceptionErrorMessage}")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// ReferenceInvalidFormat represents an error while trying to parse a string as a reference.
|
||||
public class ReferenceInvalidFormatException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "invalid reference format";
|
||||
|
||||
public ReferenceInvalidFormatException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// TagInvalidFormat represents an error while trying to parse a string as a tag.
|
||||
public class ReferenceTagInvalidFormatException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "invalid tag format";
|
||||
|
||||
public ReferenceTagInvalidFormatException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// DigestInvalidFormat represents an error while trying to parse a string as a tag.
|
||||
public class ReferenceDigestInvalidFormatException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "invalid digest format";
|
||||
|
||||
public ReferenceDigestInvalidFormatException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// NameContainsUppercase is returned for invalid repository names that contain uppercase characters.
|
||||
public class ReferenceNameContainsUppercaseException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "repository name must be lowercase";
|
||||
|
||||
public ReferenceNameContainsUppercaseException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// NameEmpty is returned for empty, invalid repository names.
|
||||
public class ReferenceNameEmptyException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "repository name must have at least one component";
|
||||
|
||||
public ReferenceNameEmptyException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
|
||||
public class ReferenceNameTooLongException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "repository name must not be more than 255 characters";
|
||||
|
||||
public ReferenceNameTooLongException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNameNotCanonical is returned when a name is not canonical.
|
||||
public class ReferenceNameNotCanonicalException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "repository name must be canonical";
|
||||
|
||||
public ReferenceNameNotCanonicalException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidDigestFormatError : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "invalid digest format";
|
||||
|
||||
public InvalidDigestFormatError(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UnsupportedAlgorithmError : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "unsupported digest algorithm";
|
||||
|
||||
public UnsupportedAlgorithmError(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidDigestLengthError : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "invalid checksum digest length";
|
||||
|
||||
public InvalidDigestLengthError(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
public DockerReferenceException(string reference, string exceptionErrorMessage)
|
||||
: base($"Error while parsing docker reference {reference} : {exceptionErrorMessage}")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// ReferenceInvalidFormat represents an error while trying to parse a string as a reference.
|
||||
public class ReferenceInvalidFormatException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "invalid reference format";
|
||||
|
||||
public ReferenceInvalidFormatException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// TagInvalidFormat represents an error while trying to parse a string as a tag.
|
||||
public class ReferenceTagInvalidFormatException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "invalid tag format";
|
||||
|
||||
public ReferenceTagInvalidFormatException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// DigestInvalidFormat represents an error while trying to parse a string as a tag.
|
||||
public class ReferenceDigestInvalidFormatException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "invalid digest format";
|
||||
|
||||
public ReferenceDigestInvalidFormatException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// NameContainsUppercase is returned for invalid repository names that contain uppercase characters.
|
||||
public class ReferenceNameContainsUppercaseException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "repository name must be lowercase";
|
||||
|
||||
public ReferenceNameContainsUppercaseException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// NameEmpty is returned for empty, invalid repository names.
|
||||
public class ReferenceNameEmptyException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "repository name must have at least one component";
|
||||
|
||||
public ReferenceNameEmptyException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
|
||||
public class ReferenceNameTooLongException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "repository name must not be more than 255 characters";
|
||||
|
||||
public ReferenceNameTooLongException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNameNotCanonical is returned when a name is not canonical.
|
||||
public class ReferenceNameNotCanonicalException : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "repository name must be canonical";
|
||||
|
||||
public ReferenceNameNotCanonicalException(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidDigestFormatError : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "invalid digest format";
|
||||
|
||||
public InvalidDigestFormatError(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UnsupportedAlgorithmError : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "unsupported digest algorithm";
|
||||
|
||||
public UnsupportedAlgorithmError(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidDigestLengthError : DockerReferenceException
|
||||
{
|
||||
private const string ErrorMessage = "invalid checksum digest length";
|
||||
|
||||
public InvalidDigestLengthError(string reference)
|
||||
: base(reference, ErrorMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,146 +26,145 @@ using Microsoft.ComponentDetection.Contracts;
|
|||
//
|
||||
// identifier := /[a-f0-9]{64}/
|
||||
// short-identifier := /[a-f0-9]{6,64}/
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public class DockerReferenceUtility
|
||||
{
|
||||
public class DockerReferenceUtility
|
||||
// NameTotalLengthMax is the maximum total number of characters in a repository name.
|
||||
private const int NameTotalLengthMax = 255;
|
||||
private const string DEFAULTDOMAIN = "docker.io";
|
||||
private const string LEGACYDEFAULTDOMAIN = "index.docker.io";
|
||||
private const string OFFICIALREPOSITORYNAME = "library";
|
||||
|
||||
public static DockerReference ParseQualifiedName(string qualifiedName)
|
||||
{
|
||||
// NameTotalLengthMax is the maximum total number of characters in a repository name.
|
||||
private const int NameTotalLengthMax = 255;
|
||||
private const string DEFAULTDOMAIN = "docker.io";
|
||||
private const string LEGACYDEFAULTDOMAIN = "index.docker.io";
|
||||
private const string OFFICIALREPOSITORYNAME = "library";
|
||||
|
||||
public static DockerReference ParseQualifiedName(string qualifiedName)
|
||||
var regexp = DockerRegex.ReferenceRegexp;
|
||||
if (!regexp.IsMatch(qualifiedName))
|
||||
{
|
||||
var regexp = DockerRegex.ReferenceRegexp;
|
||||
if (!regexp.IsMatch(qualifiedName))
|
||||
if (string.IsNullOrWhiteSpace(qualifiedName))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(qualifiedName))
|
||||
{
|
||||
throw new ReferenceNameEmptyException(qualifiedName);
|
||||
}
|
||||
|
||||
if (regexp.IsMatch(qualifiedName.ToLower()))
|
||||
{
|
||||
throw new ReferenceNameContainsUppercaseException(qualifiedName);
|
||||
}
|
||||
|
||||
throw new ReferenceInvalidFormatException(qualifiedName);
|
||||
throw new ReferenceNameEmptyException(qualifiedName);
|
||||
}
|
||||
|
||||
var matches = regexp.Match(qualifiedName).Groups;
|
||||
|
||||
var name = matches[1].Value;
|
||||
if (name.Length > NameTotalLengthMax)
|
||||
if (regexp.IsMatch(qualifiedName.ToLower()))
|
||||
{
|
||||
throw new ReferenceNameTooLongException(name);
|
||||
throw new ReferenceNameContainsUppercaseException(qualifiedName);
|
||||
}
|
||||
|
||||
var reference = new Reference();
|
||||
|
||||
var nameMatch = DockerRegex.AnchoredNameRegexp.Match(name).Groups;
|
||||
if (nameMatch.Count == 3)
|
||||
{
|
||||
reference.Domain = nameMatch[1].Value;
|
||||
reference.Repository = nameMatch[2].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
reference.Domain = string.Empty;
|
||||
reference.Repository = matches[1].Value;
|
||||
}
|
||||
|
||||
reference.Tag = matches[2].Value;
|
||||
|
||||
if (matches.Count > 3 && !string.IsNullOrEmpty(matches[3].Value))
|
||||
{
|
||||
DigestUtility.CheckDigest(matches[3].Value, true);
|
||||
reference.Digest = matches[3].Value;
|
||||
}
|
||||
|
||||
return CreateDockerReference(reference);
|
||||
throw new ReferenceInvalidFormatException(qualifiedName);
|
||||
}
|
||||
|
||||
public static (string Domain, string Remainder) SplitDockerDomain(string name)
|
||||
{
|
||||
string domain;
|
||||
string remainder;
|
||||
var matches = regexp.Match(qualifiedName).Groups;
|
||||
|
||||
var indexOfSlash = name.IndexOf('/');
|
||||
if (indexOfSlash == -1 || !(
|
||||
var name = matches[1].Value;
|
||||
if (name.Length > NameTotalLengthMax)
|
||||
{
|
||||
throw new ReferenceNameTooLongException(name);
|
||||
}
|
||||
|
||||
var reference = new Reference();
|
||||
|
||||
var nameMatch = DockerRegex.AnchoredNameRegexp.Match(name).Groups;
|
||||
if (nameMatch.Count == 3)
|
||||
{
|
||||
reference.Domain = nameMatch[1].Value;
|
||||
reference.Repository = nameMatch[2].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
reference.Domain = string.Empty;
|
||||
reference.Repository = matches[1].Value;
|
||||
}
|
||||
|
||||
reference.Tag = matches[2].Value;
|
||||
|
||||
if (matches.Count > 3 && !string.IsNullOrEmpty(matches[3].Value))
|
||||
{
|
||||
DigestUtility.CheckDigest(matches[3].Value, true);
|
||||
reference.Digest = matches[3].Value;
|
||||
}
|
||||
|
||||
return CreateDockerReference(reference);
|
||||
}
|
||||
|
||||
public static (string Domain, string Remainder) SplitDockerDomain(string name)
|
||||
{
|
||||
string domain;
|
||||
string remainder;
|
||||
|
||||
var indexOfSlash = name.IndexOf('/');
|
||||
if (indexOfSlash == -1 || !(
|
||||
name.LastIndexOf('.', indexOfSlash) != -1 ||
|
||||
name.LastIndexOf(':', indexOfSlash) != -1 ||
|
||||
name.StartsWith("localhost/")))
|
||||
{
|
||||
domain = DEFAULTDOMAIN;
|
||||
remainder = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
domain = name[..indexOfSlash];
|
||||
remainder = name[(indexOfSlash + 1)..];
|
||||
}
|
||||
|
||||
if (domain == LEGACYDEFAULTDOMAIN)
|
||||
{
|
||||
domain = DEFAULTDOMAIN;
|
||||
}
|
||||
|
||||
if (domain == DEFAULTDOMAIN && remainder.IndexOf('/') == -1)
|
||||
{
|
||||
remainder = $"{OFFICIALREPOSITORYNAME}/{remainder}";
|
||||
}
|
||||
|
||||
return (domain, remainder);
|
||||
}
|
||||
|
||||
public static DockerReference ParseFamiliarName(string name)
|
||||
{
|
||||
if (DockerRegex.AnchoredIdentifierRegexp.IsMatch(name))
|
||||
{
|
||||
throw new ReferenceNameNotCanonicalException(name);
|
||||
}
|
||||
|
||||
(var domain, var remainder) = SplitDockerDomain(name);
|
||||
|
||||
string remoteName;
|
||||
var tagSeparatorIndex = remainder.IndexOf(':');
|
||||
if (tagSeparatorIndex > -1)
|
||||
{
|
||||
remoteName = remainder[..tagSeparatorIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteName = remainder;
|
||||
}
|
||||
|
||||
if (remoteName.ToLower() != remoteName)
|
||||
{
|
||||
throw new ReferenceNameContainsUppercaseException(name);
|
||||
}
|
||||
|
||||
return ParseQualifiedName($"{domain}/{remainder}");
|
||||
domain = DEFAULTDOMAIN;
|
||||
remainder = name;
|
||||
}
|
||||
|
||||
public static DockerReference ParseAll(string name)
|
||||
else
|
||||
{
|
||||
if (DockerRegex.AnchoredIdentifierRegexp.IsMatch(name))
|
||||
{
|
||||
return CreateDockerReference(new Reference { Digest = $"sha256:{name}" });
|
||||
}
|
||||
|
||||
if (DigestUtility.CheckDigest(name, false))
|
||||
{
|
||||
return CreateDockerReference(new Reference { Digest = name });
|
||||
}
|
||||
|
||||
return ParseFamiliarName(name);
|
||||
domain = name[..indexOfSlash];
|
||||
remainder = name[(indexOfSlash + 1)..];
|
||||
}
|
||||
|
||||
private static DockerReference CreateDockerReference(Reference options)
|
||||
if (domain == LEGACYDEFAULTDOMAIN)
|
||||
{
|
||||
return DockerReference.CreateDockerReference(options.Repository, options.Domain, options.Digest, options.Tag);
|
||||
domain = DEFAULTDOMAIN;
|
||||
}
|
||||
|
||||
if (domain == DEFAULTDOMAIN && remainder.IndexOf('/') == -1)
|
||||
{
|
||||
remainder = $"{OFFICIALREPOSITORYNAME}/{remainder}";
|
||||
}
|
||||
|
||||
return (domain, remainder);
|
||||
}
|
||||
|
||||
public static DockerReference ParseFamiliarName(string name)
|
||||
{
|
||||
if (DockerRegex.AnchoredIdentifierRegexp.IsMatch(name))
|
||||
{
|
||||
throw new ReferenceNameNotCanonicalException(name);
|
||||
}
|
||||
|
||||
(var domain, var remainder) = SplitDockerDomain(name);
|
||||
|
||||
string remoteName;
|
||||
var tagSeparatorIndex = remainder.IndexOf(':');
|
||||
if (tagSeparatorIndex > -1)
|
||||
{
|
||||
remoteName = remainder[..tagSeparatorIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteName = remainder;
|
||||
}
|
||||
|
||||
if (remoteName.ToLower() != remoteName)
|
||||
{
|
||||
throw new ReferenceNameContainsUppercaseException(name);
|
||||
}
|
||||
|
||||
return ParseQualifiedName($"{domain}/{remainder}");
|
||||
}
|
||||
|
||||
public static DockerReference ParseAll(string name)
|
||||
{
|
||||
if (DockerRegex.AnchoredIdentifierRegexp.IsMatch(name))
|
||||
{
|
||||
return CreateDockerReference(new Reference { Digest = $"sha256:{name}" });
|
||||
}
|
||||
|
||||
if (DigestUtility.CheckDigest(name, false))
|
||||
{
|
||||
return CreateDockerReference(new Reference { Digest = name });
|
||||
}
|
||||
|
||||
return ParseFamiliarName(name);
|
||||
}
|
||||
|
||||
private static DockerReference CreateDockerReference(Reference options)
|
||||
{
|
||||
return DockerReference.CreateDockerReference(options.Repository, options.Domain, options.Digest, options.Tag);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,120 +1,119 @@
|
|||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public class DockerRegex
|
||||
{
|
||||
public class DockerRegex
|
||||
{
|
||||
public static readonly Regex AlphaNumericRegexp = new Regex("[a-z0-9]+");
|
||||
public static readonly Regex SeparatorRegexp = new Regex("(?:[._]|__|[-]*)");
|
||||
public static readonly Regex AlphaNumericRegexp = new Regex("[a-z0-9]+");
|
||||
public static readonly Regex SeparatorRegexp = new Regex("(?:[._]|__|[-]*)");
|
||||
|
||||
public static readonly Regex DomainComponentRegexp = new Regex("(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])");
|
||||
public static readonly Regex TagRegexp = new Regex(@"[\w][\w.-]{0,127}");
|
||||
public static readonly Regex DigestRegexp = new Regex("[a-zA-Z][a-zA-Z0-9]*(?:[-_+.][a-zA-Z][a-zA-Z0-9]*)*[:][a-fA-F0-9]{32,}");
|
||||
public static readonly Regex IdentifierRegexp = new Regex("[a-f0-9]{64}");
|
||||
public static readonly Regex DomainComponentRegexp = new Regex("(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])");
|
||||
public static readonly Regex TagRegexp = new Regex(@"[\w][\w.-]{0,127}");
|
||||
public static readonly Regex DigestRegexp = new Regex("[a-zA-Z][a-zA-Z0-9]*(?:[-_+.][a-zA-Z][a-zA-Z0-9]*)*[:][a-fA-F0-9]{32,}");
|
||||
public static readonly Regex IdentifierRegexp = new Regex("[a-f0-9]{64}");
|
||||
|
||||
public static readonly Regex NameComponentRegexp = Expression(
|
||||
AlphaNumericRegexp,
|
||||
Optional(Repeated(SeparatorRegexp, AlphaNumericRegexp)));
|
||||
public static readonly Regex NameComponentRegexp = Expression(
|
||||
AlphaNumericRegexp,
|
||||
Optional(Repeated(SeparatorRegexp, AlphaNumericRegexp)));
|
||||
|
||||
public static readonly Regex DomainRegexp = Expression(
|
||||
DomainComponentRegexp,
|
||||
Optional(
|
||||
Repeated(
|
||||
new Regex(@"\."),
|
||||
DomainComponentRegexp)),
|
||||
Optional(
|
||||
new Regex(":"),
|
||||
new Regex("[0-9]+")));
|
||||
public static readonly Regex DomainRegexp = Expression(
|
||||
DomainComponentRegexp,
|
||||
Optional(
|
||||
Repeated(
|
||||
new Regex(@"\."),
|
||||
DomainComponentRegexp)),
|
||||
Optional(
|
||||
new Regex(":"),
|
||||
new Regex("[0-9]+")));
|
||||
|
||||
public static readonly Regex AnchoredDigestRegexp = Anchored(DigestRegexp);
|
||||
public static readonly Regex AnchoredDigestRegexp = Anchored(DigestRegexp);
|
||||
|
||||
public static readonly Regex NameRegexp = Expression(
|
||||
Optional(
|
||||
DomainRegexp,
|
||||
new Regex(@"\/")),
|
||||
public static readonly Regex NameRegexp = Expression(
|
||||
Optional(
|
||||
DomainRegexp,
|
||||
new Regex(@"\/")),
|
||||
NameComponentRegexp,
|
||||
Optional(
|
||||
Repeated(
|
||||
new Regex(@"\/"),
|
||||
NameComponentRegexp)));
|
||||
|
||||
public static readonly Regex AnchoredNameRegexp = Anchored(
|
||||
Optional(
|
||||
Capture(DomainRegexp),
|
||||
new Regex(@"\/")),
|
||||
Capture(
|
||||
NameComponentRegexp,
|
||||
Optional(
|
||||
Repeated(
|
||||
new Regex(@"\/"),
|
||||
NameComponentRegexp)));
|
||||
NameComponentRegexp))));
|
||||
|
||||
public static readonly Regex AnchoredNameRegexp = Anchored(
|
||||
Optional(
|
||||
Capture(DomainRegexp),
|
||||
new Regex(@"\/")),
|
||||
Capture(
|
||||
NameComponentRegexp,
|
||||
Optional(
|
||||
Repeated(
|
||||
new Regex(@"\/"),
|
||||
NameComponentRegexp))));
|
||||
public static readonly Regex ReferenceRegexp = Anchored(
|
||||
Capture(NameRegexp),
|
||||
Optional(new Regex(":"), Capture(TagRegexp)),
|
||||
Optional(new Regex("@"), Capture(DigestRegexp)));
|
||||
|
||||
public static readonly Regex ReferenceRegexp = Anchored(
|
||||
Capture(NameRegexp),
|
||||
Optional(new Regex(":"), Capture(TagRegexp)),
|
||||
Optional(new Regex("@"), Capture(DigestRegexp)));
|
||||
public static readonly Regex AnchoredIdentifierRegexp = Anchored(IdentifierRegexp);
|
||||
|
||||
public static readonly Regex AnchoredIdentifierRegexp = Anchored(IdentifierRegexp);
|
||||
/// <summary>
|
||||
/// expression defines a full expression, where each regular expression must follow the previous.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> full Regex expression <see cref="Regex"/> from the given list. </returns>
|
||||
public static Regex Expression(params Regex[] regexps)
|
||||
{
|
||||
return new Regex(string.Join(string.Empty, regexps.Select(re => re.ToString())));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// expression defines a full expression, where each regular expression must follow the previous.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> full Regex expression <see cref="Regex"/> from the given list. </returns>
|
||||
public static Regex Expression(params Regex[] regexps)
|
||||
{
|
||||
return new Regex(string.Join(string.Empty, regexps.Select(re => re.ToString())));
|
||||
}
|
||||
/// <summary>
|
||||
/// group wraps the regexp in a non-capturing group.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> <see cref="Regex"/> of the non-capturing group. </returns>
|
||||
public static Regex Group(params Regex[] regexps)
|
||||
{
|
||||
return new Regex($"(?:{Expression(regexps)})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// group wraps the regexp in a non-capturing group.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> <see cref="Regex"/> of the non-capturing group. </returns>
|
||||
public static Regex Group(params Regex[] regexps)
|
||||
{
|
||||
return new Regex($"(?:{Expression(regexps)})");
|
||||
}
|
||||
/// <summary>
|
||||
/// repeated wraps the regexp in a non-capturing group to get one or more matches.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> The wrapped <see cref="Regex"/>. </returns>
|
||||
public static Regex Optional(params Regex[] regexps)
|
||||
{
|
||||
return new Regex($"{Group(regexps)}?");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// repeated wraps the regexp in a non-capturing group to get one or more matches.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> The wrapped <see cref="Regex"/>. </returns>
|
||||
public static Regex Optional(params Regex[] regexps)
|
||||
{
|
||||
return new Regex($"{Group(regexps)}?");
|
||||
}
|
||||
/// <summary>
|
||||
/// repeated wraps the regexp in a non-capturing group to get one or more matches.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> The wrapped <see cref="Regex"/>. </returns>
|
||||
public static Regex Repeated(params Regex[] regexps)
|
||||
{
|
||||
return new Regex($"{Group(regexps)}+");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// repeated wraps the regexp in a non-capturing group to get one or more matches.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> The wrapped <see cref="Regex"/>. </returns>
|
||||
public static Regex Repeated(params Regex[] regexps)
|
||||
{
|
||||
return new Regex($"{Group(regexps)}+");
|
||||
}
|
||||
/// <summary>
|
||||
/// anchored anchors the regular expression by adding start and end delimiters.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> The anchored <see cref="Regex"/>. </returns>
|
||||
public static Regex Anchored(params Regex[] regexps)
|
||||
{
|
||||
return new Regex($"^{Expression(regexps)}$");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// anchored anchors the regular expression by adding start and end delimiters.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> The anchored <see cref="Regex"/>. </returns>
|
||||
public static Regex Anchored(params Regex[] regexps)
|
||||
{
|
||||
return new Regex($"^{Expression(regexps)}$");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// capture wraps the expression in a capturing group.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> The captured <see cref="Regex"/>. </returns>
|
||||
public static Regex Capture(params Regex[] regexps)
|
||||
{
|
||||
return new Regex($"({Expression(regexps)})");
|
||||
}
|
||||
/// <summary>
|
||||
/// capture wraps the expression in a capturing group.
|
||||
/// </summary>
|
||||
/// <param name="regexps">list of Regular expressions.</param>
|
||||
/// <returns> The captured <see cref="Regex"/>. </returns>
|
||||
public static Regex Capture(params Regex[] regexps)
|
||||
{
|
||||
return new Regex($"({Expression(regexps)})");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,230 +12,229 @@ using Microsoft.ComponentDetection.Contracts;
|
|||
using Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[Export(typeof(IDockerService))]
|
||||
public class DockerService : IDockerService
|
||||
{
|
||||
[Export(typeof(IDockerService))]
|
||||
public class DockerService : IDockerService
|
||||
// Base image annotations from ADO dockerTask
|
||||
private const string BaseImageRefAnnotation = "image.base.ref.name";
|
||||
private const string BaseImageDigestAnnotation = "image.base.digest";
|
||||
|
||||
private static readonly DockerClient Client = new DockerClientConfiguration().CreateClient();
|
||||
private static int incrementingContainerId;
|
||||
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public async Task<bool> CanPingDockerAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Base image annotations from ADO dockerTask
|
||||
private const string BaseImageRefAnnotation = "image.base.ref.name";
|
||||
private const string BaseImageDigestAnnotation = "image.base.digest";
|
||||
|
||||
private static readonly DockerClient Client = new DockerClientConfiguration().CreateClient();
|
||||
private static int incrementingContainerId;
|
||||
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public async Task<bool> CanPingDockerAsync(CancellationToken cancellationToken = default)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
await Client.System.PingAsync(cancellationToken);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogException(e, false);
|
||||
return false;
|
||||
}
|
||||
await Client.System.PingAsync(cancellationToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> CanRunLinuxContainersAsync(CancellationToken cancellationToken = default)
|
||||
catch (Exception e)
|
||||
{
|
||||
using var record = new DockerServiceSystemInfoTelemetryRecord();
|
||||
if (!await this.CanPingDockerAsync(cancellationToken))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var systemInfoResponse = await Client.System.GetSystemInfoAsync(cancellationToken);
|
||||
record.SystemInfo = JsonConvert.SerializeObject(systemInfoResponse);
|
||||
return string.Equals(systemInfoResponse.OSType, "linux", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
record.ExceptionMessage = e.Message;
|
||||
}
|
||||
this.Logger.LogException(e, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> CanRunLinuxContainersAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var record = new DockerServiceSystemInfoTelemetryRecord();
|
||||
if (!await this.CanPingDockerAsync(cancellationToken))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> ImageExistsLocallyAsync(string image, CancellationToken cancellationToken = default)
|
||||
try
|
||||
{
|
||||
using var record = new DockerServiceImageExistsLocallyTelemetryRecord
|
||||
{
|
||||
Image = image,
|
||||
};
|
||||
try
|
||||
{
|
||||
var imageInspectResponse = await Client.Images.InspectImageAsync(image, cancellationToken);
|
||||
record.ImageInspectResponse = JsonConvert.SerializeObject(imageInspectResponse);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
record.ExceptionMessage = e.Message;
|
||||
return false;
|
||||
}
|
||||
var systemInfoResponse = await Client.System.GetSystemInfoAsync(cancellationToken);
|
||||
record.SystemInfo = JsonConvert.SerializeObject(systemInfoResponse);
|
||||
return string.Equals(systemInfoResponse.OSType, "linux", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
record.ExceptionMessage = e.Message;
|
||||
}
|
||||
|
||||
public async Task<bool> TryPullImageAsync(string image, CancellationToken cancellationToken = default)
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> ImageExistsLocallyAsync(string image, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var record = new DockerServiceImageExistsLocallyTelemetryRecord
|
||||
{
|
||||
using var record = new DockerServiceTryPullImageTelemetryRecord
|
||||
{
|
||||
Image = image,
|
||||
};
|
||||
var parameters = new ImagesCreateParameters
|
||||
{
|
||||
FromImage = image,
|
||||
};
|
||||
try
|
||||
{
|
||||
var createImageProgress = new List<string>();
|
||||
var progress = new Progress<JSONMessage>(message =>
|
||||
{
|
||||
createImageProgress.Add(JsonConvert.SerializeObject(message));
|
||||
});
|
||||
await Client.Images.CreateImageAsync(parameters, null, progress, cancellationToken);
|
||||
record.CreateImageProgress = JsonConvert.SerializeObject(createImageProgress);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
record.ExceptionMessage = e.Message;
|
||||
return false;
|
||||
}
|
||||
Image = image,
|
||||
};
|
||||
try
|
||||
{
|
||||
var imageInspectResponse = await Client.Images.InspectImageAsync(image, cancellationToken);
|
||||
record.ImageInspectResponse = JsonConvert.SerializeObject(imageInspectResponse);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<ContainerDetails> InspectImageAsync(string image, CancellationToken cancellationToken = default)
|
||||
catch (Exception e)
|
||||
{
|
||||
using var record = new DockerServiceInspectImageTelemetryRecord
|
||||
{
|
||||
Image = image,
|
||||
};
|
||||
try
|
||||
{
|
||||
var imageInspectResponse = await Client.Images.InspectImageAsync(image, cancellationToken);
|
||||
record.ImageInspectResponse = JsonConvert.SerializeObject(imageInspectResponse);
|
||||
|
||||
var baseImageRef = string.Empty;
|
||||
var baseImageDigest = string.Empty;
|
||||
|
||||
imageInspectResponse.Config.Labels?.TryGetValue(BaseImageRefAnnotation, out baseImageRef);
|
||||
imageInspectResponse.Config.Labels?.TryGetValue(BaseImageDigestAnnotation, out baseImageDigest);
|
||||
|
||||
record.BaseImageRef = baseImageRef;
|
||||
record.BaseImageDigest = baseImageDigest;
|
||||
|
||||
var layers = imageInspectResponse.RootFS?.Layers
|
||||
.Select((diffId, index) =>
|
||||
new DockerLayer
|
||||
{
|
||||
DiffId = diffId,
|
||||
LayerIndex = index,
|
||||
});
|
||||
|
||||
return new ContainerDetails
|
||||
{
|
||||
Id = GetContainerId(),
|
||||
ImageId = imageInspectResponse.ID,
|
||||
Digests = imageInspectResponse.RepoDigests,
|
||||
Tags = imageInspectResponse.RepoTags,
|
||||
CreatedAt = imageInspectResponse.Created,
|
||||
BaseImageDigest = baseImageDigest,
|
||||
BaseImageRef = baseImageRef,
|
||||
Layers = layers ?? Enumerable.Empty<DockerLayer>(),
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
record.ExceptionMessage = e.Message;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(string Stdout, string Stderr)> CreateAndRunContainerAsync(string image, IList<string> command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var record = new DockerServiceTelemetryRecord
|
||||
{
|
||||
Image = image,
|
||||
Command = JsonConvert.SerializeObject(command),
|
||||
};
|
||||
await this.TryPullImageAsync(image, cancellationToken);
|
||||
var container = await CreateContainerAsync(image, command, cancellationToken);
|
||||
record.Container = JsonConvert.SerializeObject(container);
|
||||
var stream = await AttachContainerAsync(container.ID, cancellationToken);
|
||||
await StartContainerAsync(container.ID, cancellationToken);
|
||||
var (stdout, stderr) = await stream.ReadOutputToEndAsync(cancellationToken);
|
||||
record.Stdout = stdout;
|
||||
record.Stderr = stderr;
|
||||
await RemoveContainerAsync(container.ID, cancellationToken);
|
||||
return (stdout, stderr);
|
||||
}
|
||||
|
||||
private static async Task<CreateContainerResponse> CreateContainerAsync(
|
||||
string image,
|
||||
IList<string> command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var parameters = new CreateContainerParameters
|
||||
{
|
||||
Image = image,
|
||||
Cmd = command,
|
||||
NetworkDisabled = true,
|
||||
HostConfig = new HostConfig
|
||||
{
|
||||
CapDrop = new List<string>
|
||||
{
|
||||
"all",
|
||||
},
|
||||
SecurityOpt = new List<string>
|
||||
{
|
||||
"no-new-privileges",
|
||||
},
|
||||
Binds = new List<string>
|
||||
{
|
||||
$"{Path.GetTempPath()}:/tmp",
|
||||
"/var/run/docker.sock:/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
};
|
||||
return await Client.Containers.CreateContainerAsync(parameters, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<MultiplexedStream> AttachContainerAsync(string containerId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var parameters = new ContainerAttachParameters
|
||||
{
|
||||
Stdout = true,
|
||||
Stderr = true,
|
||||
Stream = true,
|
||||
};
|
||||
return await Client.Containers.AttachContainerAsync(containerId, false, parameters, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task StartContainerAsync(string containerId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var parameters = new ContainerStartParameters();
|
||||
await Client.Containers.StartContainerAsync(containerId, parameters, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task RemoveContainerAsync(string containerId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var parameters = new ContainerRemoveParameters
|
||||
{
|
||||
Force = true,
|
||||
RemoveVolumes = true,
|
||||
};
|
||||
await Client.Containers.RemoveContainerAsync(containerId, parameters, cancellationToken);
|
||||
}
|
||||
|
||||
private static int GetContainerId()
|
||||
{
|
||||
return Interlocked.Increment(ref incrementingContainerId);
|
||||
record.ExceptionMessage = e.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TryPullImageAsync(string image, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var record = new DockerServiceTryPullImageTelemetryRecord
|
||||
{
|
||||
Image = image,
|
||||
};
|
||||
var parameters = new ImagesCreateParameters
|
||||
{
|
||||
FromImage = image,
|
||||
};
|
||||
try
|
||||
{
|
||||
var createImageProgress = new List<string>();
|
||||
var progress = new Progress<JSONMessage>(message =>
|
||||
{
|
||||
createImageProgress.Add(JsonConvert.SerializeObject(message));
|
||||
});
|
||||
await Client.Images.CreateImageAsync(parameters, null, progress, cancellationToken);
|
||||
record.CreateImageProgress = JsonConvert.SerializeObject(createImageProgress);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
record.ExceptionMessage = e.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ContainerDetails> InspectImageAsync(string image, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var record = new DockerServiceInspectImageTelemetryRecord
|
||||
{
|
||||
Image = image,
|
||||
};
|
||||
try
|
||||
{
|
||||
var imageInspectResponse = await Client.Images.InspectImageAsync(image, cancellationToken);
|
||||
record.ImageInspectResponse = JsonConvert.SerializeObject(imageInspectResponse);
|
||||
|
||||
var baseImageRef = string.Empty;
|
||||
var baseImageDigest = string.Empty;
|
||||
|
||||
imageInspectResponse.Config.Labels?.TryGetValue(BaseImageRefAnnotation, out baseImageRef);
|
||||
imageInspectResponse.Config.Labels?.TryGetValue(BaseImageDigestAnnotation, out baseImageDigest);
|
||||
|
||||
record.BaseImageRef = baseImageRef;
|
||||
record.BaseImageDigest = baseImageDigest;
|
||||
|
||||
var layers = imageInspectResponse.RootFS?.Layers
|
||||
.Select((diffId, index) =>
|
||||
new DockerLayer
|
||||
{
|
||||
DiffId = diffId,
|
||||
LayerIndex = index,
|
||||
});
|
||||
|
||||
return new ContainerDetails
|
||||
{
|
||||
Id = GetContainerId(),
|
||||
ImageId = imageInspectResponse.ID,
|
||||
Digests = imageInspectResponse.RepoDigests,
|
||||
Tags = imageInspectResponse.RepoTags,
|
||||
CreatedAt = imageInspectResponse.Created,
|
||||
BaseImageDigest = baseImageDigest,
|
||||
BaseImageRef = baseImageRef,
|
||||
Layers = layers ?? Enumerable.Empty<DockerLayer>(),
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
record.ExceptionMessage = e.Message;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(string Stdout, string Stderr)> CreateAndRunContainerAsync(string image, IList<string> command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var record = new DockerServiceTelemetryRecord
|
||||
{
|
||||
Image = image,
|
||||
Command = JsonConvert.SerializeObject(command),
|
||||
};
|
||||
await this.TryPullImageAsync(image, cancellationToken);
|
||||
var container = await CreateContainerAsync(image, command, cancellationToken);
|
||||
record.Container = JsonConvert.SerializeObject(container);
|
||||
var stream = await AttachContainerAsync(container.ID, cancellationToken);
|
||||
await StartContainerAsync(container.ID, cancellationToken);
|
||||
var (stdout, stderr) = await stream.ReadOutputToEndAsync(cancellationToken);
|
||||
record.Stdout = stdout;
|
||||
record.Stderr = stderr;
|
||||
await RemoveContainerAsync(container.ID, cancellationToken);
|
||||
return (stdout, stderr);
|
||||
}
|
||||
|
||||
private static async Task<CreateContainerResponse> CreateContainerAsync(
|
||||
string image,
|
||||
IList<string> command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var parameters = new CreateContainerParameters
|
||||
{
|
||||
Image = image,
|
||||
Cmd = command,
|
||||
NetworkDisabled = true,
|
||||
HostConfig = new HostConfig
|
||||
{
|
||||
CapDrop = new List<string>
|
||||
{
|
||||
"all",
|
||||
},
|
||||
SecurityOpt = new List<string>
|
||||
{
|
||||
"no-new-privileges",
|
||||
},
|
||||
Binds = new List<string>
|
||||
{
|
||||
$"{Path.GetTempPath()}:/tmp",
|
||||
"/var/run/docker.sock:/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
};
|
||||
return await Client.Containers.CreateContainerAsync(parameters, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<MultiplexedStream> AttachContainerAsync(string containerId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var parameters = new ContainerAttachParameters
|
||||
{
|
||||
Stdout = true,
|
||||
Stderr = true,
|
||||
Stream = true,
|
||||
};
|
||||
return await Client.Containers.AttachContainerAsync(containerId, false, parameters, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task StartContainerAsync(string containerId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var parameters = new ContainerStartParameters();
|
||||
await Client.Containers.StartContainerAsync(containerId, parameters, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task RemoveContainerAsync(string containerId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var parameters = new ContainerRemoveParameters
|
||||
{
|
||||
Force = true,
|
||||
RemoveVolumes = true,
|
||||
};
|
||||
await Client.Containers.RemoveContainerAsync(containerId, parameters, cancellationToken);
|
||||
}
|
||||
|
||||
private static int GetContainerId()
|
||||
{
|
||||
return Interlocked.Increment(ref incrementingContainerId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,32 +3,31 @@ using System.Composition;
|
|||
using System.Linq;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[Export(typeof(IEnvironmentVariableService))]
|
||||
public class EnvironmentVariableService : IEnvironmentVariableService
|
||||
{
|
||||
[Export(typeof(IEnvironmentVariableService))]
|
||||
public class EnvironmentVariableService : IEnvironmentVariableService
|
||||
public bool DoesEnvironmentVariableExist(string name)
|
||||
{
|
||||
public bool DoesEnvironmentVariableExist(string name)
|
||||
{
|
||||
return this.GetEnvironmentVariable(name) != null;
|
||||
}
|
||||
return this.GetEnvironmentVariable(name) != null;
|
||||
}
|
||||
|
||||
public string GetEnvironmentVariable(string name)
|
||||
{
|
||||
// Environment variables are case-insensitive on Windows, and case-sensitive on
|
||||
// Linux and MacOS.
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.environment.getenvironmentvariable
|
||||
var caseInsensitiveName = Environment.GetEnvironmentVariables().Keys
|
||||
.OfType<string>()
|
||||
.FirstOrDefault(x => string.Compare(x, name, true) == 0);
|
||||
public string GetEnvironmentVariable(string name)
|
||||
{
|
||||
// Environment variables are case-insensitive on Windows, and case-sensitive on
|
||||
// Linux and MacOS.
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.environment.getenvironmentvariable
|
||||
var caseInsensitiveName = Environment.GetEnvironmentVariables().Keys
|
||||
.OfType<string>()
|
||||
.FirstOrDefault(x => string.Compare(x, name, true) == 0);
|
||||
|
||||
return caseInsensitiveName != null ? Environment.GetEnvironmentVariable(caseInsensitiveName) : null;
|
||||
}
|
||||
return caseInsensitiveName != null ? Environment.GetEnvironmentVariable(caseInsensitiveName) : null;
|
||||
}
|
||||
|
||||
public bool IsEnvironmentVariableValueTrue(string name)
|
||||
{
|
||||
_ = bool.TryParse(this.GetEnvironmentVariable(name), out var result);
|
||||
return result;
|
||||
}
|
||||
public bool IsEnvironmentVariableValueTrue(string name)
|
||||
{
|
||||
_ = bool.TryParse(this.GetEnvironmentVariable(name), out var result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Exceptions
|
||||
namespace Microsoft.ComponentDetection.Common.Exceptions;
|
||||
|
||||
public class InvalidUserInputException : Exception
|
||||
{
|
||||
public class InvalidUserInputException : Exception
|
||||
public InvalidUserInputException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
public InvalidUserInputException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
|
@ -13,137 +13,137 @@ using System.Threading.Tasks.Dataflow;
|
|||
using Microsoft.ComponentDetection.Contracts;
|
||||
using Microsoft.ComponentDetection.Contracts.Internal;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[Export(typeof(IObservableDirectoryWalkerFactory))]
|
||||
[Export(typeof(FastDirectoryWalkerFactory))]
|
||||
[Shared]
|
||||
public class FastDirectoryWalkerFactory : IObservableDirectoryWalkerFactory
|
||||
{
|
||||
[Export(typeof(IObservableDirectoryWalkerFactory))]
|
||||
[Export(typeof(FastDirectoryWalkerFactory))]
|
||||
[Shared]
|
||||
public class FastDirectoryWalkerFactory : IObservableDirectoryWalkerFactory
|
||||
private readonly ConcurrentDictionary<DirectoryInfo, Lazy<IObservable<FileSystemInfo>>> pendingScans = new ConcurrentDictionary<DirectoryInfo, Lazy<IObservable<FileSystemInfo>>>();
|
||||
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
[Import]
|
||||
public IPathUtilityService PathUtilityService { get; set; }
|
||||
|
||||
public IObservable<FileSystemInfo> GetDirectoryScanner(DirectoryInfo root, ConcurrentDictionary<string, bool> scannedDirectories, ExcludeDirectoryPredicate directoryExclusionPredicate, IEnumerable<string> filePatterns = null, bool recurse = true)
|
||||
{
|
||||
private readonly ConcurrentDictionary<DirectoryInfo, Lazy<IObservable<FileSystemInfo>>> pendingScans = new ConcurrentDictionary<DirectoryInfo, Lazy<IObservable<FileSystemInfo>>>();
|
||||
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
[Import]
|
||||
public IPathUtilityService PathUtilityService { get; set; }
|
||||
|
||||
public IObservable<FileSystemInfo> GetDirectoryScanner(DirectoryInfo root, ConcurrentDictionary<string, bool> scannedDirectories, ExcludeDirectoryPredicate directoryExclusionPredicate, IEnumerable<string> filePatterns = null, bool recurse = true)
|
||||
return Observable.Create<FileSystemInfo>(s =>
|
||||
{
|
||||
return Observable.Create<FileSystemInfo>(s =>
|
||||
if (!root.Exists)
|
||||
{
|
||||
if (!root.Exists)
|
||||
this.Logger?.LogError($"Root directory doesn't exist: {root.FullName}");
|
||||
s.OnCompleted();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
PatternMatchingUtility.FilePatternMatcher fileIsMatch = null;
|
||||
|
||||
if (filePatterns == null || !filePatterns.Any())
|
||||
{
|
||||
fileIsMatch = span => true;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileIsMatch = PatternMatchingUtility.GetFilePatternMatcher(filePatterns);
|
||||
}
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
this.Logger?.LogInfo($"Starting enumeration of {root.FullName}");
|
||||
|
||||
var fileCount = 0;
|
||||
var directoryCount = 0;
|
||||
|
||||
var shouldRecurse = new FileSystemEnumerable<FileSystemInfo>.FindPredicate((ref FileSystemEntry entry) =>
|
||||
{
|
||||
if (!recurse)
|
||||
{
|
||||
this.Logger?.LogError($"Root directory doesn't exist: {root.FullName}");
|
||||
s.OnCompleted();
|
||||
return Task.CompletedTask;
|
||||
return false;
|
||||
}
|
||||
|
||||
PatternMatchingUtility.FilePatternMatcher fileIsMatch = null;
|
||||
|
||||
if (filePatterns == null || !filePatterns.Any())
|
||||
if (!(entry.ToFileSystemInfo() is DirectoryInfo di))
|
||||
{
|
||||
fileIsMatch = span => true;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileIsMatch = PatternMatchingUtility.GetFilePatternMatcher(filePatterns);
|
||||
return false;
|
||||
}
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
var realDirectory = di;
|
||||
|
||||
this.Logger?.LogInfo($"Starting enumeration of {root.FullName}");
|
||||
|
||||
var fileCount = 0;
|
||||
var directoryCount = 0;
|
||||
|
||||
var shouldRecurse = new FileSystemEnumerable<FileSystemInfo>.FindPredicate((ref FileSystemEntry entry) =>
|
||||
if (di.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
{
|
||||
if (!recurse)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var realPath = this.PathUtilityService.ResolvePhysicalPath(di.FullName);
|
||||
|
||||
if (!(entry.ToFileSystemInfo() is DirectoryInfo di))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var realDirectory = di;
|
||||
|
||||
if (di.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
{
|
||||
var realPath = this.PathUtilityService.ResolvePhysicalPath(di.FullName);
|
||||
|
||||
realDirectory = new DirectoryInfo(realPath);
|
||||
}
|
||||
|
||||
if (!scannedDirectories.TryAdd(realDirectory.FullName, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (directoryExclusionPredicate != null)
|
||||
{
|
||||
return !directoryExclusionPredicate(entry.FileName.ToString(), entry.Directory.ToString());
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
var initialIterator = new FileSystemEnumerable<FileSystemInfo>(root.FullName, this.Transform, new EnumerationOptions()
|
||||
{
|
||||
RecurseSubdirectories = false,
|
||||
IgnoreInaccessible = true,
|
||||
ReturnSpecialDirectories = false,
|
||||
})
|
||||
{
|
||||
ShouldIncludePredicate = (ref FileSystemEntry entry) =>
|
||||
{
|
||||
if (!entry.IsDirectory && fileIsMatch(entry.FileName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return shouldRecurse(ref entry);
|
||||
},
|
||||
};
|
||||
|
||||
var observables = new List<IObservable<FileSystemInfo>>();
|
||||
|
||||
var initialFiles = new List<FileInfo>();
|
||||
var initialDirectories = new List<DirectoryInfo>();
|
||||
|
||||
foreach (var fileSystemInfo in initialIterator)
|
||||
{
|
||||
if (fileSystemInfo is FileInfo fi)
|
||||
{
|
||||
initialFiles.Add(fi);
|
||||
}
|
||||
else if (fileSystemInfo is DirectoryInfo di)
|
||||
{
|
||||
initialDirectories.Add(di);
|
||||
}
|
||||
realDirectory = new DirectoryInfo(realPath);
|
||||
}
|
||||
|
||||
observables.Add(Observable.Create<FileSystemInfo>(sub =>
|
||||
if (!scannedDirectories.TryAdd(realDirectory.FullName, true))
|
||||
{
|
||||
foreach (var fileInfo in initialFiles)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (directoryExclusionPredicate != null)
|
||||
{
|
||||
return !directoryExclusionPredicate(entry.FileName.ToString(), entry.Directory.ToString());
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
var initialIterator = new FileSystemEnumerable<FileSystemInfo>(root.FullName, this.Transform, new EnumerationOptions()
|
||||
{
|
||||
RecurseSubdirectories = false,
|
||||
IgnoreInaccessible = true,
|
||||
ReturnSpecialDirectories = false,
|
||||
})
|
||||
{
|
||||
ShouldIncludePredicate = (ref FileSystemEntry entry) =>
|
||||
{
|
||||
if (!entry.IsDirectory && fileIsMatch(entry.FileName))
|
||||
{
|
||||
sub.OnNext(fileInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
sub.OnCompleted();
|
||||
return shouldRecurse(ref entry);
|
||||
},
|
||||
};
|
||||
|
||||
return Task.CompletedTask;
|
||||
}));
|
||||
var observables = new List<IObservable<FileSystemInfo>>();
|
||||
|
||||
if (recurse)
|
||||
var initialFiles = new List<FileInfo>();
|
||||
var initialDirectories = new List<DirectoryInfo>();
|
||||
|
||||
foreach (var fileSystemInfo in initialIterator)
|
||||
{
|
||||
if (fileSystemInfo is FileInfo fi)
|
||||
{
|
||||
observables.Add(Observable.Create<FileSystemInfo>(async observer =>
|
||||
{
|
||||
var scan = new ActionBlock<DirectoryInfo>(
|
||||
di =>
|
||||
initialFiles.Add(fi);
|
||||
}
|
||||
else if (fileSystemInfo is DirectoryInfo di)
|
||||
{
|
||||
initialDirectories.Add(di);
|
||||
}
|
||||
}
|
||||
|
||||
observables.Add(Observable.Create<FileSystemInfo>(sub =>
|
||||
{
|
||||
foreach (var fileInfo in initialFiles)
|
||||
{
|
||||
sub.OnNext(fileInfo);
|
||||
}
|
||||
|
||||
sub.OnCompleted();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}));
|
||||
|
||||
if (recurse)
|
||||
{
|
||||
observables.Add(Observable.Create<FileSystemInfo>(async observer =>
|
||||
{
|
||||
var scan = new ActionBlock<DirectoryInfo>(
|
||||
di =>
|
||||
{
|
||||
var enumerator = new FileSystemEnumerable<FileSystemInfo>(di.FullName, this.Transform, new EnumerationOptions()
|
||||
{
|
||||
|
@ -160,21 +160,21 @@ namespace Microsoft.ComponentDetection.Common
|
|||
observer.OnNext(fileSystemInfo);
|
||||
}
|
||||
},
|
||||
new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount });
|
||||
new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount });
|
||||
|
||||
foreach (var directoryInfo in initialDirectories)
|
||||
{
|
||||
scan.Post(directoryInfo);
|
||||
}
|
||||
foreach (var directoryInfo in initialDirectories)
|
||||
{
|
||||
scan.Post(directoryInfo);
|
||||
}
|
||||
|
||||
scan.Complete();
|
||||
await scan.Completion;
|
||||
observer.OnCompleted();
|
||||
}));
|
||||
}
|
||||
scan.Complete();
|
||||
await scan.Completion;
|
||||
observer.OnCompleted();
|
||||
}));
|
||||
}
|
||||
|
||||
return observables.Concat().Subscribe(
|
||||
info =>
|
||||
return observables.Concat().Subscribe(
|
||||
info =>
|
||||
{
|
||||
if (info is FileInfo)
|
||||
{
|
||||
|
@ -187,60 +187,60 @@ namespace Microsoft.ComponentDetection.Common
|
|||
|
||||
s.OnNext(info);
|
||||
},
|
||||
() =>
|
||||
() =>
|
||||
{
|
||||
sw.Stop();
|
||||
this.Logger?.LogInfo($"Enumerated {fileCount} files and {directoryCount} directories in {sw.Elapsed}");
|
||||
s.OnCompleted();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialized an observable file enumerator.
|
||||
/// </summary>
|
||||
/// <param name="root">Root directory to scan.</param>
|
||||
/// <param name="directoryExclusionPredicate">predicate for excluding directories.</param>
|
||||
/// <param name="minimumConnectionCount">Number of observers that need to subscribe before the observable connects and starts enumerating.</param>
|
||||
/// <param name="filePatterns">Pattern used to filter files.</param>
|
||||
public void Initialize(DirectoryInfo root, ExcludeDirectoryPredicate directoryExclusionPredicate, int minimumConnectionCount, IEnumerable<string> filePatterns = null)
|
||||
/// <summary>
|
||||
/// Initialized an observable file enumerator.
|
||||
/// </summary>
|
||||
/// <param name="root">Root directory to scan.</param>
|
||||
/// <param name="directoryExclusionPredicate">predicate for excluding directories.</param>
|
||||
/// <param name="minimumConnectionCount">Number of observers that need to subscribe before the observable connects and starts enumerating.</param>
|
||||
/// <param name="filePatterns">Pattern used to filter files.</param>
|
||||
public void Initialize(DirectoryInfo root, ExcludeDirectoryPredicate directoryExclusionPredicate, int minimumConnectionCount, IEnumerable<string> filePatterns = null)
|
||||
{
|
||||
this.pendingScans.GetOrAdd(root, new Lazy<IObservable<FileSystemInfo>>(() => this.CreateDirectoryWalker(root, directoryExclusionPredicate, minimumConnectionCount, filePatterns)));
|
||||
}
|
||||
|
||||
public IObservable<FileSystemInfo> Subscribe(DirectoryInfo root, IEnumerable<string> patterns)
|
||||
{
|
||||
var patternArray = patterns.ToArray();
|
||||
|
||||
if (this.pendingScans.TryGetValue(root, out var scannerObservable))
|
||||
{
|
||||
this.pendingScans.GetOrAdd(root, new Lazy<IObservable<FileSystemInfo>>(() => this.CreateDirectoryWalker(root, directoryExclusionPredicate, minimumConnectionCount, filePatterns)));
|
||||
}
|
||||
this.Logger.LogVerbose(string.Join(":", patterns));
|
||||
|
||||
public IObservable<FileSystemInfo> Subscribe(DirectoryInfo root, IEnumerable<string> patterns)
|
||||
{
|
||||
var patternArray = patterns.ToArray();
|
||||
|
||||
if (this.pendingScans.TryGetValue(root, out var scannerObservable))
|
||||
var inner = scannerObservable.Value.Where(fsi =>
|
||||
{
|
||||
this.Logger.LogVerbose(string.Join(":", patterns));
|
||||
|
||||
var inner = scannerObservable.Value.Where(fsi =>
|
||||
if (fsi is FileInfo fi)
|
||||
{
|
||||
if (fsi is FileInfo fi)
|
||||
{
|
||||
return this.MatchesAnyPattern(fi, patternArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return this.MatchesAnyPattern(fi, patternArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return inner;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Subscribe called without initializing scanner");
|
||||
return inner;
|
||||
}
|
||||
|
||||
public IObservable<ProcessRequest> GetFilteredComponentStreamObservable(DirectoryInfo root, IEnumerable<string> patterns, IComponentRecorder componentRecorder)
|
||||
throw new InvalidOperationException("Subscribe called without initializing scanner");
|
||||
}
|
||||
|
||||
public IObservable<ProcessRequest> GetFilteredComponentStreamObservable(DirectoryInfo root, IEnumerable<string> patterns, IComponentRecorder componentRecorder)
|
||||
{
|
||||
var observable = this.Subscribe(root, patterns).OfType<FileInfo>().SelectMany(f => patterns.Select(sp => new
|
||||
{
|
||||
var observable = this.Subscribe(root, patterns).OfType<FileInfo>().SelectMany(f => patterns.Select(sp => new
|
||||
{
|
||||
SearchPattern = sp,
|
||||
File = f,
|
||||
})).Where(x =>
|
||||
SearchPattern = sp,
|
||||
File = f,
|
||||
})).Where(x =>
|
||||
{
|
||||
var searchPattern = x.SearchPattern;
|
||||
var fileName = x.File.Name;
|
||||
|
@ -257,38 +257,37 @@ namespace Microsoft.ComponentDetection.Common
|
|||
};
|
||||
});
|
||||
|
||||
return observable;
|
||||
}
|
||||
return observable;
|
||||
}
|
||||
|
||||
public void StartScan(DirectoryInfo root)
|
||||
public void StartScan(DirectoryInfo root)
|
||||
{
|
||||
if (this.pendingScans.TryRemove(root, out var scannerObservable))
|
||||
{
|
||||
if (this.pendingScans.TryRemove(root, out var scannerObservable))
|
||||
{
|
||||
// scannerObservable.Connect();
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("StartScan called without initializing scanner");
|
||||
// scannerObservable.Connect();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.pendingScans.Clear();
|
||||
}
|
||||
throw new InvalidOperationException("StartScan called without initializing scanner");
|
||||
}
|
||||
|
||||
private FileSystemInfo Transform(ref FileSystemEntry entry)
|
||||
{
|
||||
return entry.ToFileSystemInfo();
|
||||
}
|
||||
public void Reset()
|
||||
{
|
||||
this.pendingScans.Clear();
|
||||
}
|
||||
|
||||
private IObservable<FileSystemInfo> CreateDirectoryWalker(DirectoryInfo di, ExcludeDirectoryPredicate directoryExclusionPredicate, int minimumConnectionCount, IEnumerable<string> filePatterns)
|
||||
{
|
||||
return this.GetDirectoryScanner(di, new ConcurrentDictionary<string, bool>(), directoryExclusionPredicate, filePatterns, true).Replay() // Returns a replay subject which will republish anything found to new subscribers.
|
||||
.AutoConnect(minimumConnectionCount); // Specifies that this connectable observable should start when minimumConnectionCount subscribe.
|
||||
}
|
||||
private FileSystemInfo Transform(ref FileSystemEntry entry)
|
||||
{
|
||||
return entry.ToFileSystemInfo();
|
||||
}
|
||||
|
||||
private bool MatchesAnyPattern(FileInfo fi, params string[] searchPatterns)
|
||||
{
|
||||
return searchPatterns != null && searchPatterns.Any(sp => this.PathUtilityService.MatchesPattern(sp, fi.Name));
|
||||
}
|
||||
private IObservable<FileSystemInfo> CreateDirectoryWalker(DirectoryInfo di, ExcludeDirectoryPredicate directoryExclusionPredicate, int minimumConnectionCount, IEnumerable<string> filePatterns)
|
||||
{
|
||||
return this.GetDirectoryScanner(di, new ConcurrentDictionary<string, bool>(), directoryExclusionPredicate, filePatterns, true).Replay() // Returns a replay subject which will republish anything found to new subscribers.
|
||||
.AutoConnect(minimumConnectionCount); // Specifies that this connectable observable should start when minimumConnectionCount subscribe.
|
||||
}
|
||||
|
||||
private bool MatchesAnyPattern(FileInfo fi, params string[] searchPatterns)
|
||||
{
|
||||
return searchPatterns != null && searchPatterns.Any(sp => this.PathUtilityService.MatchesPattern(sp, fi.Name));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,34 +2,33 @@
|
|||
using System.IO;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps some common file operations for easier testability. This interface is *only used by the command line driven app*.
|
||||
/// </summary>
|
||||
[Export(typeof(IFileUtilityService))]
|
||||
[Export(typeof(FileUtilityService))]
|
||||
[Shared]
|
||||
public class FileUtilityService : IFileUtilityService
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps some common file operations for easier testability. This interface is *only used by the command line driven app*.
|
||||
/// </summary>
|
||||
[Export(typeof(IFileUtilityService))]
|
||||
[Export(typeof(FileUtilityService))]
|
||||
[Shared]
|
||||
public class FileUtilityService : IFileUtilityService
|
||||
public string ReadAllText(string filePath)
|
||||
{
|
||||
public string ReadAllText(string filePath)
|
||||
{
|
||||
return File.ReadAllText(filePath);
|
||||
}
|
||||
return File.ReadAllText(filePath);
|
||||
}
|
||||
|
||||
public string ReadAllText(FileInfo file)
|
||||
{
|
||||
return File.ReadAllText(file.FullName);
|
||||
}
|
||||
public string ReadAllText(FileInfo file)
|
||||
{
|
||||
return File.ReadAllText(file.FullName);
|
||||
}
|
||||
|
||||
public bool Exists(string fileName)
|
||||
{
|
||||
return File.Exists(fileName);
|
||||
}
|
||||
public bool Exists(string fileName)
|
||||
{
|
||||
return File.Exists(fileName);
|
||||
}
|
||||
|
||||
public Stream MakeFileStream(string fileName)
|
||||
{
|
||||
return new FileStream(fileName, FileMode.Open, FileAccess.Read);
|
||||
}
|
||||
public Stream MakeFileStream(string fileName)
|
||||
{
|
||||
return new FileStream(fileName, FileMode.Open, FileAccess.Read);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,75 +1,74 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Composition;
|
||||
using System.IO;
|
||||
using Microsoft.ComponentDetection.Common.Exceptions;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[Export(typeof(IFileWritingService))]
|
||||
[Export(typeof(FileWritingService))]
|
||||
[Shared]
|
||||
public class FileWritingService : IFileWritingService
|
||||
{
|
||||
[Export(typeof(IFileWritingService))]
|
||||
[Export(typeof(FileWritingService))]
|
||||
[Shared]
|
||||
public class FileWritingService : IFileWritingService
|
||||
public const string TimestampFormatString = "yyyyMMddHHmmssfff";
|
||||
|
||||
private readonly object lockObject = new object();
|
||||
private readonly string timestamp = DateTime.Now.ToString(TimestampFormatString);
|
||||
|
||||
public string BasePath { get; private set; }
|
||||
|
||||
public void Init(string basePath)
|
||||
{
|
||||
public const string TimestampFormatString = "yyyyMMddHHmmssfff";
|
||||
|
||||
private readonly object lockObject = new object();
|
||||
private readonly string timestamp = DateTime.Now.ToString(TimestampFormatString);
|
||||
|
||||
public string BasePath { get; private set; }
|
||||
|
||||
public void Init(string basePath)
|
||||
if (!string.IsNullOrEmpty(basePath) && !Directory.Exists(basePath))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(basePath) && !Directory.Exists(basePath))
|
||||
{
|
||||
throw new InvalidUserInputException($"The path {basePath} does not exist.", new DirectoryNotFoundException());
|
||||
}
|
||||
|
||||
this.BasePath = string.IsNullOrEmpty(basePath) ? Path.GetTempPath() : basePath;
|
||||
throw new InvalidUserInputException($"The path {basePath} does not exist.", new DirectoryNotFoundException());
|
||||
}
|
||||
|
||||
public void AppendToFile(string relativeFilePath, string text)
|
||||
{
|
||||
relativeFilePath = this.ResolveFilePath(relativeFilePath);
|
||||
this.BasePath = string.IsNullOrEmpty(basePath) ? Path.GetTempPath() : basePath;
|
||||
}
|
||||
|
||||
lock (this.lockObject)
|
||||
{
|
||||
File.AppendAllText(relativeFilePath, text);
|
||||
}
|
||||
public void AppendToFile(string relativeFilePath, string text)
|
||||
{
|
||||
relativeFilePath = this.ResolveFilePath(relativeFilePath);
|
||||
|
||||
lock (this.lockObject)
|
||||
{
|
||||
File.AppendAllText(relativeFilePath, text);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteFile(string relativeFilePath, string text)
|
||||
{
|
||||
relativeFilePath = this.ResolveFilePath(relativeFilePath);
|
||||
|
||||
lock (this.lockObject)
|
||||
{
|
||||
File.WriteAllText(relativeFilePath, text);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteFile(FileInfo absolutePath, string text)
|
||||
{
|
||||
File.WriteAllText(absolutePath.FullName, text);
|
||||
}
|
||||
|
||||
public string ResolveFilePath(string relativeFilePath)
|
||||
{
|
||||
this.EnsureInit();
|
||||
if (relativeFilePath.Contains("{timestamp}"))
|
||||
{
|
||||
relativeFilePath = relativeFilePath.Replace("{timestamp}", this.timestamp);
|
||||
}
|
||||
|
||||
public void WriteFile(string relativeFilePath, string text)
|
||||
relativeFilePath = Path.Combine(this.BasePath, relativeFilePath);
|
||||
return relativeFilePath;
|
||||
}
|
||||
|
||||
private void EnsureInit()
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.BasePath))
|
||||
{
|
||||
relativeFilePath = this.ResolveFilePath(relativeFilePath);
|
||||
|
||||
lock (this.lockObject)
|
||||
{
|
||||
File.WriteAllText(relativeFilePath, text);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteFile(FileInfo absolutePath, string text)
|
||||
{
|
||||
File.WriteAllText(absolutePath.FullName, text);
|
||||
}
|
||||
|
||||
public string ResolveFilePath(string relativeFilePath)
|
||||
{
|
||||
this.EnsureInit();
|
||||
if (relativeFilePath.Contains("{timestamp}"))
|
||||
{
|
||||
relativeFilePath = relativeFilePath.Replace("{timestamp}", this.timestamp);
|
||||
}
|
||||
|
||||
relativeFilePath = Path.Combine(this.BasePath, relativeFilePath);
|
||||
return relativeFilePath;
|
||||
}
|
||||
|
||||
private void EnsureInit()
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.BasePath))
|
||||
{
|
||||
throw new InvalidOperationException("Base path has not yet been initialized in File Writing Service!");
|
||||
}
|
||||
throw new InvalidOperationException("Base path has not yet been initialized in File Writing Service!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public interface IConsoleWritingService
|
||||
{
|
||||
public interface IConsoleWritingService
|
||||
{
|
||||
void Write(string content);
|
||||
}
|
||||
void Write(string content);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
// All file paths are relative and will replace occurrences of {timestamp} with the shared file timestamp.
|
||||
public interface IFileWritingService
|
||||
{
|
||||
// All file paths are relative and will replace occurrences of {timestamp} with the shared file timestamp.
|
||||
public interface IFileWritingService
|
||||
{
|
||||
void AppendToFile(string relativeFilePath, string text);
|
||||
void AppendToFile(string relativeFilePath, string text);
|
||||
|
||||
void WriteFile(string relativeFilePath, string text);
|
||||
void WriteFile(string relativeFilePath, string text);
|
||||
|
||||
void WriteFile(FileInfo relativeFilePath, string text);
|
||||
void WriteFile(FileInfo relativeFilePath, string text);
|
||||
|
||||
string ResolveFilePath(string relativeFilePath);
|
||||
}
|
||||
string ResolveFilePath(string relativeFilePath);
|
||||
}
|
||||
|
|
|
@ -2,16 +2,15 @@
|
|||
using System.IO;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
/// <summary>Factory for generating safe file enumerables.</summary>
|
||||
public interface ISafeFileEnumerableFactory
|
||||
{
|
||||
/// <summary>Factory for generating safe file enumerables.</summary>
|
||||
public interface ISafeFileEnumerableFactory
|
||||
{
|
||||
/// <summary>Creates a "safe" file enumerable, which provides logging while evaluating search patterns on a known directory structure.</summary>
|
||||
/// <param name="directory">The directory to search "from", e.g. the top level directory being searched.</param>
|
||||
/// <param name="searchPatterns">The patterns to use in the search.</param>
|
||||
/// <param name="directoryExclusionPredicate">Predicate which indicates which directories should be excluded.</param>
|
||||
/// <returns>A FileInfo enumerable that should be iterated over, containing all valid files given the input patterns and directory exclusions.</returns>
|
||||
IEnumerable<MatchedFile> CreateSafeFileEnumerable(DirectoryInfo directory, IEnumerable<string> searchPatterns, ExcludeDirectoryPredicate directoryExclusionPredicate);
|
||||
}
|
||||
/// <summary>Creates a "safe" file enumerable, which provides logging while evaluating search patterns on a known directory structure.</summary>
|
||||
/// <param name="directory">The directory to search "from", e.g. the top level directory being searched.</param>
|
||||
/// <param name="searchPatterns">The patterns to use in the search.</param>
|
||||
/// <param name="directoryExclusionPredicate">Predicate which indicates which directories should be excluded.</param>
|
||||
/// <returns>A FileInfo enumerable that should be iterated over, containing all valid files given the input patterns and directory exclusions.</returns>
|
||||
IEnumerable<MatchedFile> CreateSafeFileEnumerable(DirectoryInfo directory, IEnumerable<string> searchPatterns, ExcludeDirectoryPredicate directoryExclusionPredicate);
|
||||
}
|
||||
|
|
|
@ -2,51 +2,50 @@
|
|||
using System.IO;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public class LazyComponentStream : IComponentStream
|
||||
{
|
||||
public class LazyComponentStream : IComponentStream
|
||||
private readonly FileInfo fileInfo;
|
||||
private readonly Lazy<byte[]> fileBuffer;
|
||||
private readonly ILogger logger;
|
||||
|
||||
public LazyComponentStream(FileInfo fileInfo, string pattern, ILogger logger)
|
||||
{
|
||||
private readonly FileInfo fileInfo;
|
||||
private readonly Lazy<byte[]> fileBuffer;
|
||||
private readonly ILogger logger;
|
||||
this.Pattern = pattern;
|
||||
this.Location = fileInfo.FullName;
|
||||
this.fileInfo = fileInfo;
|
||||
this.logger = logger;
|
||||
this.fileBuffer = new Lazy<byte[]>(this.SafeOpenFile);
|
||||
}
|
||||
|
||||
public LazyComponentStream(FileInfo fileInfo, string pattern, ILogger logger)
|
||||
public Stream Stream => new MemoryStream(this.fileBuffer.Value);
|
||||
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public string Location { get; set; }
|
||||
|
||||
private byte[] SafeOpenFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Pattern = pattern;
|
||||
this.Location = fileInfo.FullName;
|
||||
this.fileInfo = fileInfo;
|
||||
this.logger = logger;
|
||||
this.fileBuffer = new Lazy<byte[]>(this.SafeOpenFile);
|
||||
using var fs = this.fileInfo.OpenRead();
|
||||
|
||||
var buffer = new byte[this.fileInfo.Length];
|
||||
fs.Read(buffer, 0, (int)this.fileInfo.Length);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
this.logger?.LogWarning($"Unauthorized access exception caught when trying to open {this.fileInfo.FullName}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.logger?.LogWarning($"Unhandled exception caught when trying to open {this.fileInfo.FullName}");
|
||||
this.logger?.LogException(e, isError: false);
|
||||
}
|
||||
|
||||
public Stream Stream => new MemoryStream(this.fileBuffer.Value);
|
||||
|
||||
public string Pattern { get; set; }
|
||||
|
||||
public string Location { get; set; }
|
||||
|
||||
private byte[] SafeOpenFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fs = this.fileInfo.OpenRead();
|
||||
|
||||
var buffer = new byte[this.fileInfo.Length];
|
||||
fs.Read(buffer, 0, (int)this.fileInfo.Length);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
this.logger?.LogWarning($"Unauthorized access exception caught when trying to open {this.fileInfo.FullName}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.logger?.LogWarning($"Unhandled exception caught when trying to open {this.fileInfo.FullName}");
|
||||
this.logger?.LogException(e, isError: false);
|
||||
}
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Composition;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
@ -6,152 +6,151 @@ using Microsoft.ComponentDetection.Contracts;
|
|||
|
||||
using static System.Environment;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[Export(typeof(ILogger))]
|
||||
[Export(typeof(Logger))]
|
||||
[Shared]
|
||||
public class Logger : ILogger
|
||||
{
|
||||
[Export(typeof(ILogger))]
|
||||
[Export(typeof(Logger))]
|
||||
[Shared]
|
||||
public class Logger : ILogger
|
||||
public const string LogRelativePath = "GovCompDisc_Log_{timestamp}.log";
|
||||
|
||||
[Import]
|
||||
public IFileWritingService FileWritingService { get; set; }
|
||||
|
||||
[Import]
|
||||
public IConsoleWritingService ConsoleWriter { get; set; }
|
||||
|
||||
private VerbosityMode Verbosity { get; set; }
|
||||
|
||||
private bool WriteToFile { get; set; }
|
||||
|
||||
private bool WriteLinePrefix { get; set; }
|
||||
|
||||
public void Init(VerbosityMode verbosity, bool writeLinePrefix = true)
|
||||
{
|
||||
public const string LogRelativePath = "GovCompDisc_Log_{timestamp}.log";
|
||||
|
||||
[Import]
|
||||
public IFileWritingService FileWritingService { get; set; }
|
||||
|
||||
[Import]
|
||||
public IConsoleWritingService ConsoleWriter { get; set; }
|
||||
|
||||
private VerbosityMode Verbosity { get; set; }
|
||||
|
||||
private bool WriteToFile { get; set; }
|
||||
|
||||
private bool WriteLinePrefix { get; set; }
|
||||
|
||||
public void Init(VerbosityMode verbosity, bool writeLinePrefix = true)
|
||||
this.WriteToFile = true;
|
||||
this.Verbosity = verbosity;
|
||||
this.WriteLinePrefix = writeLinePrefix;
|
||||
try
|
||||
{
|
||||
this.WriteToFile = true;
|
||||
this.Verbosity = verbosity;
|
||||
this.WriteLinePrefix = writeLinePrefix;
|
||||
try
|
||||
{
|
||||
this.FileWritingService.WriteFile(LogRelativePath, string.Empty);
|
||||
this.LogInfo($"Log file: {this.FileWritingService.ResolveFilePath(LogRelativePath)}");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
this.WriteToFile = false;
|
||||
this.LogError("There was an issue writing to the log file, for the remainder of execution verbose output will be written to the console.");
|
||||
this.Verbosity = VerbosityMode.Verbose;
|
||||
}
|
||||
this.FileWritingService.WriteFile(LogRelativePath, string.Empty);
|
||||
this.LogInfo($"Log file: {this.FileWritingService.ResolveFilePath(LogRelativePath)}");
|
||||
}
|
||||
|
||||
public void LogCreateLoggingGroup()
|
||||
catch (Exception)
|
||||
{
|
||||
this.PrintToConsole(NewLine, VerbosityMode.Normal);
|
||||
this.AppendToFile(NewLine);
|
||||
}
|
||||
|
||||
public void LogWarning(string message)
|
||||
{
|
||||
this.LogInternal("WARN", message);
|
||||
}
|
||||
|
||||
public void LogInfo(string message)
|
||||
{
|
||||
this.LogInternal("INFO", message);
|
||||
}
|
||||
|
||||
public void LogVerbose(string message)
|
||||
{
|
||||
this.LogInternal("VERBOSE", message, VerbosityMode.Verbose);
|
||||
}
|
||||
|
||||
public void LogError(string message)
|
||||
{
|
||||
this.LogInternal("ERROR", message, VerbosityMode.Quiet);
|
||||
}
|
||||
|
||||
public void LogFailedReadingFile(string filePath, Exception e)
|
||||
{
|
||||
this.PrintToConsole(NewLine, VerbosityMode.Verbose);
|
||||
this.LogFailedProcessingFile(filePath);
|
||||
this.LogException(e, isError: false);
|
||||
using var record = new FailedReadingFileRecord
|
||||
{
|
||||
FilePath = filePath,
|
||||
ExceptionMessage = e.Message,
|
||||
StackTrace = e.StackTrace,
|
||||
};
|
||||
}
|
||||
|
||||
public void LogException(
|
||||
Exception e,
|
||||
bool isError,
|
||||
bool printException = false,
|
||||
[CallerMemberName] string callerMemberName = "",
|
||||
[CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
var tag = isError ? "[ERROR]" : "[INFO]";
|
||||
|
||||
var fullExceptionText = $"{tag} Exception encountered." + NewLine +
|
||||
$"CallerMember: [{callerMemberName} : {callerLineNumber}]" + NewLine +
|
||||
e.ToString() + NewLine;
|
||||
|
||||
var shortExceptionText = $"{tag} {callerMemberName} logged {e.GetType().Name}: {e.Message}{NewLine}";
|
||||
|
||||
var consoleText = printException ? fullExceptionText : shortExceptionText;
|
||||
|
||||
if (isError)
|
||||
{
|
||||
this.PrintToConsole(consoleText, VerbosityMode.Quiet);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.PrintToConsole(consoleText, VerbosityMode.Verbose);
|
||||
}
|
||||
|
||||
this.AppendToFile(fullExceptionText);
|
||||
}
|
||||
|
||||
// TODO: All these vso specific logs should go away
|
||||
public void LogBuildWarning(string message)
|
||||
{
|
||||
this.PrintToConsole($"##vso[task.LogIssue type=warning;]{message}{NewLine}", VerbosityMode.Quiet);
|
||||
}
|
||||
|
||||
public void LogBuildError(string message)
|
||||
{
|
||||
this.PrintToConsole($"##vso[task.LogIssue type=error;]{message}{NewLine}", VerbosityMode.Quiet);
|
||||
}
|
||||
|
||||
private void LogFailedProcessingFile(string filePath)
|
||||
{
|
||||
this.LogVerbose($"Could not read component details from file {filePath} {NewLine}");
|
||||
}
|
||||
|
||||
private void AppendToFile(string text)
|
||||
{
|
||||
if (this.WriteToFile)
|
||||
{
|
||||
this.FileWritingService.AppendToFile(LogRelativePath, text);
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintToConsole(string text, VerbosityMode minVerbosity)
|
||||
{
|
||||
if (this.Verbosity >= minVerbosity)
|
||||
{
|
||||
this.ConsoleWriter.Write(text);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogInternal(string prefix, string message, VerbosityMode verbosity = VerbosityMode.Normal)
|
||||
{
|
||||
var formattedPrefix = (!this.WriteLinePrefix || string.IsNullOrWhiteSpace(prefix)) ? string.Empty : $"[{prefix}] ";
|
||||
var text = $"{formattedPrefix}{message} {NewLine}";
|
||||
|
||||
this.PrintToConsole(text, verbosity);
|
||||
this.AppendToFile(text);
|
||||
this.WriteToFile = false;
|
||||
this.LogError("There was an issue writing to the log file, for the remainder of execution verbose output will be written to the console.");
|
||||
this.Verbosity = VerbosityMode.Verbose;
|
||||
}
|
||||
}
|
||||
|
||||
public void LogCreateLoggingGroup()
|
||||
{
|
||||
this.PrintToConsole(NewLine, VerbosityMode.Normal);
|
||||
this.AppendToFile(NewLine);
|
||||
}
|
||||
|
||||
public void LogWarning(string message)
|
||||
{
|
||||
this.LogInternal("WARN", message);
|
||||
}
|
||||
|
||||
public void LogInfo(string message)
|
||||
{
|
||||
this.LogInternal("INFO", message);
|
||||
}
|
||||
|
||||
public void LogVerbose(string message)
|
||||
{
|
||||
this.LogInternal("VERBOSE", message, VerbosityMode.Verbose);
|
||||
}
|
||||
|
||||
public void LogError(string message)
|
||||
{
|
||||
this.LogInternal("ERROR", message, VerbosityMode.Quiet);
|
||||
}
|
||||
|
||||
public void LogFailedReadingFile(string filePath, Exception e)
|
||||
{
|
||||
this.PrintToConsole(NewLine, VerbosityMode.Verbose);
|
||||
this.LogFailedProcessingFile(filePath);
|
||||
this.LogException(e, isError: false);
|
||||
using var record = new FailedReadingFileRecord
|
||||
{
|
||||
FilePath = filePath,
|
||||
ExceptionMessage = e.Message,
|
||||
StackTrace = e.StackTrace,
|
||||
};
|
||||
}
|
||||
|
||||
public void LogException(
|
||||
Exception e,
|
||||
bool isError,
|
||||
bool printException = false,
|
||||
[CallerMemberName] string callerMemberName = "",
|
||||
[CallerLineNumber] int callerLineNumber = 0)
|
||||
{
|
||||
var tag = isError ? "[ERROR]" : "[INFO]";
|
||||
|
||||
var fullExceptionText = $"{tag} Exception encountered." + NewLine +
|
||||
$"CallerMember: [{callerMemberName} : {callerLineNumber}]" + NewLine +
|
||||
e.ToString() + NewLine;
|
||||
|
||||
var shortExceptionText = $"{tag} {callerMemberName} logged {e.GetType().Name}: {e.Message}{NewLine}";
|
||||
|
||||
var consoleText = printException ? fullExceptionText : shortExceptionText;
|
||||
|
||||
if (isError)
|
||||
{
|
||||
this.PrintToConsole(consoleText, VerbosityMode.Quiet);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.PrintToConsole(consoleText, VerbosityMode.Verbose);
|
||||
}
|
||||
|
||||
this.AppendToFile(fullExceptionText);
|
||||
}
|
||||
|
||||
// TODO: All these vso specific logs should go away
|
||||
public void LogBuildWarning(string message)
|
||||
{
|
||||
this.PrintToConsole($"##vso[task.LogIssue type=warning;]{message}{NewLine}", VerbosityMode.Quiet);
|
||||
}
|
||||
|
||||
public void LogBuildError(string message)
|
||||
{
|
||||
this.PrintToConsole($"##vso[task.LogIssue type=error;]{message}{NewLine}", VerbosityMode.Quiet);
|
||||
}
|
||||
|
||||
private void LogFailedProcessingFile(string filePath)
|
||||
{
|
||||
this.LogVerbose($"Could not read component details from file {filePath} {NewLine}");
|
||||
}
|
||||
|
||||
private void AppendToFile(string text)
|
||||
{
|
||||
if (this.WriteToFile)
|
||||
{
|
||||
this.FileWritingService.AppendToFile(LogRelativePath, text);
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintToConsole(string text, VerbosityMode minVerbosity)
|
||||
{
|
||||
if (this.Verbosity >= minVerbosity)
|
||||
{
|
||||
this.ConsoleWriter.Write(text);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogInternal(string prefix, string message, VerbosityMode verbosity = VerbosityMode.Normal)
|
||||
{
|
||||
var formattedPrefix = (!this.WriteLinePrefix || string.IsNullOrWhiteSpace(prefix)) ? string.Empty : $"[{prefix}] ";
|
||||
var text = $"{formattedPrefix}{message} {NewLine}";
|
||||
|
||||
this.PrintToConsole(text, verbosity);
|
||||
this.AppendToFile(text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
{
|
||||
public class MatchedFile
|
||||
{
|
||||
public FileInfo File { get; set; }
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public string Pattern { get; set; }
|
||||
}
|
||||
public class MatchedFile
|
||||
{
|
||||
public FileInfo File { get; set; }
|
||||
|
||||
public string Pattern { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Composition;
|
||||
using System.Diagnostics;
|
||||
|
@ -9,282 +9,281 @@ using System.Text;
|
|||
using Microsoft.ComponentDetection.Contracts;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
// We may want to consider breaking this class into Win/Mac/Linux variants if it gets bigger
|
||||
[Export(typeof(IPathUtilityService))]
|
||||
[Export(typeof(PathUtilityService))]
|
||||
[Shared]
|
||||
public class PathUtilityService : IPathUtilityService
|
||||
{
|
||||
// We may want to consider breaking this class into Win/Mac/Linux variants if it gets bigger
|
||||
[Export(typeof(IPathUtilityService))]
|
||||
[Export(typeof(PathUtilityService))]
|
||||
[Shared]
|
||||
public class PathUtilityService : IPathUtilityService
|
||||
public const uint CreationDispositionRead = 0x3;
|
||||
|
||||
public const uint FileFlagBackupSemantics = 0x02000000;
|
||||
|
||||
public const int InitalPathBufferSize = 512;
|
||||
|
||||
public const string LongPathPrefix = "\\\\?\\";
|
||||
|
||||
private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
private readonly ConcurrentDictionary<string, string> resolvedPaths = new ConcurrentDictionary<string, string>();
|
||||
|
||||
private readonly object isRunningOnWindowsContainerLock = new object();
|
||||
private bool? isRunningOnWindowsContainer = null;
|
||||
|
||||
public bool IsRunningOnWindowsContainer
|
||||
{
|
||||
public const uint CreationDispositionRead = 0x3;
|
||||
|
||||
public const uint FileFlagBackupSemantics = 0x02000000;
|
||||
|
||||
public const int InitalPathBufferSize = 512;
|
||||
|
||||
public const string LongPathPrefix = "\\\\?\\";
|
||||
|
||||
private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
private readonly ConcurrentDictionary<string, string> resolvedPaths = new ConcurrentDictionary<string, string>();
|
||||
|
||||
private readonly object isRunningOnWindowsContainerLock = new object();
|
||||
private bool? isRunningOnWindowsContainer = null;
|
||||
|
||||
public bool IsRunningOnWindowsContainer
|
||||
get
|
||||
{
|
||||
get
|
||||
if (!this.isRunningOnWindowsContainer.HasValue)
|
||||
{
|
||||
if (!this.isRunningOnWindowsContainer.HasValue)
|
||||
lock (this.isRunningOnWindowsContainerLock)
|
||||
{
|
||||
lock (this.isRunningOnWindowsContainerLock)
|
||||
if (!this.isRunningOnWindowsContainer.HasValue)
|
||||
{
|
||||
if (!this.isRunningOnWindowsContainer.HasValue)
|
||||
{
|
||||
this.isRunningOnWindowsContainer = this.CheckIfRunningOnWindowsContainer();
|
||||
}
|
||||
this.isRunningOnWindowsContainer = this.CheckIfRunningOnWindowsContainer();
|
||||
}
|
||||
}
|
||||
|
||||
return this.isRunningOnWindowsContainer.Value;
|
||||
}
|
||||
|
||||
return this.isRunningOnWindowsContainer.Value;
|
||||
}
|
||||
}
|
||||
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This call can be made on a linux system to get the absolute path of a file. It will resolve nested layers.
|
||||
/// Note: You may pass IntPtr.Zero to the output parameter. You MUST then free the IntPtr that RealPathLinux returns
|
||||
/// using FreeMemoryLinux otherwise things will get very leaky.
|
||||
/// </summary>
|
||||
/// <param name="path"> The path to resolve. </param>
|
||||
/// <param name="output"> The pointer output. </param>
|
||||
/// <returns> A pointer <see cref= "IntPtr"/> to the absolute path of a file. </returns>
|
||||
[DllImport("libc", EntryPoint = "realpath")]
|
||||
public static extern IntPtr RealPathLinux([MarshalAs(UnmanagedType.LPStr)] string path, IntPtr output);
|
||||
|
||||
/// <summary>
|
||||
/// Use this function to free memory and prevent memory leaks.
|
||||
/// However, beware.... Improper usage of this function will cause segfaults and other nasty double-free errors.
|
||||
/// THIS WILL CRASH THE CLR IF YOU USE IT WRONG.
|
||||
/// </summary>
|
||||
/// <param name="toFree">Pointer to the memory space to free. </param>
|
||||
[DllImport("libc", EntryPoint = "free")]
|
||||
public static extern void FreeMemoryLinux([In] IntPtr toFree);
|
||||
|
||||
public static bool MatchesPattern(string searchPattern, ref FileSystemEntry fse)
|
||||
{
|
||||
if (searchPattern.StartsWith("*") && fse.FileName.EndsWith(searchPattern.AsSpan()[1..], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (searchPattern.EndsWith("*") && fse.FileName.StartsWith(searchPattern.AsSpan()[..^1], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (fse.FileName.Equals(searchPattern.AsSpan(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetParentDirectory(string path)
|
||||
{
|
||||
return Path.GetDirectoryName(path);
|
||||
}
|
||||
|
||||
public bool IsFileBelowAnother(string aboveFilePath, string belowFilePath)
|
||||
{
|
||||
var aboveDirectoryPath = Path.GetDirectoryName(aboveFilePath);
|
||||
var belowDirectoryPath = Path.GetDirectoryName(belowFilePath);
|
||||
|
||||
// Return true if they are not the same path but the second has the first as its base
|
||||
return (aboveDirectoryPath.Length != belowDirectoryPath.Length) && belowDirectoryPath.StartsWith(aboveDirectoryPath);
|
||||
}
|
||||
|
||||
public bool MatchesPattern(string searchPattern, string fileName)
|
||||
{
|
||||
if (searchPattern.StartsWith("*") && fileName.EndsWith(searchPattern[1..], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (searchPattern.EndsWith("*") && fileName.StartsWith(searchPattern[..^1], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (searchPattern.Equals(fileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string ResolvePhysicalPath(string path)
|
||||
{
|
||||
if (IsWindows)
|
||||
{
|
||||
return this.ResolvePhysicalPathWindows(path);
|
||||
}
|
||||
else if (IsLinux || IsMacOS)
|
||||
{
|
||||
return this.ResolvePhysicalPathLibC(path);
|
||||
}
|
||||
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This call can be made on a linux system to get the absolute path of a file. It will resolve nested layers.
|
||||
/// Note: You may pass IntPtr.Zero to the output parameter. You MUST then free the IntPtr that RealPathLinux returns
|
||||
/// using FreeMemoryLinux otherwise things will get very leaky.
|
||||
/// </summary>
|
||||
/// <param name="path"> The path to resolve. </param>
|
||||
/// <param name="output"> The pointer output. </param>
|
||||
/// <returns> A pointer <see cref= "IntPtr"/> to the absolute path of a file. </returns>
|
||||
[DllImport("libc", EntryPoint = "realpath")]
|
||||
public static extern IntPtr RealPathLinux([MarshalAs(UnmanagedType.LPStr)] string path, IntPtr output);
|
||||
|
||||
/// <summary>
|
||||
/// Use this function to free memory and prevent memory leaks.
|
||||
/// However, beware.... Improper usage of this function will cause segfaults and other nasty double-free errors.
|
||||
/// THIS WILL CRASH THE CLR IF YOU USE IT WRONG.
|
||||
/// </summary>
|
||||
/// <param name="toFree">Pointer to the memory space to free. </param>
|
||||
[DllImport("libc", EntryPoint = "free")]
|
||||
public static extern void FreeMemoryLinux([In] IntPtr toFree);
|
||||
|
||||
public static bool MatchesPattern(string searchPattern, ref FileSystemEntry fse)
|
||||
public string ResolvePhysicalPathWindows(string path)
|
||||
{
|
||||
if (!IsWindows)
|
||||
{
|
||||
if (searchPattern.StartsWith("*") && fse.FileName.EndsWith(searchPattern.AsSpan()[1..], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (searchPattern.EndsWith("*") && fse.FileName.StartsWith(searchPattern.AsSpan()[..^1], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (fse.FileName.Equals(searchPattern.AsSpan(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
throw new PlatformNotSupportedException("Attempted to call a function that makes windows-only SDK calls");
|
||||
}
|
||||
|
||||
public string GetParentDirectory(string path)
|
||||
if (this.IsRunningOnWindowsContainer)
|
||||
{
|
||||
return Path.GetDirectoryName(path);
|
||||
}
|
||||
|
||||
public bool IsFileBelowAnother(string aboveFilePath, string belowFilePath)
|
||||
{
|
||||
var aboveDirectoryPath = Path.GetDirectoryName(aboveFilePath);
|
||||
var belowDirectoryPath = Path.GetDirectoryName(belowFilePath);
|
||||
|
||||
// Return true if they are not the same path but the second has the first as its base
|
||||
return (aboveDirectoryPath.Length != belowDirectoryPath.Length) && belowDirectoryPath.StartsWith(aboveDirectoryPath);
|
||||
}
|
||||
|
||||
public bool MatchesPattern(string searchPattern, string fileName)
|
||||
{
|
||||
if (searchPattern.StartsWith("*") && fileName.EndsWith(searchPattern[1..], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (searchPattern.EndsWith("*") && fileName.StartsWith(searchPattern[..^1], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (searchPattern.Equals(fileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string ResolvePhysicalPath(string path)
|
||||
{
|
||||
if (IsWindows)
|
||||
{
|
||||
return this.ResolvePhysicalPathWindows(path);
|
||||
}
|
||||
else if (IsLinux || IsMacOS)
|
||||
{
|
||||
return this.ResolvePhysicalPathLibC(path);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public string ResolvePhysicalPathWindows(string path)
|
||||
if (this.resolvedPaths.TryGetValue(path, out var cachedPath))
|
||||
{
|
||||
if (!IsWindows)
|
||||
{
|
||||
throw new PlatformNotSupportedException("Attempted to call a function that makes windows-only SDK calls");
|
||||
}
|
||||
|
||||
if (this.IsRunningOnWindowsContainer)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
if (this.resolvedPaths.TryGetValue(path, out var cachedPath))
|
||||
{
|
||||
return cachedPath;
|
||||
}
|
||||
|
||||
var symlink = new DirectoryInfo(path);
|
||||
|
||||
using var directoryHandle = CreateFile(symlink.FullName, 0, 2, IntPtr.Zero, CreationDispositionRead, FileFlagBackupSemantics, IntPtr.Zero);
|
||||
|
||||
if (directoryHandle.IsInvalid)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
var resultBuilder = new StringBuilder(InitalPathBufferSize);
|
||||
var mResult = GetFinalPathNameByHandle(directoryHandle.DangerousGetHandle(), resultBuilder, resultBuilder.Capacity, 0);
|
||||
|
||||
// If GetFinalPathNameByHandle needs a bigger buffer, it will tell us the size it needs (including the null terminator) in finalPathNameResultCode
|
||||
if (mResult > InitalPathBufferSize)
|
||||
{
|
||||
resultBuilder = new StringBuilder(mResult);
|
||||
mResult = GetFinalPathNameByHandle(directoryHandle.DangerousGetHandle(), resultBuilder, resultBuilder.Capacity, 0);
|
||||
}
|
||||
|
||||
if (mResult < 0)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
var result = resultBuilder.ToString();
|
||||
|
||||
result = result.StartsWith(LongPathPrefix) ? result[LongPathPrefix.Length..] : result;
|
||||
|
||||
this.resolvedPaths.TryAdd(path, result);
|
||||
|
||||
return result;
|
||||
return cachedPath;
|
||||
}
|
||||
|
||||
public string ResolvePhysicalPathLibC(string path)
|
||||
var symlink = new DirectoryInfo(path);
|
||||
|
||||
using var directoryHandle = CreateFile(symlink.FullName, 0, 2, IntPtr.Zero, CreationDispositionRead, FileFlagBackupSemantics, IntPtr.Zero);
|
||||
|
||||
if (directoryHandle.IsInvalid)
|
||||
{
|
||||
if (!IsLinux && !IsMacOS)
|
||||
{
|
||||
throw new PlatformNotSupportedException("Attempted to call a function that makes linux-only library calls");
|
||||
}
|
||||
|
||||
var pointer = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
pointer = RealPathLinux(path, IntPtr.Zero);
|
||||
|
||||
if (pointer != IntPtr.Zero)
|
||||
{
|
||||
var toReturn = Marshal.PtrToStringAnsi(pointer);
|
||||
return toReturn;
|
||||
}
|
||||
else
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogException(ex, isError: false, printException: true);
|
||||
return path;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pointer != IntPtr.Zero)
|
||||
{
|
||||
FreeMemoryLinux(pointer);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern SafeFileHandle CreateFile(
|
||||
[In] string lpFileName,
|
||||
[In] uint dwDesiredAccess,
|
||||
[In] uint dwShareMode,
|
||||
[In] IntPtr lpSecurityAttributes,
|
||||
[In] uint dwCreationDisposition,
|
||||
[In] uint dwFlagsAndAttributes,
|
||||
[In] IntPtr hTemplateFile);
|
||||
var resultBuilder = new StringBuilder(InitalPathBufferSize);
|
||||
var mResult = GetFinalPathNameByHandle(directoryHandle.DangerousGetHandle(), resultBuilder, resultBuilder.Capacity, 0);
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern int GetFinalPathNameByHandle([In] IntPtr hFile, [Out] StringBuilder lpszFilePath, [In] int cchFilePath, [In] int dwFlags);
|
||||
|
||||
private bool CheckIfRunningOnWindowsContainer()
|
||||
// If GetFinalPathNameByHandle needs a bigger buffer, it will tell us the size it needs (including the null terminator) in finalPathNameResultCode
|
||||
if (mResult > InitalPathBufferSize)
|
||||
{
|
||||
if (IsLinux)
|
||||
resultBuilder = new StringBuilder(mResult);
|
||||
mResult = GetFinalPathNameByHandle(directoryHandle.DangerousGetHandle(), resultBuilder, resultBuilder.Capacity, 0);
|
||||
}
|
||||
|
||||
if (mResult < 0)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
var result = resultBuilder.ToString();
|
||||
|
||||
result = result.StartsWith(LongPathPrefix) ? result[LongPathPrefix.Length..] : result;
|
||||
|
||||
this.resolvedPaths.TryAdd(path, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string ResolvePhysicalPathLibC(string path)
|
||||
{
|
||||
if (!IsLinux && !IsMacOS)
|
||||
{
|
||||
throw new PlatformNotSupportedException("Attempted to call a function that makes linux-only library calls");
|
||||
}
|
||||
|
||||
var pointer = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
pointer = RealPathLinux(path, IntPtr.Zero);
|
||||
|
||||
if (pointer != IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// This isn't the best way to do this in C#, but netstandard doesn't seem to support the service api calls
|
||||
// that we need to do this without shelling out
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = "/c NET START",
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = false,
|
||||
UseShellExecute = false,
|
||||
},
|
||||
};
|
||||
|
||||
var sb = new StringBuilder();
|
||||
process.Start();
|
||||
|
||||
while (!process.HasExited)
|
||||
{
|
||||
sb.Append(process.StandardOutput.ReadToEnd());
|
||||
}
|
||||
|
||||
process.WaitForExit();
|
||||
sb.Append(process.StandardOutput.ReadToEnd());
|
||||
|
||||
if (sb.ToString().Contains("Container Execution Agent"))
|
||||
{
|
||||
this.Logger.LogWarning("Detected execution in a Windows container. Currently windows containers < 1809 do not support symlinks well, so disabling symlink resolution/dedupe behavior");
|
||||
return true;
|
||||
var toReturn = Marshal.PtrToStringAnsi(pointer);
|
||||
return toReturn;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return path;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogException(ex, isError: false, printException: true);
|
||||
return path;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pointer != IntPtr.Zero)
|
||||
{
|
||||
FreeMemoryLinux(pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern SafeFileHandle CreateFile(
|
||||
[In] string lpFileName,
|
||||
[In] uint dwDesiredAccess,
|
||||
[In] uint dwShareMode,
|
||||
[In] IntPtr lpSecurityAttributes,
|
||||
[In] uint dwCreationDisposition,
|
||||
[In] uint dwFlagsAndAttributes,
|
||||
[In] IntPtr hTemplateFile);
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern int GetFinalPathNameByHandle([In] IntPtr hFile, [Out] StringBuilder lpszFilePath, [In] int cchFilePath, [In] int dwFlags);
|
||||
|
||||
private bool CheckIfRunningOnWindowsContainer()
|
||||
{
|
||||
if (IsLinux)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// This isn't the best way to do this in C#, but netstandard doesn't seem to support the service api calls
|
||||
// that we need to do this without shelling out
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = "/c NET START",
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = false,
|
||||
UseShellExecute = false,
|
||||
},
|
||||
};
|
||||
|
||||
var sb = new StringBuilder();
|
||||
process.Start();
|
||||
|
||||
while (!process.HasExited)
|
||||
{
|
||||
sb.Append(process.StandardOutput.ReadToEnd());
|
||||
}
|
||||
|
||||
process.WaitForExit();
|
||||
sb.Append(process.StandardOutput.ReadToEnd());
|
||||
|
||||
if (sb.ToString().Contains("Container Execution Agent"))
|
||||
{
|
||||
this.Logger.LogWarning("Detected execution in a Windows container. Currently windows containers < 1809 do not support symlinks well, so disabling symlink resolution/dedupe behavior");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,53 +4,52 @@ using System.Linq;
|
|||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public static class PatternMatchingUtility
|
||||
{
|
||||
public static class PatternMatchingUtility
|
||||
public delegate bool FilePatternMatcher(ReadOnlySpan<char> span);
|
||||
|
||||
public static FilePatternMatcher GetFilePatternMatcher(IEnumerable<string> patterns)
|
||||
{
|
||||
public delegate bool FilePatternMatcher(ReadOnlySpan<char> span);
|
||||
var ordinalComparison = Expression.Constant(StringComparison.Ordinal, typeof(StringComparison));
|
||||
var asSpan = typeof(MemoryExtensions).GetMethod("AsSpan", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new[] { typeof(string) }, new ParameterModifier[0]);
|
||||
var equals = typeof(MemoryExtensions).GetMethod("Equals", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new[] { typeof(ReadOnlySpan<char>), typeof(ReadOnlySpan<char>), typeof(StringComparison) }, new ParameterModifier[0]);
|
||||
var startsWith = typeof(MemoryExtensions).GetMethod("StartsWith", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new[] { typeof(ReadOnlySpan<char>), typeof(ReadOnlySpan<char>), typeof(StringComparison) }, new ParameterModifier[0]);
|
||||
var endsWith = typeof(MemoryExtensions).GetMethod("EndsWith", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new[] { typeof(ReadOnlySpan<char>), typeof(ReadOnlySpan<char>), typeof(StringComparison) }, new ParameterModifier[0]);
|
||||
|
||||
public static FilePatternMatcher GetFilePatternMatcher(IEnumerable<string> patterns)
|
||||
var predicates = new List<Expression>();
|
||||
var left = Expression.Parameter(typeof(ReadOnlySpan<char>), "fileName");
|
||||
|
||||
foreach (var pattern in patterns)
|
||||
{
|
||||
var ordinalComparison = Expression.Constant(StringComparison.Ordinal, typeof(StringComparison));
|
||||
var asSpan = typeof(MemoryExtensions).GetMethod("AsSpan", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new[] { typeof(string) }, new ParameterModifier[0]);
|
||||
var equals = typeof(MemoryExtensions).GetMethod("Equals", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new[] { typeof(ReadOnlySpan<char>), typeof(ReadOnlySpan<char>), typeof(StringComparison) }, new ParameterModifier[0]);
|
||||
var startsWith = typeof(MemoryExtensions).GetMethod("StartsWith", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new[] { typeof(ReadOnlySpan<char>), typeof(ReadOnlySpan<char>), typeof(StringComparison) }, new ParameterModifier[0]);
|
||||
var endsWith = typeof(MemoryExtensions).GetMethod("EndsWith", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, new[] { typeof(ReadOnlySpan<char>), typeof(ReadOnlySpan<char>), typeof(StringComparison) }, new ParameterModifier[0]);
|
||||
|
||||
var predicates = new List<Expression>();
|
||||
var left = Expression.Parameter(typeof(ReadOnlySpan<char>), "fileName");
|
||||
|
||||
foreach (var pattern in patterns)
|
||||
if (pattern.StartsWith("*"))
|
||||
{
|
||||
if (pattern.StartsWith("*"))
|
||||
{
|
||||
var match = Expression.Constant(pattern[1..], typeof(string));
|
||||
var right = Expression.Call(null, asSpan, match);
|
||||
var combine = Expression.Call(null, endsWith, left, right, ordinalComparison);
|
||||
predicates.Add(combine);
|
||||
}
|
||||
else if (pattern.EndsWith("*"))
|
||||
{
|
||||
var match = Expression.Constant(pattern[..^1], typeof(string));
|
||||
var right = Expression.Call(null, asSpan, match);
|
||||
var combine = Expression.Call(null, startsWith, left, right, ordinalComparison);
|
||||
predicates.Add(combine);
|
||||
}
|
||||
else
|
||||
{
|
||||
var match = Expression.Constant(pattern, typeof(string));
|
||||
var right = Expression.Call(null, asSpan, match);
|
||||
var combine = Expression.Call(null, equals, left, right, ordinalComparison);
|
||||
predicates.Add(combine);
|
||||
}
|
||||
var match = Expression.Constant(pattern[1..], typeof(string));
|
||||
var right = Expression.Call(null, asSpan, match);
|
||||
var combine = Expression.Call(null, endsWith, left, right, ordinalComparison);
|
||||
predicates.Add(combine);
|
||||
}
|
||||
else if (pattern.EndsWith("*"))
|
||||
{
|
||||
var match = Expression.Constant(pattern[..^1], typeof(string));
|
||||
var right = Expression.Call(null, asSpan, match);
|
||||
var combine = Expression.Call(null, startsWith, left, right, ordinalComparison);
|
||||
predicates.Add(combine);
|
||||
}
|
||||
else
|
||||
{
|
||||
var match = Expression.Constant(pattern, typeof(string));
|
||||
var right = Expression.Call(null, asSpan, match);
|
||||
var combine = Expression.Call(null, equals, left, right, ordinalComparison);
|
||||
predicates.Add(combine);
|
||||
}
|
||||
|
||||
var aggregateExpression = predicates.Aggregate(Expression.OrElse);
|
||||
|
||||
var func = Expression.Lambda<FilePatternMatcher>(aggregateExpression, left).Compile();
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
var aggregateExpression = predicates.Aggregate(Expression.OrElse);
|
||||
|
||||
var func = Expression.Lambda<FilePatternMatcher>(aggregateExpression, left).Compile();
|
||||
|
||||
return func;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,55 +1,55 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Enumeration;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public class SafeFileEnumerable : IEnumerable<MatchedFile>
|
||||
{
|
||||
public class SafeFileEnumerable : IEnumerable<MatchedFile>
|
||||
private readonly IEnumerable<string> searchPatterns;
|
||||
private readonly ExcludeDirectoryPredicate directoryExclusionPredicate;
|
||||
private readonly DirectoryInfo directory;
|
||||
private readonly ILogger logger;
|
||||
private readonly IPathUtilityService pathUtilityService;
|
||||
private readonly bool recursivelyScanDirectories;
|
||||
private readonly Func<FileInfo, bool> fileMatchingPredicate;
|
||||
|
||||
private readonly EnumerationOptions enumerationOptions;
|
||||
|
||||
private readonly HashSet<string> enumeratedDirectories;
|
||||
|
||||
public SafeFileEnumerable(DirectoryInfo directory, IEnumerable<string> searchPatterns, ILogger logger, IPathUtilityService pathUtilityService, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true, HashSet<string> previouslyEnumeratedDirectories = null)
|
||||
{
|
||||
private readonly IEnumerable<string> searchPatterns;
|
||||
private readonly ExcludeDirectoryPredicate directoryExclusionPredicate;
|
||||
private readonly DirectoryInfo directory;
|
||||
private readonly ILogger logger;
|
||||
private readonly IPathUtilityService pathUtilityService;
|
||||
private readonly bool recursivelyScanDirectories;
|
||||
private readonly Func<FileInfo, bool> fileMatchingPredicate;
|
||||
this.directory = directory;
|
||||
this.logger = logger;
|
||||
this.searchPatterns = searchPatterns;
|
||||
this.directoryExclusionPredicate = directoryExclusionPredicate;
|
||||
this.recursivelyScanDirectories = recursivelyScanDirectories;
|
||||
this.pathUtilityService = pathUtilityService;
|
||||
this.enumeratedDirectories = previouslyEnumeratedDirectories;
|
||||
|
||||
private readonly EnumerationOptions enumerationOptions;
|
||||
|
||||
private readonly HashSet<string> enumeratedDirectories;
|
||||
|
||||
public SafeFileEnumerable(DirectoryInfo directory, IEnumerable<string> searchPatterns, ILogger logger, IPathUtilityService pathUtilityService, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true, HashSet<string> previouslyEnumeratedDirectories = null)
|
||||
this.enumerationOptions = new EnumerationOptions()
|
||||
{
|
||||
this.directory = directory;
|
||||
this.logger = logger;
|
||||
this.searchPatterns = searchPatterns;
|
||||
this.directoryExclusionPredicate = directoryExclusionPredicate;
|
||||
this.recursivelyScanDirectories = recursivelyScanDirectories;
|
||||
this.pathUtilityService = pathUtilityService;
|
||||
this.enumeratedDirectories = previouslyEnumeratedDirectories;
|
||||
IgnoreInaccessible = true,
|
||||
RecurseSubdirectories = this.recursivelyScanDirectories,
|
||||
ReturnSpecialDirectories = false,
|
||||
MatchType = MatchType.Simple,
|
||||
};
|
||||
}
|
||||
|
||||
this.enumerationOptions = new EnumerationOptions()
|
||||
{
|
||||
IgnoreInaccessible = true,
|
||||
RecurseSubdirectories = this.recursivelyScanDirectories,
|
||||
ReturnSpecialDirectories = false,
|
||||
MatchType = MatchType.Simple,
|
||||
};
|
||||
}
|
||||
public SafeFileEnumerable(DirectoryInfo directory, Func<FileInfo, bool> fileMatchingPredicate, ILogger logger, IPathUtilityService pathUtilityService, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true, HashSet<string> previouslyEnumeratedDirectories = null)
|
||||
: this(directory, new List<string> { "*" }, logger, pathUtilityService, directoryExclusionPredicate, recursivelyScanDirectories, previouslyEnumeratedDirectories) => this.fileMatchingPredicate = fileMatchingPredicate;
|
||||
|
||||
public SafeFileEnumerable(DirectoryInfo directory, Func<FileInfo, bool> fileMatchingPredicate, ILogger logger, IPathUtilityService pathUtilityService, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true, HashSet<string> previouslyEnumeratedDirectories = null)
|
||||
: this(directory, new List<string> { "*" }, logger, pathUtilityService, directoryExclusionPredicate, recursivelyScanDirectories, previouslyEnumeratedDirectories) => this.fileMatchingPredicate = fileMatchingPredicate;
|
||||
public IEnumerator<MatchedFile> GetEnumerator()
|
||||
{
|
||||
var previouslyEnumeratedDirectories = this.enumeratedDirectories ?? new HashSet<string>();
|
||||
|
||||
public IEnumerator<MatchedFile> GetEnumerator()
|
||||
{
|
||||
var previouslyEnumeratedDirectories = this.enumeratedDirectories ?? new HashSet<string>();
|
||||
|
||||
var fse = new FileSystemEnumerable<MatchedFile>(
|
||||
this.directory.FullName,
|
||||
(ref FileSystemEntry entry) =>
|
||||
var fse = new FileSystemEnumerable<MatchedFile>(
|
||||
this.directory.FullName,
|
||||
(ref FileSystemEntry entry) =>
|
||||
{
|
||||
if (!(entry.ToFileSystemInfo() is FileInfo fi))
|
||||
{
|
||||
|
@ -67,83 +67,82 @@ namespace Microsoft.ComponentDetection.Common
|
|||
|
||||
return new MatchedFile() { File = fi, Pattern = foundPattern };
|
||||
},
|
||||
this.enumerationOptions)
|
||||
this.enumerationOptions)
|
||||
{
|
||||
ShouldIncludePredicate = (ref FileSystemEntry entry) =>
|
||||
{
|
||||
ShouldIncludePredicate = (ref FileSystemEntry entry) =>
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var searchPattern in this.searchPatterns)
|
||||
{
|
||||
if (PathUtilityService.MatchesPattern(searchPattern, ref entry))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
ShouldRecursePredicate = (ref FileSystemEntry entry) =>
|
||||
{
|
||||
if (!this.recursivelyScanDirectories)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetPath = entry.ToFullPath();
|
||||
|
||||
var seenPreviously = false;
|
||||
|
||||
if (entry.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
{
|
||||
var realPath = this.pathUtilityService.ResolvePhysicalPath(targetPath);
|
||||
|
||||
seenPreviously = previouslyEnumeratedDirectories.Contains(realPath);
|
||||
previouslyEnumeratedDirectories.Add(realPath);
|
||||
|
||||
if (realPath.StartsWith(targetPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (previouslyEnumeratedDirectories.Contains(targetPath))
|
||||
{
|
||||
seenPreviously = true;
|
||||
}
|
||||
|
||||
previouslyEnumeratedDirectories.Add(targetPath);
|
||||
|
||||
if (seenPreviously)
|
||||
{
|
||||
this.logger.LogVerbose($"Encountered real path {targetPath} before. Short-Circuiting directory traversal");
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is actually a *directory* name (not FileName) and the directory containing that directory.
|
||||
if (entry.IsDirectory && this.directoryExclusionPredicate != null && this.directoryExclusionPredicate(entry.FileName, entry.Directory))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var file in fse)
|
||||
{
|
||||
if (this.fileMatchingPredicate == null || this.fileMatchingPredicate(file.File))
|
||||
{
|
||||
yield return file;
|
||||
}
|
||||
|
||||
foreach (var searchPattern in this.searchPatterns)
|
||||
{
|
||||
if (PathUtilityService.MatchesPattern(searchPattern, ref entry))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
ShouldRecursePredicate = (ref FileSystemEntry entry) =>
|
||||
{
|
||||
if (!this.recursivelyScanDirectories)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetPath = entry.ToFullPath();
|
||||
|
||||
var seenPreviously = false;
|
||||
|
||||
if (entry.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
{
|
||||
var realPath = this.pathUtilityService.ResolvePhysicalPath(targetPath);
|
||||
|
||||
seenPreviously = previouslyEnumeratedDirectories.Contains(realPath);
|
||||
previouslyEnumeratedDirectories.Add(realPath);
|
||||
|
||||
if (realPath.StartsWith(targetPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (previouslyEnumeratedDirectories.Contains(targetPath))
|
||||
{
|
||||
seenPreviously = true;
|
||||
}
|
||||
|
||||
previouslyEnumeratedDirectories.Add(targetPath);
|
||||
|
||||
if (seenPreviously)
|
||||
{
|
||||
this.logger.LogVerbose($"Encountered real path {targetPath} before. Short-Circuiting directory traversal");
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is actually a *directory* name (not FileName) and the directory containing that directory.
|
||||
if (entry.IsDirectory && this.directoryExclusionPredicate != null && this.directoryExclusionPredicate(entry.FileName, entry.Directory))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var file in fse)
|
||||
{
|
||||
if (this.fileMatchingPredicate == null || this.fileMatchingPredicate(file.File))
|
||||
{
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,21 +3,20 @@ using System.Composition;
|
|||
using System.IO;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
[Export(typeof(ISafeFileEnumerableFactory))]
|
||||
[Shared]
|
||||
public class SafeFileEnumerableFactory : ISafeFileEnumerableFactory
|
||||
{
|
||||
[Export(typeof(ISafeFileEnumerableFactory))]
|
||||
[Shared]
|
||||
public class SafeFileEnumerableFactory : ISafeFileEnumerableFactory
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
[Import]
|
||||
public IPathUtilityService PathUtilityService { get; set; }
|
||||
|
||||
public IEnumerable<MatchedFile> CreateSafeFileEnumerable(DirectoryInfo directory, IEnumerable<string> searchPatterns, ExcludeDirectoryPredicate directoryExclusionPredicate)
|
||||
{
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
[Import]
|
||||
public IPathUtilityService PathUtilityService { get; set; }
|
||||
|
||||
public IEnumerable<MatchedFile> CreateSafeFileEnumerable(DirectoryInfo directory, IEnumerable<string> searchPatterns, ExcludeDirectoryPredicate directoryExclusionPredicate)
|
||||
{
|
||||
return new SafeFileEnumerable(directory, searchPatterns, this.Logger, this.PathUtilityService, directoryExclusionPredicate);
|
||||
}
|
||||
return new SafeFileEnumerable(directory, searchPatterns, this.Logger, this.PathUtilityService, directoryExclusionPredicate);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public enum ScanType
|
||||
{
|
||||
public enum ScanType
|
||||
{
|
||||
Invalid = 0,
|
||||
Register = 1,
|
||||
LogOnly = 2,
|
||||
}
|
||||
Invalid = 0,
|
||||
Register = 1,
|
||||
LogOnly = 2,
|
||||
}
|
||||
|
|
|
@ -1,102 +1,101 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public class TabularStringFormat
|
||||
{
|
||||
public class TabularStringFormat
|
||||
public const char DefaultVerticalLineChar = '|';
|
||||
public const char DefaultHorizontalLineChar = '_';
|
||||
|
||||
private readonly IList<Column> columns;
|
||||
private readonly char horizontalLineChar;
|
||||
private readonly char verticalLineChar;
|
||||
private readonly string tableTitle;
|
||||
|
||||
public TabularStringFormat(IList<Column> columns, char horizontalLineChar = DefaultHorizontalLineChar, char verticalLineChar = DefaultVerticalLineChar, string tableTitle = null)
|
||||
{
|
||||
public const char DefaultVerticalLineChar = '|';
|
||||
public const char DefaultHorizontalLineChar = '_';
|
||||
this.columns = columns;
|
||||
this.horizontalLineChar = horizontalLineChar;
|
||||
this.verticalLineChar = verticalLineChar;
|
||||
this.tableTitle = tableTitle;
|
||||
}
|
||||
|
||||
private readonly IList<Column> columns;
|
||||
private readonly char horizontalLineChar;
|
||||
private readonly char verticalLineChar;
|
||||
private readonly string tableTitle;
|
||||
|
||||
public TabularStringFormat(IList<Column> columns, char horizontalLineChar = DefaultHorizontalLineChar, char verticalLineChar = DefaultVerticalLineChar, string tableTitle = null)
|
||||
public string GenerateString(IEnumerable<IList<object>> rows)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (!string.IsNullOrWhiteSpace(this.tableTitle))
|
||||
{
|
||||
this.columns = columns;
|
||||
this.horizontalLineChar = horizontalLineChar;
|
||||
this.verticalLineChar = verticalLineChar;
|
||||
this.tableTitle = tableTitle;
|
||||
this.PrintTitleSection(sb);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.WriteFlatLine(sb, false);
|
||||
}
|
||||
|
||||
public string GenerateString(IEnumerable<IList<object>> rows)
|
||||
sb.Append(this.verticalLineChar);
|
||||
foreach (var column in this.columns)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (!string.IsNullOrWhiteSpace(this.tableTitle))
|
||||
sb.Append(column.Header.PadRight(column.Width));
|
||||
sb.Append(this.verticalLineChar);
|
||||
}
|
||||
|
||||
this.WriteFlatLine(sb);
|
||||
foreach (var row in rows)
|
||||
{
|
||||
sb.Append(this.verticalLineChar);
|
||||
if (row.Count != this.columns.Count)
|
||||
{
|
||||
this.PrintTitleSection(sb);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.WriteFlatLine(sb, false);
|
||||
throw new InvalidOperationException("All rows must have length equal to the number of columns present.");
|
||||
}
|
||||
|
||||
sb.Append(this.verticalLineChar);
|
||||
foreach (var column in this.columns)
|
||||
for (var i = 0; i < this.columns.Count; i++)
|
||||
{
|
||||
sb.Append(column.Header.PadRight(column.Width));
|
||||
var dataString = this.columns[i].Format != null ? string.Format(this.columns[i].Format, row[i]) : row[i].ToString();
|
||||
sb.Append(dataString.PadRight(this.columns[i].Width));
|
||||
sb.Append(this.verticalLineChar);
|
||||
}
|
||||
|
||||
this.WriteFlatLine(sb);
|
||||
foreach (var row in rows)
|
||||
{
|
||||
sb.Append(this.verticalLineChar);
|
||||
if (row.Count != this.columns.Count)
|
||||
{
|
||||
throw new InvalidOperationException("All rows must have length equal to the number of columns present.");
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.columns.Count; i++)
|
||||
{
|
||||
var dataString = this.columns[i].Format != null ? string.Format(this.columns[i].Format, row[i]) : row[i].ToString();
|
||||
sb.Append(dataString.PadRight(this.columns[i].Width));
|
||||
sb.Append(this.verticalLineChar);
|
||||
}
|
||||
|
||||
this.WriteFlatLine(sb);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void PrintTitleSection(StringBuilder sb)
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void PrintTitleSection(StringBuilder sb)
|
||||
{
|
||||
this.WriteFlatLine(sb, false);
|
||||
var tableWidth = this.columns.Sum(column => column.Width);
|
||||
sb.Append(this.verticalLineChar);
|
||||
sb.Append(this.tableTitle.PadRight(tableWidth + this.columns.Count - 1));
|
||||
sb.Append(this.verticalLineChar);
|
||||
|
||||
sb.AppendLine();
|
||||
sb.Append(this.verticalLineChar);
|
||||
for (var i = 0; i < this.columns.Count - 1; i++)
|
||||
{
|
||||
this.WriteFlatLine(sb, false);
|
||||
var tableWidth = this.columns.Sum(column => column.Width);
|
||||
sb.Append(this.verticalLineChar);
|
||||
sb.Append(this.tableTitle.PadRight(tableWidth + this.columns.Count - 1));
|
||||
sb.Append(this.verticalLineChar);
|
||||
|
||||
sb.AppendLine();
|
||||
sb.Append(this.verticalLineChar);
|
||||
for (var i = 0; i < this.columns.Count - 1; i++)
|
||||
{
|
||||
sb.Append(string.Empty.PadRight(this.columns[i].Width, this.horizontalLineChar));
|
||||
sb.Append(this.horizontalLineChar);
|
||||
}
|
||||
|
||||
sb.Append(string.Empty.PadRight(this.columns[this.columns.Count - 1].Width, this.horizontalLineChar));
|
||||
sb.Append(this.verticalLineChar);
|
||||
sb.AppendLine();
|
||||
sb.Append(string.Empty.PadRight(this.columns[i].Width, this.horizontalLineChar));
|
||||
sb.Append(this.horizontalLineChar);
|
||||
}
|
||||
|
||||
private void WriteFlatLine(StringBuilder sb, bool withPipes = true)
|
||||
sb.Append(string.Empty.PadRight(this.columns[this.columns.Count - 1].Width, this.horizontalLineChar));
|
||||
sb.Append(this.verticalLineChar);
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
private void WriteFlatLine(StringBuilder sb, bool withPipes = true)
|
||||
{
|
||||
var splitCharacter = withPipes ? this.verticalLineChar : this.horizontalLineChar;
|
||||
sb.AppendLine();
|
||||
sb.Append(splitCharacter);
|
||||
for (var i = 0; i < this.columns.Count; i++)
|
||||
{
|
||||
var splitCharacter = withPipes ? this.verticalLineChar : this.horizontalLineChar;
|
||||
sb.AppendLine();
|
||||
sb.Append(string.Empty.PadRight(this.columns[i].Width, this.horizontalLineChar));
|
||||
sb.Append(splitCharacter);
|
||||
for (var i = 0; i < this.columns.Count; i++)
|
||||
{
|
||||
sb.Append(string.Empty.PadRight(this.columns[i].Width, this.horizontalLineChar));
|
||||
sb.Append(splitCharacter);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Attributes
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Denotes that a telemetry property should be treated as a Metric (numeric) value
|
||||
///
|
||||
/// It is up to the implementing Telemetry Service to interpret this value.
|
||||
/// </summary>
|
||||
public class MetricAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Denotes that a telemetry property should be treated as a Metric (numeric) value
|
||||
///
|
||||
/// It is up to the implementing Telemetry Service to interpret this value.
|
||||
/// </summary>
|
||||
public class MetricAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
using System;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Attributes
|
||||
{
|
||||
public class TelemetryServiceAttribute : Attribute
|
||||
{
|
||||
public TelemetryServiceAttribute(string serviceType) => this.ServiceType = serviceType;
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Attributes;
|
||||
|
||||
public string ServiceType { get; }
|
||||
}
|
||||
public class TelemetryServiceAttribute : Attribute
|
||||
{
|
||||
public TelemetryServiceAttribute(string serviceType) => this.ServiceType = serviceType;
|
||||
|
||||
public string ServiceType { get; }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Composition;
|
||||
using Microsoft.ComponentDetection.Common.Telemetry.Attributes;
|
||||
|
@ -7,49 +7,48 @@ using Microsoft.ComponentDetection.Contracts;
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry;
|
||||
|
||||
[Export(typeof(ITelemetryService))]
|
||||
[TelemetryService(nameof(CommandLineTelemetryService))]
|
||||
internal class CommandLineTelemetryService : ITelemetryService
|
||||
{
|
||||
[Export(typeof(ITelemetryService))]
|
||||
[TelemetryService(nameof(CommandLineTelemetryService))]
|
||||
internal class CommandLineTelemetryService : ITelemetryService
|
||||
private static readonly ConcurrentQueue<JObject> Records = new ConcurrentQueue<JObject>();
|
||||
|
||||
public const string TelemetryRelativePath = "ScanTelemetry_{timestamp}.json";
|
||||
|
||||
private TelemetryMode telemetryMode = TelemetryMode.Production;
|
||||
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
[Import]
|
||||
public IFileWritingService FileWritingService { get; set; }
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
private static readonly ConcurrentQueue<JObject> Records = new ConcurrentQueue<JObject>();
|
||||
this.FileWritingService.WriteFile(TelemetryRelativePath, JsonConvert.SerializeObject(Records));
|
||||
}
|
||||
|
||||
public const string TelemetryRelativePath = "ScanTelemetry_{timestamp}.json";
|
||||
|
||||
private TelemetryMode telemetryMode = TelemetryMode.Production;
|
||||
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
[Import]
|
||||
public IFileWritingService FileWritingService { get; set; }
|
||||
|
||||
public void Flush()
|
||||
public void PostRecord(IDetectionTelemetryRecord record)
|
||||
{
|
||||
if (this.telemetryMode != TelemetryMode.Disabled)
|
||||
{
|
||||
this.FileWritingService.WriteFile(TelemetryRelativePath, JsonConvert.SerializeObject(Records));
|
||||
}
|
||||
var jsonRecord = JObject.FromObject(record);
|
||||
jsonRecord.Add("Timestamp", DateTime.UtcNow);
|
||||
jsonRecord.Add("CorrelationId", TelemetryConstants.CorrelationId);
|
||||
|
||||
public void PostRecord(IDetectionTelemetryRecord record)
|
||||
{
|
||||
if (this.telemetryMode != TelemetryMode.Disabled)
|
||||
Records.Enqueue(jsonRecord);
|
||||
|
||||
if (this.telemetryMode == TelemetryMode.Debug)
|
||||
{
|
||||
var jsonRecord = JObject.FromObject(record);
|
||||
jsonRecord.Add("Timestamp", DateTime.UtcNow);
|
||||
jsonRecord.Add("CorrelationId", TelemetryConstants.CorrelationId);
|
||||
|
||||
Records.Enqueue(jsonRecord);
|
||||
|
||||
if (this.telemetryMode == TelemetryMode.Debug)
|
||||
{
|
||||
this.Logger.LogInfo(jsonRecord.ToString());
|
||||
}
|
||||
this.Logger.LogInfo(jsonRecord.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMode(TelemetryMode mode)
|
||||
{
|
||||
this.telemetryMode = mode;
|
||||
}
|
||||
public void SetMode(TelemetryMode mode)
|
||||
{
|
||||
this.telemetryMode = mode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
using Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry;
|
||||
|
||||
public interface ITelemetryService
|
||||
{
|
||||
public interface ITelemetryService
|
||||
{
|
||||
/// <summary>
|
||||
/// Post a record to the telemetry service.
|
||||
/// </summary>
|
||||
/// <param name="record">The telemetry record to post.</param>
|
||||
void PostRecord(IDetectionTelemetryRecord record);
|
||||
/// <summary>
|
||||
/// Post a record to the telemetry service.
|
||||
/// </summary>
|
||||
/// <param name="record">The telemetry record to post.</param>
|
||||
void PostRecord(IDetectionTelemetryRecord record);
|
||||
|
||||
/// <summary>
|
||||
/// Flush all telemetry events from the queue (usually called on shutdown to clear the queue).
|
||||
/// </summary>
|
||||
void Flush();
|
||||
/// <summary>
|
||||
/// Flush all telemetry events from the queue (usually called on shutdown to clear the queue).
|
||||
/// </summary>
|
||||
void Flush();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the telemetry mode for the service.
|
||||
/// </summary>
|
||||
void SetMode(TelemetryMode mode);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the telemetry mode for the service.
|
||||
/// </summary>
|
||||
void SetMode(TelemetryMode mode);
|
||||
}
|
||||
|
|
|
@ -1,48 +1,47 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.ComponentDetection.Common.Telemetry.Attributes;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public abstract class BaseDetectionTelemetryRecord : IDetectionTelemetryRecord
|
||||
{
|
||||
public abstract class BaseDetectionTelemetryRecord : IDetectionTelemetryRecord
|
||||
private readonly Stopwatch stopwatch = new Stopwatch();
|
||||
|
||||
private bool disposedValue = false;
|
||||
|
||||
protected BaseDetectionTelemetryRecord() => this.stopwatch.Start();
|
||||
|
||||
public abstract string RecordName { get; }
|
||||
|
||||
[Metric]
|
||||
public TimeSpan? ExecutionTime { get; protected set; }
|
||||
|
||||
public void StopExecutionTimer()
|
||||
{
|
||||
private readonly Stopwatch stopwatch = new Stopwatch();
|
||||
|
||||
private bool disposedValue = false;
|
||||
|
||||
protected BaseDetectionTelemetryRecord() => this.stopwatch.Start();
|
||||
|
||||
public abstract string RecordName { get; }
|
||||
|
||||
[Metric]
|
||||
public TimeSpan? ExecutionTime { get; protected set; }
|
||||
|
||||
public void StopExecutionTimer()
|
||||
if (this.stopwatch.IsRunning)
|
||||
{
|
||||
if (this.stopwatch.IsRunning)
|
||||
{
|
||||
this.stopwatch.Stop();
|
||||
this.ExecutionTime = this.stopwatch.Elapsed;
|
||||
}
|
||||
this.stopwatch.Stop();
|
||||
this.ExecutionTime = this.stopwatch.Elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!this.disposedValue)
|
||||
{
|
||||
if (!this.disposedValue)
|
||||
if (disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this.StopExecutionTimer();
|
||||
TelemetryRelay.Instance.PostTelemetryRecord(this);
|
||||
}
|
||||
|
||||
this.disposedValue = true;
|
||||
this.StopExecutionTimer();
|
||||
TelemetryRelay.Instance.PostTelemetryRecord(this);
|
||||
}
|
||||
|
||||
this.disposedValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,53 +1,52 @@
|
|||
using System;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class BcdeExecutionTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
public override string RecordName => "BcdeExecution";
|
||||
|
||||
public class BcdeExecutionTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
public string Command { get; set; }
|
||||
|
||||
public int? ExitCode { get; set; }
|
||||
|
||||
public int? HiddenExitCode { get; set; }
|
||||
|
||||
public string UnhandledException { get; set; }
|
||||
|
||||
public string Arguments { get; set; }
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public string AgentOSMeaningfulDetails { get; set; }
|
||||
|
||||
public string AgentOSDescription { get; set; }
|
||||
|
||||
public static async Task<TReturn> TrackAsync<TReturn>(
|
||||
Func<BcdeExecutionTelemetryRecord, CancellationToken, Task<TReturn>> functionToTrack,
|
||||
bool terminalRecord = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
public override string RecordName => "BcdeExecution";
|
||||
using var record = new BcdeExecutionTelemetryRecord();
|
||||
|
||||
public string Command { get; set; }
|
||||
|
||||
public int? ExitCode { get; set; }
|
||||
|
||||
public int? HiddenExitCode { get; set; }
|
||||
|
||||
public string UnhandledException { get; set; }
|
||||
|
||||
public string Arguments { get; set; }
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public string AgentOSMeaningfulDetails { get; set; }
|
||||
|
||||
public string AgentOSDescription { get; set; }
|
||||
|
||||
public static async Task<TReturn> TrackAsync<TReturn>(
|
||||
Func<BcdeExecutionTelemetryRecord, CancellationToken, Task<TReturn>> functionToTrack,
|
||||
bool terminalRecord = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
try
|
||||
{
|
||||
using var record = new BcdeExecutionTelemetryRecord();
|
||||
|
||||
try
|
||||
return await functionToTrack(record, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
record.UnhandledException = ex.ToString();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
record.Dispose();
|
||||
if (terminalRecord && !(record.Command?.Equals("help", StringComparison.InvariantCultureIgnoreCase) ?? false))
|
||||
{
|
||||
return await functionToTrack(record, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
record.UnhandledException = ex.ToString();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
record.Dispose();
|
||||
if (terminalRecord && !(record.Command?.Equals("help", StringComparison.InvariantCultureIgnoreCase) ?? false))
|
||||
{
|
||||
await TelemetryRelay.Instance.ShutdownAsync();
|
||||
}
|
||||
await TelemetryRelay.Instance.ShutdownAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,41 +1,40 @@
|
|||
using System;
|
||||
using Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class CommandLineInvocationTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class CommandLineInvocationTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
public override string RecordName => "CommandLineInvocation";
|
||||
|
||||
public string PathThatWasRan { get; set; }
|
||||
|
||||
public string Parameters { get; set; }
|
||||
|
||||
public int? ExitCode { get; set; }
|
||||
|
||||
public string StandardError { get; set; }
|
||||
|
||||
public string UnhandledException { get; set; }
|
||||
|
||||
internal void Track(CommandLineExecutionResult result, string path, string parameters)
|
||||
{
|
||||
public override string RecordName => "CommandLineInvocation";
|
||||
this.ExitCode = result.ExitCode;
|
||||
this.StandardError = result.StdErr;
|
||||
this.TrackCommon(path, parameters);
|
||||
}
|
||||
|
||||
public string PathThatWasRan { get; set; }
|
||||
internal void Track(Exception ex, string path, string parameters)
|
||||
{
|
||||
this.ExitCode = -1;
|
||||
this.UnhandledException = ex.ToString();
|
||||
this.TrackCommon(path, parameters);
|
||||
}
|
||||
|
||||
public string Parameters { get; set; }
|
||||
|
||||
public int? ExitCode { get; set; }
|
||||
|
||||
public string StandardError { get; set; }
|
||||
|
||||
public string UnhandledException { get; set; }
|
||||
|
||||
internal void Track(CommandLineExecutionResult result, string path, string parameters)
|
||||
{
|
||||
this.ExitCode = result.ExitCode;
|
||||
this.StandardError = result.StdErr;
|
||||
this.TrackCommon(path, parameters);
|
||||
}
|
||||
|
||||
internal void Track(Exception ex, string path, string parameters)
|
||||
{
|
||||
this.ExitCode = -1;
|
||||
this.UnhandledException = ex.ToString();
|
||||
this.TrackCommon(path, parameters);
|
||||
}
|
||||
|
||||
private void TrackCommon(string path, string parameters)
|
||||
{
|
||||
this.PathThatWasRan = path;
|
||||
this.Parameters = parameters;
|
||||
this.StopExecutionTimer();
|
||||
}
|
||||
private void TrackCommon(string path, string parameters)
|
||||
{
|
||||
this.PathThatWasRan = path;
|
||||
this.Parameters = parameters;
|
||||
this.StopExecutionTimer();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class DetectedComponentScopeRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class DetectedComponentScopeRecord : BaseDetectionTelemetryRecord
|
||||
public override string RecordName => "ComponentScopeRecord";
|
||||
|
||||
public int? MavenProvidedScopeCount { get; set; } = 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public void IncrementProvidedScopeCount()
|
||||
{
|
||||
public override string RecordName => "ComponentScopeRecord";
|
||||
|
||||
public int? MavenProvidedScopeCount { get; set; } = 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public void IncrementProvidedScopeCount()
|
||||
{
|
||||
this.MavenProvidedScopeCount++;
|
||||
}
|
||||
this.MavenProvidedScopeCount++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class DetectorExecutionTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class DetectorExecutionTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "DetectorExecution";
|
||||
public override string RecordName => "DetectorExecution";
|
||||
|
||||
public string DetectorId { get; set; }
|
||||
public string DetectorId { get; set; }
|
||||
|
||||
public int? DetectedComponentCount { get; set; }
|
||||
public int? DetectedComponentCount { get; set; }
|
||||
|
||||
public int? ExplicitlyReferencedComponentCount { get; set; }
|
||||
public int? ExplicitlyReferencedComponentCount { get; set; }
|
||||
|
||||
public int? ReturnCode { get; set; }
|
||||
public int? ReturnCode { get; set; }
|
||||
|
||||
public bool IsExperimental { get; set; }
|
||||
public bool IsExperimental { get; set; }
|
||||
|
||||
public string ExperimentalInformation { get; set; }
|
||||
public string ExperimentalInformation { get; set; }
|
||||
|
||||
public string AdditionalTelemetryDetails { get; set; }
|
||||
}
|
||||
public string AdditionalTelemetryDetails { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class DockerServiceImageExistsLocallyTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class DockerServiceImageExistsLocallyTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "DockerServiceImageExistsLocally";
|
||||
public override string RecordName => "DockerServiceImageExistsLocally";
|
||||
|
||||
public string Image { get; set; }
|
||||
public string Image { get; set; }
|
||||
|
||||
public string ImageInspectResponse { get; set; }
|
||||
public string ImageInspectResponse { get; set; }
|
||||
|
||||
public string ExceptionMessage { get; set; }
|
||||
}
|
||||
public string ExceptionMessage { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class DockerServiceInspectImageTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class DockerServiceInspectImageTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "DockerServiceInspectImage";
|
||||
public override string RecordName => "DockerServiceInspectImage";
|
||||
|
||||
public string Image { get; set; }
|
||||
public string Image { get; set; }
|
||||
|
||||
public string BaseImageDigest { get; set; }
|
||||
public string BaseImageDigest { get; set; }
|
||||
|
||||
public string BaseImageRef { get; set; }
|
||||
public string BaseImageRef { get; set; }
|
||||
|
||||
public string ImageInspectResponse { get; set; }
|
||||
public string ImageInspectResponse { get; set; }
|
||||
|
||||
public string ExceptionMessage { get; set; }
|
||||
}
|
||||
public string ExceptionMessage { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class DockerServiceSystemInfoTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class DockerServiceSystemInfoTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "DockerServiceSystemInfo";
|
||||
public override string RecordName => "DockerServiceSystemInfo";
|
||||
|
||||
public string SystemInfo { get; set; }
|
||||
public string SystemInfo { get; set; }
|
||||
|
||||
public string ExceptionMessage { get; set; }
|
||||
}
|
||||
public string ExceptionMessage { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class DockerServiceTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class DockerServiceTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "DockerService";
|
||||
public override string RecordName => "DockerService";
|
||||
|
||||
public string Image { get; set; }
|
||||
public string Image { get; set; }
|
||||
|
||||
public string Command { get; set; }
|
||||
public string Command { get; set; }
|
||||
|
||||
public string Container { get; set; }
|
||||
public string Container { get; set; }
|
||||
|
||||
public string Stdout { get; set; }
|
||||
public string Stdout { get; set; }
|
||||
|
||||
public string Stderr { get; set; }
|
||||
}
|
||||
public string Stderr { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class DockerServiceTryPullImageTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class DockerServiceTryPullImageTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "DockerServiceTryPullImage";
|
||||
public override string RecordName => "DockerServiceTryPullImage";
|
||||
|
||||
public string Image { get; set; }
|
||||
public string Image { get; set; }
|
||||
|
||||
public string CreateImageProgress { get; set; }
|
||||
public string CreateImageProgress { get; set; }
|
||||
|
||||
public string ExceptionMessage { get; set; }
|
||||
}
|
||||
public string ExceptionMessage { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class FailedReadingFileRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class FailedReadingFileRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "FailedReadingFile";
|
||||
public override string RecordName => "FailedReadingFile";
|
||||
|
||||
public string FilePath { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
|
||||
public string ExceptionMessage { get; set; }
|
||||
public string ExceptionMessage { get; set; }
|
||||
|
||||
public string StackTrace { get; set; }
|
||||
}
|
||||
public string StackTrace { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class GoGraphTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class GoGraphTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "GoGraph";
|
||||
public override string RecordName => "GoGraph";
|
||||
|
||||
public string ProjectRoot { get; set; }
|
||||
public string ProjectRoot { get; set; }
|
||||
|
||||
public bool IsGoAvailable { get; set; }
|
||||
public bool IsGoAvailable { get; set; }
|
||||
|
||||
public bool WasGraphSuccessful { get; set; }
|
||||
}
|
||||
public bool WasGraphSuccessful { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public interface IDetectionTelemetryRecord : IDisposable
|
||||
{
|
||||
public interface IDetectionTelemetryRecord : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the record to be logged.
|
||||
/// </summary>
|
||||
string RecordName { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the name of the record to be logged.
|
||||
/// </summary>
|
||||
string RecordName { get; }
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class LinuxContainerDetectorImageDetectionFailed : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class LinuxContainerDetectorImageDetectionFailed : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "LinuxContainerDetectorImageDetectionFailed";
|
||||
public override string RecordName => "LinuxContainerDetectorImageDetectionFailed";
|
||||
|
||||
public string ImageId { get; set; }
|
||||
public string ImageId { get; set; }
|
||||
|
||||
public string Message { get; set; }
|
||||
public string Message { get; set; }
|
||||
|
||||
public string ExceptionType { get; set; }
|
||||
public string ExceptionType { get; set; }
|
||||
|
||||
public string StackTrace { get; set; }
|
||||
}
|
||||
public string StackTrace { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class LinuxContainerDetectorLayerAwareness : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class LinuxContainerDetectorLayerAwareness : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "LinuxContainerDetectorLayerAwareness";
|
||||
public override string RecordName => "LinuxContainerDetectorLayerAwareness";
|
||||
|
||||
public string BaseImageRef { get; set; }
|
||||
public string BaseImageRef { get; set; }
|
||||
|
||||
public string BaseImageDigest { get; set; }
|
||||
public string BaseImageDigest { get; set; }
|
||||
|
||||
public int? BaseImageLayerCount { get; set; }
|
||||
public int? BaseImageLayerCount { get; set; }
|
||||
|
||||
public int? LayerCount { get; set; }
|
||||
public int? LayerCount { get; set; }
|
||||
|
||||
public string BaseImageLayerMessage { get; set; }
|
||||
}
|
||||
public string BaseImageLayerMessage { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class LinuxContainerDetectorMissingRepoNameAndTagRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class LinuxContainerDetectorMissingRepoNameAndTagRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "MissingRepoNameAndTag";
|
||||
}
|
||||
public override string RecordName => "MissingRepoNameAndTag";
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class LinuxContainerDetectorMissingVersion : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class LinuxContainerDetectorMissingVersion : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName { get; } = "MissingVersion";
|
||||
public override string RecordName { get; } = "MissingVersion";
|
||||
|
||||
public string Distribution { get; set; }
|
||||
public string Distribution { get; set; }
|
||||
|
||||
public string Release { get; set; }
|
||||
public string Release { get; set; }
|
||||
|
||||
public string[] PackageNames { get; set; }
|
||||
}
|
||||
public string[] PackageNames { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class LinuxContainerDetectorTimeout : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class LinuxContainerDetectorTimeout : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "LinuxContainerDetectorTimeout";
|
||||
}
|
||||
public override string RecordName => "LinuxContainerDetectorTimeout";
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
{
|
||||
public class LinuxContainerDetectorUnsupportedOs : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "LinuxContainerDetectorUnsupportedOs";
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public string Os { get; set; }
|
||||
}
|
||||
public class LinuxContainerDetectorUnsupportedOs : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "LinuxContainerDetectorUnsupportedOs";
|
||||
|
||||
public string Os { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class LinuxScannerSyftTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class LinuxScannerSyftTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "LinuxScannerSyftTelemetry";
|
||||
public override string RecordName => "LinuxScannerSyftTelemetry";
|
||||
|
||||
public string LinuxComponents { get; set; }
|
||||
public string LinuxComponents { get; set; }
|
||||
|
||||
public string Exception { get; set; }
|
||||
}
|
||||
public string Exception { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class LinuxScannerTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class LinuxScannerTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "LinuxScannerTelemetry";
|
||||
public override string RecordName => "LinuxScannerTelemetry";
|
||||
|
||||
public string ImageToScan { get; set; }
|
||||
public string ImageToScan { get; set; }
|
||||
|
||||
public string ScanStdOut { get; set; }
|
||||
public string ScanStdOut { get; set; }
|
||||
|
||||
public string ScanStdErr { get; set; }
|
||||
public string ScanStdErr { get; set; }
|
||||
|
||||
public string FailedDeserializingScannerOutput { get; set; }
|
||||
public string FailedDeserializingScannerOutput { get; set; }
|
||||
|
||||
public bool SemaphoreFailure { get; set; }
|
||||
public bool SemaphoreFailure { get; set; }
|
||||
|
||||
public string ScannerVersion { get; set; }
|
||||
}
|
||||
public string ScannerVersion { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
{
|
||||
public class LoadComponentDetectorsTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "LoadComponentDetectors";
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public string DetectorIds { get; set; }
|
||||
}
|
||||
public class LoadComponentDetectorsTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "LoadComponentDetectors";
|
||||
|
||||
public string DetectorIds { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,35 +1,34 @@
|
|||
using System;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class NuGetProjectAssetsTelemetryRecord : IDetectionTelemetryRecord, IDisposable
|
||||
{
|
||||
public class NuGetProjectAssetsTelemetryRecord : IDetectionTelemetryRecord, IDisposable
|
||||
private bool disposedValue = false;
|
||||
|
||||
public string RecordName => "NuGetProjectAssets";
|
||||
|
||||
public string FoundTargets { get; set; }
|
||||
|
||||
public string UnhandledException { get; set; }
|
||||
|
||||
public string Frameworks { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
private bool disposedValue = false;
|
||||
this.Dispose(true);
|
||||
}
|
||||
|
||||
public string RecordName => "NuGetProjectAssets";
|
||||
|
||||
public string FoundTargets { get; set; }
|
||||
|
||||
public string UnhandledException { get; set; }
|
||||
|
||||
public string Frameworks { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!this.disposedValue)
|
||||
{
|
||||
this.Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!this.disposedValue)
|
||||
if (disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
TelemetryRelay.Instance.PostTelemetryRecord(this);
|
||||
}
|
||||
|
||||
this.disposedValue = true;
|
||||
TelemetryRelay.Instance.PostTelemetryRecord(this);
|
||||
}
|
||||
|
||||
this.disposedValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class PypiCacheTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class PypiCacheTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "PyPiCache";
|
||||
public override string RecordName => "PyPiCache";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets total number of PyPi requests that hit the cache instead of PyPi APIs.
|
||||
/// </summary>
|
||||
public int NumCacheHits { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets total number of PyPi requests that hit the cache instead of PyPi APIs.
|
||||
/// </summary>
|
||||
public int NumCacheHits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the PyPi cache at class destruction.
|
||||
/// </summary>
|
||||
public int FinalCacheSize { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the PyPi cache at class destruction.
|
||||
/// </summary>
|
||||
public int FinalCacheSize { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
using System.Net;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class PypiFailureTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class PypiFailureTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "PypiFailure";
|
||||
public override string RecordName => "PypiFailure";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the package Name (ex: pyyaml).
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the package Name (ex: pyyaml).
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the set of dependency specifications that constrain the overall dependency request (ex: ==1.0, >=2.0).
|
||||
/// </summary>
|
||||
public string[] DependencySpecifiers { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the set of dependency specifications that constrain the overall dependency request (ex: ==1.0, >=2.0).
|
||||
/// </summary>
|
||||
public string[] DependencySpecifiers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status code of the last failed call.
|
||||
/// </summary>
|
||||
public HttpStatusCode StatusCode { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the status code of the last failed call.
|
||||
/// </summary>
|
||||
public HttpStatusCode StatusCode { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class PypiMaxRetriesReachedTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class PypiMaxRetriesReachedTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "PypiMaxRetriesReached";
|
||||
public override string RecordName => "PypiMaxRetriesReached";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the package Name (ex: pyyaml).
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the package Name (ex: pyyaml).
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the set of dependency specifications that constrain the overall dependency request (ex: ==1.0, >=2.0).
|
||||
/// </summary>
|
||||
public string[] DependencySpecifiers { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the set of dependency specifications that constrain the overall dependency request (ex: ==1.0, >=2.0).
|
||||
/// </summary>
|
||||
public string[] DependencySpecifiers { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
using System.Net;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class PypiRetryTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class PypiRetryTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "PypiRetry";
|
||||
public override string RecordName => "PypiRetry";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the package Name (ex: pyyaml).
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the package Name (ex: pyyaml).
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the set of dependency specifications that constrain the overall dependency request (ex: ==1.0, >=2.0).
|
||||
/// </summary>
|
||||
public string[] DependencySpecifiers { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the set of dependency specifications that constrain the overall dependency request (ex: ==1.0, >=2.0).
|
||||
/// </summary>
|
||||
public string[] DependencySpecifiers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status code of the last failed call that caused the retry.
|
||||
/// </summary>
|
||||
public HttpStatusCode StatusCode { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the status code of the last failed call that caused the retry.
|
||||
/// </summary>
|
||||
public HttpStatusCode StatusCode { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
public class RustCrateDetectorTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public class RustCrateDetectorTelemetryRecord : BaseDetectionTelemetryRecord
|
||||
{
|
||||
public override string RecordName => "RustCrateMalformedDependencies";
|
||||
public override string RecordName => "RustCrateMalformedDependencies";
|
||||
|
||||
public string PackageInfo { get; set; }
|
||||
public string PackageInfo { get; set; }
|
||||
|
||||
public string Dependencies { get; set; }
|
||||
}
|
||||
public string Dependencies { get; set; }
|
||||
}
|
||||
|
|
|
@ -3,28 +3,27 @@ using System.Runtime.CompilerServices;
|
|||
|
||||
[assembly: InternalsVisibleTo("Microsoft.ComponentDetection.Orchestrator")]
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry;
|
||||
|
||||
public static class TelemetryConstants
|
||||
{
|
||||
public static class TelemetryConstants
|
||||
private static Guid correlationId;
|
||||
|
||||
public static Guid CorrelationId
|
||||
{
|
||||
private static Guid correlationId;
|
||||
|
||||
public static Guid CorrelationId
|
||||
get
|
||||
{
|
||||
get
|
||||
if (correlationId == Guid.Empty)
|
||||
{
|
||||
if (correlationId == Guid.Empty)
|
||||
{
|
||||
correlationId = Guid.NewGuid();
|
||||
}
|
||||
|
||||
return correlationId;
|
||||
correlationId = Guid.NewGuid();
|
||||
}
|
||||
|
||||
set // This is temporarily public, but once a process boundary exists it will be internal and initialized by Orchestrator in BCDE
|
||||
{
|
||||
correlationId = value;
|
||||
}
|
||||
return correlationId;
|
||||
}
|
||||
|
||||
set // This is temporarily public, but once a process boundary exists it will be internal and initialized by Orchestrator in BCDE
|
||||
{
|
||||
correlationId = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
namespace Microsoft.ComponentDetection.Common.Telemetry
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry;
|
||||
|
||||
public enum TelemetryMode
|
||||
{
|
||||
public enum TelemetryMode
|
||||
{
|
||||
Disabled = 0,
|
||||
Disabled = 0,
|
||||
|
||||
Debug = 1,
|
||||
Debug = 1,
|
||||
|
||||
Production = 2,
|
||||
}
|
||||
Production = 2,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
|
@ -6,80 +6,79 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.ComponentDetection.Common.Telemetry.Records;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry
|
||||
namespace Microsoft.ComponentDetection.Common.Telemetry;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton that is responsible for relaying telemetry records to Telemetry Services.
|
||||
/// </summary>
|
||||
public sealed class TelemetryRelay
|
||||
{
|
||||
// For things not populating the telemetry services collection, let's not throw.
|
||||
private TelemetryRelay() =>
|
||||
TelemetryServices = Enumerable.Empty<ITelemetryService>();
|
||||
|
||||
[ImportMany]
|
||||
public static IEnumerable<ITelemetryService> TelemetryServices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Singleton that is responsible for relaying telemetry records to Telemetry Services.
|
||||
/// Gets a value indicating whether or not the telemetry relay has been shutdown.
|
||||
/// </summary>
|
||||
public sealed class TelemetryRelay
|
||||
public static bool Active { get; private set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton.
|
||||
/// </summary>
|
||||
public static TelemetryRelay Instance { get; } = new TelemetryRelay();
|
||||
|
||||
/// <summary>
|
||||
/// Post a given telemetry record to all telemetry services.
|
||||
/// </summary>
|
||||
/// <param name="record">Record to post. </param>
|
||||
public void PostTelemetryRecord(IDetectionTelemetryRecord record)
|
||||
{
|
||||
// For things not populating the telemetry services collection, let's not throw.
|
||||
private TelemetryRelay() =>
|
||||
TelemetryServices = Enumerable.Empty<ITelemetryService>();
|
||||
|
||||
[ImportMany]
|
||||
public static IEnumerable<ITelemetryService> TelemetryServices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the telemetry relay has been shutdown.
|
||||
/// </summary>
|
||||
public static bool Active { get; private set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton.
|
||||
/// </summary>
|
||||
public static TelemetryRelay Instance { get; } = new TelemetryRelay();
|
||||
|
||||
/// <summary>
|
||||
/// Post a given telemetry record to all telemetry services.
|
||||
/// </summary>
|
||||
/// <param name="record">Record to post. </param>
|
||||
public void PostTelemetryRecord(IDetectionTelemetryRecord record)
|
||||
foreach (var service in TelemetryServices)
|
||||
{
|
||||
foreach (var service in TelemetryServices)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
service.PostRecord(record);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Telemetry should never crash the application
|
||||
}
|
||||
service.PostRecord(record);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables the sending of telemetry and flushes any messages out of the queue for each service.
|
||||
/// </summary>
|
||||
/// <returns><see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
Active = false;
|
||||
|
||||
foreach (var service in TelemetryServices)
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
// Set a timeout for services that flush synchronously.
|
||||
await AsyncExecution.ExecuteVoidWithTimeoutAsync(
|
||||
() => service.Flush(),
|
||||
TimeSpan.FromSeconds(20),
|
||||
CancellationToken.None);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Logging output failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTelemetryMode(TelemetryMode mode)
|
||||
{
|
||||
foreach (var telemetryService in TelemetryServices ?? Enumerable.Empty<ITelemetryService>())
|
||||
{
|
||||
telemetryService.SetMode(mode);
|
||||
// Telemetry should never crash the application
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables the sending of telemetry and flushes any messages out of the queue for each service.
|
||||
/// </summary>
|
||||
/// <returns><see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
Active = false;
|
||||
|
||||
foreach (var service in TelemetryServices)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Set a timeout for services that flush synchronously.
|
||||
await AsyncExecution.ExecuteVoidWithTimeoutAsync(
|
||||
() => service.Flush(),
|
||||
TimeSpan.FromSeconds(20),
|
||||
CancellationToken.None);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Logging output failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTelemetryMode(TelemetryMode mode)
|
||||
{
|
||||
foreach (var telemetryService in TelemetryServices ?? Enumerable.Empty<ITelemetryService>())
|
||||
{
|
||||
telemetryService.SetMode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public enum VerbosityMode
|
||||
{
|
||||
public enum VerbosityMode
|
||||
{
|
||||
Quiet = 0,
|
||||
Normal = 1,
|
||||
Verbose = 2,
|
||||
}
|
||||
Quiet = 0,
|
||||
Normal = 1,
|
||||
Verbose = 2,
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
namespace Microsoft.ComponentDetection.Common
|
||||
namespace Microsoft.ComponentDetection.Common;
|
||||
|
||||
public enum WarnOnAlertSeverity
|
||||
{
|
||||
public enum WarnOnAlertSeverity
|
||||
{
|
||||
Never = 0,
|
||||
Critical = 1,
|
||||
High = 2,
|
||||
Medium = 3,
|
||||
Low = 4,
|
||||
}
|
||||
Never = 0,
|
||||
Critical = 1,
|
||||
High = 2,
|
||||
Medium = 3,
|
||||
Low = 4,
|
||||
}
|
||||
|
|
|
@ -3,43 +3,42 @@ using System.Collections.Generic;
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
// Summary:
|
||||
// Details for a docker container
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class ContainerDetails
|
||||
{
|
||||
// Summary:
|
||||
// Details for a docker container
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class ContainerDetails
|
||||
{
|
||||
// Summary:
|
||||
// ImageId for the docker container.
|
||||
public string ImageId { get; set; }
|
||||
// ImageId for the docker container.
|
||||
public string ImageId { get; set; }
|
||||
|
||||
// Summary:
|
||||
// Unique id for the container.
|
||||
public int Id { get; set; }
|
||||
// Summary:
|
||||
// Unique id for the container.
|
||||
public int Id { get; set; }
|
||||
|
||||
// Summary:
|
||||
// Digests for the container
|
||||
public IEnumerable<string> Digests { get; set; }
|
||||
// Summary:
|
||||
// Digests for the container
|
||||
public IEnumerable<string> Digests { get; set; }
|
||||
|
||||
// Summary:
|
||||
// The Repository:Tag for the base image of the docker container
|
||||
// ex: alpine:latest || alpine:v3.1 || mcr.microsoft.com/dotnet/sdk:5.0
|
||||
public string BaseImageRef { get; set; }
|
||||
// Summary:
|
||||
// The Repository:Tag for the base image of the docker container
|
||||
// ex: alpine:latest || alpine:v3.1 || mcr.microsoft.com/dotnet/sdk:5.0
|
||||
public string BaseImageRef { get; set; }
|
||||
|
||||
// Summary:
|
||||
// The digest of the exact image used as the base image
|
||||
// This is to avoid errors if there are ref updates between build time and scan time
|
||||
public string BaseImageDigest { get; set; }
|
||||
// Summary:
|
||||
// The digest of the exact image used as the base image
|
||||
// This is to avoid errors if there are ref updates between build time and scan time
|
||||
public string BaseImageDigest { get; set; }
|
||||
|
||||
// Summary:
|
||||
// The time the container was created
|
||||
public DateTime CreatedAt { get; set; }
|
||||
// Summary:
|
||||
// The time the container was created
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
// Summary:
|
||||
// Tags for the container
|
||||
public IEnumerable<string> Tags { get; set; }
|
||||
// Summary:
|
||||
// Tags for the container
|
||||
public IEnumerable<string> Tags { get; set; }
|
||||
|
||||
public IEnumerable<DockerLayer> Layers { get; set; }
|
||||
}
|
||||
public IEnumerable<DockerLayer> Layers { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class DefaultGraphScanResult : ScanResult
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class DefaultGraphScanResult : ScanResult
|
||||
{
|
||||
public DependencyGraphCollection DependencyGraphs { get; set; }
|
||||
}
|
||||
public DependencyGraphCollection DependencyGraphs { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
public class DependencyGraph : Dictionary<string, HashSet<string>>
|
||||
{
|
||||
public class DependencyGraph : Dictionary<string, HashSet<string>>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
public class DependencyGraphCollection : Dictionary<string, DependencyGraphWithMetadata>
|
||||
{
|
||||
public class DependencyGraphCollection : Dictionary<string, DependencyGraphWithMetadata>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,17 +2,16 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class DependencyGraphWithMetadata
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class DependencyGraphWithMetadata
|
||||
{
|
||||
public DependencyGraph Graph { get; set; }
|
||||
public DependencyGraph Graph { get; set; }
|
||||
|
||||
public HashSet<string> ExplicitlyReferencedComponentIds { get; set; }
|
||||
public HashSet<string> ExplicitlyReferencedComponentIds { get; set; }
|
||||
|
||||
public HashSet<string> DevelopmentDependencies { get; set; }
|
||||
public HashSet<string> DevelopmentDependencies { get; set; }
|
||||
|
||||
public HashSet<string> Dependencies { get; set; }
|
||||
}
|
||||
public HashSet<string> Dependencies { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
/// <summary>Used to communicate Dependency Scope of Component.
|
||||
/// Currently only populated for Maven component.
|
||||
/// The values are ordered in terms of priority, which is used to resolve the scope for duplicate component while merging them.
|
||||
/// </summary>
|
||||
public enum DependencyScope
|
||||
{
|
||||
/// <summary>Used to communicate Dependency Scope of Component.
|
||||
/// Currently only populated for Maven component.
|
||||
/// The values are ordered in terms of priority, which is used to resolve the scope for duplicate component while merging them.
|
||||
/// </summary>
|
||||
public enum DependencyScope
|
||||
{
|
||||
/// <summary>default scope. dependencies are available in the project during all build tasks. propogated to dependent projects. </summary>
|
||||
MavenCompile = 0,
|
||||
/// <summary>default scope. dependencies are available in the project during all build tasks. propogated to dependent projects. </summary>
|
||||
MavenCompile = 0,
|
||||
|
||||
/// <summary> Required at Runtime, but not at compile time.</summary>
|
||||
MavenRuntime = 1,
|
||||
/// <summary> Required at Runtime, but not at compile time.</summary>
|
||||
MavenRuntime = 1,
|
||||
|
||||
/// <summary>Dependencies are available only at compile time and in the test classpath of the project. These dependencies are also not transitive.</summary>
|
||||
MavenProvided = 2,
|
||||
/// <summary>Dependencies are available only at compile time and in the test classpath of the project. These dependencies are also not transitive.</summary>
|
||||
MavenProvided = 2,
|
||||
|
||||
/// <summary>Similar to provided scope. Requires explicit reference to Jar. </summary>
|
||||
MavenSystem = 3,
|
||||
/// <summary>Similar to provided scope. Requires explicit reference to Jar. </summary>
|
||||
MavenSystem = 3,
|
||||
|
||||
/// <summary>Used only at runtime.</summary>
|
||||
MavenTest = 4,
|
||||
}
|
||||
/// <summary>Used only at runtime.</summary>
|
||||
MavenTest = 4,
|
||||
}
|
||||
|
|
|
@ -4,18 +4,17 @@ using Newtonsoft.Json;
|
|||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class Detector
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class Detector
|
||||
{
|
||||
public string DetectorId { get; set; }
|
||||
public string DetectorId { get; set; }
|
||||
|
||||
public bool IsExperimental { get; set; }
|
||||
public bool IsExperimental { get; set; }
|
||||
|
||||
public int Version { get; set; }
|
||||
public int Version { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
|
||||
public IEnumerable<ComponentType> SupportedComponentTypes { get; set; }
|
||||
}
|
||||
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
|
||||
public IEnumerable<ComponentType> SupportedComponentTypes { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
public class DockerLayer
|
||||
{
|
||||
public class DockerLayer
|
||||
{
|
||||
// Summary:
|
||||
// the command/script that was executed in order to create the layer.
|
||||
public string CreatedBy { get; set; }
|
||||
// Summary:
|
||||
// the command/script that was executed in order to create the layer.
|
||||
public string CreatedBy { get; set; }
|
||||
|
||||
// Summary:
|
||||
// The Layer hash (docker inspect) that represents the changes between this layer and the previous layer
|
||||
public string DiffId { get; set; }
|
||||
// Summary:
|
||||
// The Layer hash (docker inspect) that represents the changes between this layer and the previous layer
|
||||
public string DiffId { get; set; }
|
||||
|
||||
// Summary:
|
||||
// Whether or not this layer was found in the base image of the container
|
||||
public bool IsBaseImage { get; set; }
|
||||
// Summary:
|
||||
// Whether or not this layer was found in the base image of the container
|
||||
public bool IsBaseImage { get; set; }
|
||||
|
||||
// Summary:
|
||||
// 0-indexed monotonically increasing ID for the order of the layer in the container.
|
||||
// Note: only includes non-empty layers
|
||||
public int LayerIndex { get; set; }
|
||||
}
|
||||
// Summary:
|
||||
// 0-indexed monotonically increasing ID for the order of the layer in the container.
|
||||
// Note: only includes non-empty layers
|
||||
public int LayerIndex { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.ComponentDetection.Contracts.TypedComponent;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
{
|
||||
public class LayerMappedLinuxComponents
|
||||
{
|
||||
public IEnumerable<LinuxComponent> LinuxComponents { get; set; }
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
public DockerLayer DockerLayer { get; set; }
|
||||
}
|
||||
public class LayerMappedLinuxComponents
|
||||
{
|
||||
public IEnumerable<LinuxComponent> LinuxComponents { get; set; }
|
||||
|
||||
public DockerLayer DockerLayer { get; set; }
|
||||
}
|
||||
|
|
|
@ -3,20 +3,19 @@ using Newtonsoft.Json;
|
|||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class ScanResult
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class ScanResult
|
||||
{
|
||||
public IEnumerable<ScannedComponent> ComponentsFound { get; set; }
|
||||
public IEnumerable<ScannedComponent> ComponentsFound { get; set; }
|
||||
|
||||
public IEnumerable<Detector> DetectorsInScan { get; set; }
|
||||
public IEnumerable<Detector> DetectorsInScan { get; set; }
|
||||
|
||||
public Dictionary<int, ContainerDetails> ContainerDetailsMap { get; set; }
|
||||
public Dictionary<int, ContainerDetails> ContainerDetailsMap { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ProcessingResultCode ResultCode { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ProcessingResultCode ResultCode { get; set; }
|
||||
|
||||
public string SourceDirectory { get; set; }
|
||||
}
|
||||
public string SourceDirectory { get; set; }
|
||||
}
|
||||
|
|
|
@ -3,26 +3,25 @@ using Newtonsoft.Json;
|
|||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class ScannedComponent
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))]
|
||||
public class ScannedComponent
|
||||
{
|
||||
public IEnumerable<string> LocationsFoundAt { get; set; }
|
||||
public IEnumerable<string> LocationsFoundAt { get; set; }
|
||||
|
||||
public TypedComponent.TypedComponent Component { get; set; }
|
||||
public TypedComponent.TypedComponent Component { get; set; }
|
||||
|
||||
public string DetectorId { get; set; }
|
||||
public string DetectorId { get; set; }
|
||||
|
||||
public bool? IsDevelopmentDependency { get; set; }
|
||||
public bool? IsDevelopmentDependency { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public DependencyScope? DependencyScope { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public DependencyScope? DependencyScope { get; set; }
|
||||
|
||||
public IEnumerable<TypedComponent.TypedComponent> TopLevelReferrers { get; set; }
|
||||
public IEnumerable<TypedComponent.TypedComponent> TopLevelReferrers { get; set; }
|
||||
|
||||
public IEnumerable<int> ContainerDetailIds { get; set; }
|
||||
public IEnumerable<int> ContainerDetailIds { get; set; }
|
||||
|
||||
public IDictionary<int, IEnumerable<int>> ContainerLayerIds { get; set; }
|
||||
}
|
||||
public IDictionary<int, IEnumerable<int>> ContainerLayerIds { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,62 +1,61 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.ComponentDetection.Contracts.TypedComponent;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels
|
||||
namespace Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
public class TypedComponentConverter : JsonConverter
|
||||
{
|
||||
public class TypedComponentConverter : JsonConverter
|
||||
private readonly Dictionary<ComponentType, Type> componentTypesToTypes = new Dictionary<ComponentType, Type>
|
||||
{
|
||||
private readonly Dictionary<ComponentType, Type> componentTypesToTypes = new Dictionary<ComponentType, Type>
|
||||
{
|
||||
{ ComponentType.Other, typeof(OtherComponent) },
|
||||
{ ComponentType.NuGet, typeof(NuGetComponent) },
|
||||
{ ComponentType.Npm, typeof(NpmComponent) },
|
||||
{ ComponentType.Maven, typeof(MavenComponent) },
|
||||
{ ComponentType.Git, typeof(GitComponent) },
|
||||
{ ComponentType.RubyGems, typeof(RubyGemsComponent) },
|
||||
{ ComponentType.Cargo, typeof(CargoComponent) },
|
||||
{ ComponentType.Pip, typeof(PipComponent) },
|
||||
{ ComponentType.Go, typeof(GoComponent) },
|
||||
{ ComponentType.DockerImage, typeof(DockerImageComponent) },
|
||||
{ ComponentType.Pod, typeof(PodComponent) },
|
||||
{ ComponentType.Linux, typeof(LinuxComponent) },
|
||||
{ ComponentType.Conda, typeof(CondaComponent) },
|
||||
{ ComponentType.DockerReference, typeof(DockerReferenceComponent) },
|
||||
{ ComponentType.Vcpkg, typeof(VcpkgComponent) },
|
||||
{ ComponentType.Spdx, typeof(SpdxComponent) },
|
||||
};
|
||||
{ ComponentType.Other, typeof(OtherComponent) },
|
||||
{ ComponentType.NuGet, typeof(NuGetComponent) },
|
||||
{ ComponentType.Npm, typeof(NpmComponent) },
|
||||
{ ComponentType.Maven, typeof(MavenComponent) },
|
||||
{ ComponentType.Git, typeof(GitComponent) },
|
||||
{ ComponentType.RubyGems, typeof(RubyGemsComponent) },
|
||||
{ ComponentType.Cargo, typeof(CargoComponent) },
|
||||
{ ComponentType.Pip, typeof(PipComponent) },
|
||||
{ ComponentType.Go, typeof(GoComponent) },
|
||||
{ ComponentType.DockerImage, typeof(DockerImageComponent) },
|
||||
{ ComponentType.Pod, typeof(PodComponent) },
|
||||
{ ComponentType.Linux, typeof(LinuxComponent) },
|
||||
{ ComponentType.Conda, typeof(CondaComponent) },
|
||||
{ ComponentType.DockerReference, typeof(DockerReferenceComponent) },
|
||||
{ ComponentType.Vcpkg, typeof(VcpkgComponent) },
|
||||
{ ComponentType.Spdx, typeof(SpdxComponent) },
|
||||
};
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(TypedComponent.TypedComponent);
|
||||
}
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(TypedComponent.TypedComponent);
|
||||
}
|
||||
|
||||
public override object ReadJson(
|
||||
JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var jo = JToken.Load(reader);
|
||||
public override object ReadJson(
|
||||
JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var jo = JToken.Load(reader);
|
||||
|
||||
var value = (ComponentType)Enum.Parse(typeof(ComponentType), (string)jo["type"]);
|
||||
var targetType = this.componentTypesToTypes[value];
|
||||
var instanceOfTypedComponent = Activator.CreateInstance(targetType, true);
|
||||
serializer.Populate(jo.CreateReader(), instanceOfTypedComponent);
|
||||
var value = (ComponentType)Enum.Parse(typeof(ComponentType), (string)jo["type"]);
|
||||
var targetType = this.componentTypesToTypes[value];
|
||||
var instanceOfTypedComponent = Activator.CreateInstance(targetType, true);
|
||||
serializer.Populate(jo.CreateReader(), instanceOfTypedComponent);
|
||||
|
||||
return instanceOfTypedComponent;
|
||||
}
|
||||
return instanceOfTypedComponent;
|
||||
}
|
||||
|
||||
public override void WriteJson(
|
||||
JsonWriter writer,
|
||||
object value,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override void WriteJson(
|
||||
JsonWriter writer,
|
||||
object value,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,73 +2,72 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
/// <summary>A detected component, found during component detection scans. This is the container for all metadata gathered during detection.</summary>
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
public class DetectedComponent
|
||||
{
|
||||
/// <summary>A detected component, found during component detection scans. This is the container for all metadata gathered during detection.</summary>
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
public class DetectedComponent
|
||||
private readonly object hashLock = new object();
|
||||
|
||||
/// <summary>Creates a new DetectedComponent.</summary>
|
||||
/// <param name="component">The typed component instance to base this detection on.</param>
|
||||
/// <param name="detector">The detector that detected this component.</param>
|
||||
/// <param name="containerDetailsId">Id of the containerDetails, this is only necessary if the component was found inside a container.</param>
|
||||
/// <param name="containerLayerId">Id of the layer the component was found, this is only necessary if the component was found inside a container.</param>
|
||||
public DetectedComponent(TypedComponent.TypedComponent component, IComponentDetector detector = null, int? containerDetailsId = null, int? containerLayerId = null)
|
||||
{
|
||||
private readonly object hashLock = new object();
|
||||
|
||||
/// <summary>Creates a new DetectedComponent.</summary>
|
||||
/// <param name="component">The typed component instance to base this detection on.</param>
|
||||
/// <param name="detector">The detector that detected this component.</param>
|
||||
/// <param name="containerDetailsId">Id of the containerDetails, this is only necessary if the component was found inside a container.</param>
|
||||
/// <param name="containerLayerId">Id of the layer the component was found, this is only necessary if the component was found inside a container.</param>
|
||||
public DetectedComponent(TypedComponent.TypedComponent component, IComponentDetector detector = null, int? containerDetailsId = null, int? containerLayerId = null)
|
||||
this.Component = component;
|
||||
this.FilePaths = new HashSet<string>();
|
||||
this.DetectedBy = detector;
|
||||
this.ContainerDetailIds = new HashSet<int>();
|
||||
this.ContainerLayerIds = new Dictionary<int, IEnumerable<int>>();
|
||||
if (containerDetailsId.HasValue)
|
||||
{
|
||||
this.Component = component;
|
||||
this.FilePaths = new HashSet<string>();
|
||||
this.DetectedBy = detector;
|
||||
this.ContainerDetailIds = new HashSet<int>();
|
||||
this.ContainerLayerIds = new Dictionary<int, IEnumerable<int>>();
|
||||
if (containerDetailsId.HasValue)
|
||||
this.ContainerDetailIds.Add(containerDetailsId.Value);
|
||||
if (containerLayerId.HasValue)
|
||||
{
|
||||
this.ContainerDetailIds.Add(containerDetailsId.Value);
|
||||
if (containerLayerId.HasValue)
|
||||
{
|
||||
this.ContainerLayerIds.Add(containerDetailsId.Value, new List<int>() { containerLayerId.Value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the detector that detected this component.
|
||||
/// </summary>
|
||||
public IComponentDetector DetectedBy { get; set; }
|
||||
|
||||
/// <summary>Gets the component associated with this detection.</summary>
|
||||
public TypedComponent.TypedComponent Component { get; private set; }
|
||||
|
||||
/// <summary> Gets or sets the hashset containing the file paths associated with the component. </summary>
|
||||
public HashSet<string> FilePaths { get; set; }
|
||||
|
||||
/// <summary> Gets or sets the dependency roots for this component. </summary>
|
||||
public HashSet<TypedComponent.TypedComponent> DependencyRoots { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the flag to mark the component as a development dependency or not.
|
||||
/// This is used at build or development time not a distributed dependency.</summary>
|
||||
public bool? DevelopmentDependency { get; set; }
|
||||
|
||||
/// <summary> Gets or sets the details of the container where this component was found.</summary>
|
||||
public HashSet<int> ContainerDetailIds { get; set; }
|
||||
|
||||
/// <summary> Gets or sets the layer within a container where this component was found.</summary>
|
||||
public IDictionary<int, IEnumerable<int>> ContainerLayerIds { get; set; }
|
||||
|
||||
/// <summary> Gets or sets Dependency Scope of the component.</summary>
|
||||
public DependencyScope? DependencyScope { get; set; }
|
||||
|
||||
private string DebuggerDisplay => $"{this.Component.DebuggerDisplay}";
|
||||
|
||||
/// <summary>Adds a filepath to the FilePaths hashset for this detected component.</summary>
|
||||
/// <param name="filePath">The file path to add to the hashset.</param>
|
||||
public void AddComponentFilePath(string filePath)
|
||||
{
|
||||
lock (this.hashLock)
|
||||
{
|
||||
this.FilePaths.Add(filePath);
|
||||
this.ContainerLayerIds.Add(containerDetailsId.Value, new List<int>() { containerLayerId.Value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the detector that detected this component.
|
||||
/// </summary>
|
||||
public IComponentDetector DetectedBy { get; set; }
|
||||
|
||||
/// <summary>Gets the component associated with this detection.</summary>
|
||||
public TypedComponent.TypedComponent Component { get; private set; }
|
||||
|
||||
/// <summary> Gets or sets the hashset containing the file paths associated with the component. </summary>
|
||||
public HashSet<string> FilePaths { get; set; }
|
||||
|
||||
/// <summary> Gets or sets the dependency roots for this component. </summary>
|
||||
public HashSet<TypedComponent.TypedComponent> DependencyRoots { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the flag to mark the component as a development dependency or not.
|
||||
/// This is used at build or development time not a distributed dependency.</summary>
|
||||
public bool? DevelopmentDependency { get; set; }
|
||||
|
||||
/// <summary> Gets or sets the details of the container where this component was found.</summary>
|
||||
public HashSet<int> ContainerDetailIds { get; set; }
|
||||
|
||||
/// <summary> Gets or sets the layer within a container where this component was found.</summary>
|
||||
public IDictionary<int, IEnumerable<int>> ContainerLayerIds { get; set; }
|
||||
|
||||
/// <summary> Gets or sets Dependency Scope of the component.</summary>
|
||||
public DependencyScope? DependencyScope { get; set; }
|
||||
|
||||
private string DebuggerDisplay => $"{this.Component.DebuggerDisplay}";
|
||||
|
||||
/// <summary>Adds a filepath to the FilePaths hashset for this detected component.</summary>
|
||||
/// <param name="filePath">The file path to add to the hashset.</param>
|
||||
public void AddComponentFilePath(string filePath)
|
||||
{
|
||||
lock (this.hashLock)
|
||||
{
|
||||
this.FilePaths.Add(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,47 @@
|
|||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
/// <summary>Class of detector, the names of which are converted into categories for all default detectors.</summary>
|
||||
public enum DetectorClass
|
||||
{
|
||||
/// <summary>Class of detector, the names of which are converted into categories for all default detectors.</summary>
|
||||
public enum DetectorClass
|
||||
{
|
||||
/// <summary>Default value, which indicates all classes should be run. Not used as an actual category.</summary>
|
||||
All,
|
||||
/// <summary>Default value, which indicates all classes should be run. Not used as an actual category.</summary>
|
||||
All,
|
||||
|
||||
/// <summary>Indicates a detector applies to NPM packages.</summary>
|
||||
Npm,
|
||||
/// <summary>Indicates a detector applies to NPM packages.</summary>
|
||||
Npm,
|
||||
|
||||
/// <summary>Indicates a detector applies to NuGet packages.</summary>
|
||||
NuGet,
|
||||
/// <summary>Indicates a detector applies to NuGet packages.</summary>
|
||||
NuGet,
|
||||
|
||||
/// <summary>Indicates a detector applies to Maven packages.</summary>
|
||||
Maven,
|
||||
/// <summary>Indicates a detector applies to Maven packages.</summary>
|
||||
Maven,
|
||||
|
||||
/// <summary>Indicates a detector applies to RubyGems packages.</summary>
|
||||
RubyGems,
|
||||
/// <summary>Indicates a detector applies to RubyGems packages.</summary>
|
||||
RubyGems,
|
||||
|
||||
/// <summary>Indicates a detector applies to Cargo packages.</summary>
|
||||
Cargo,
|
||||
/// <summary>Indicates a detector applies to Cargo packages.</summary>
|
||||
Cargo,
|
||||
|
||||
/// <summary>Indicates a detector applies to Pip packages.</summary>
|
||||
Pip,
|
||||
/// <summary>Indicates a detector applies to Pip packages.</summary>
|
||||
Pip,
|
||||
|
||||
/// <summary>Indicates a detector applies to Go modules.</summary>
|
||||
GoMod,
|
||||
/// <summary>Indicates a detector applies to Go modules.</summary>
|
||||
GoMod,
|
||||
|
||||
/// <summary>Indicates a detector applies to CocoaPods packages.</summary>
|
||||
CocoaPods,
|
||||
/// <summary>Indicates a detector applies to CocoaPods packages.</summary>
|
||||
CocoaPods,
|
||||
|
||||
/// <summary>Indicates a detector applies to Linux packages.</summary>
|
||||
Linux,
|
||||
/// <summary>Indicates a detector applies to Linux packages.</summary>
|
||||
Linux,
|
||||
|
||||
/// <summary>Indicates a detector applies to Conda packages.</summary>
|
||||
Conda,
|
||||
/// <summary>Indicates a detector applies to Conda packages.</summary>
|
||||
Conda,
|
||||
|
||||
/// <summary>Indicates a detector applies to SPDX files.</summary>
|
||||
Spdx,
|
||||
/// <summary>Indicates a detector applies to SPDX files.</summary>
|
||||
Spdx,
|
||||
|
||||
/// <summary>Indicates a detector applies to Vcpkg packages.</summary>
|
||||
Vcpkg,
|
||||
/// <summary>Indicates a detector applies to Vcpkg packages.</summary>
|
||||
Vcpkg,
|
||||
|
||||
/// <summary>Indicates a detector applies to Docker references.</summary>
|
||||
DockerReference,
|
||||
}
|
||||
/// <summary>Indicates a detector applies to Docker references.</summary>
|
||||
DockerReference,
|
||||
}
|
||||
|
|
|
@ -1,219 +1,218 @@
|
|||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
public enum DockerReferenceKind
|
||||
{
|
||||
public enum DockerReferenceKind
|
||||
{
|
||||
Canonical = 0,
|
||||
Repository = 1,
|
||||
Tagged = 2,
|
||||
Dual = 3,
|
||||
Digest = 4,
|
||||
}
|
||||
Canonical = 0,
|
||||
Repository = 1,
|
||||
Tagged = 2,
|
||||
Dual = 3,
|
||||
Digest = 4,
|
||||
}
|
||||
|
||||
#pragma warning disable SA1402
|
||||
public class DockerReference
|
||||
{
|
||||
public virtual DockerReferenceKind Kind { get; }
|
||||
public class DockerReference
|
||||
{
|
||||
public virtual DockerReferenceKind Kind { get; }
|
||||
|
||||
public static DockerReference CreateDockerReference(string repository, string domain, string digest, string tag)
|
||||
public static DockerReference CreateDockerReference(string repository, string domain, string digest, string tag)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(repository) && string.IsNullOrEmpty(domain))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(repository) && string.IsNullOrEmpty(domain))
|
||||
if (!string.IsNullOrEmpty(digest))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(digest))
|
||||
return new DigestReference
|
||||
{
|
||||
return new DigestReference
|
||||
{
|
||||
Digest = digest,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.InvalidOperationException("Repository name must have at least one component");
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrEmpty(tag))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(digest))
|
||||
{
|
||||
return new CanonicalReference
|
||||
{
|
||||
Domain = domain,
|
||||
Repository = repository,
|
||||
Digest = digest,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RepositoryReference
|
||||
{
|
||||
Domain = domain,
|
||||
Repository = repository,
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrEmpty(digest))
|
||||
{
|
||||
return new TaggedReference
|
||||
{
|
||||
Domain = domain,
|
||||
Repository = repository,
|
||||
Tag = tag,
|
||||
Digest = digest,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DualReference
|
||||
throw new System.InvalidOperationException("Repository name must have at least one component");
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrEmpty(tag))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(digest))
|
||||
{
|
||||
return new CanonicalReference
|
||||
{
|
||||
Domain = domain,
|
||||
Repository = repository,
|
||||
Tag = tag,
|
||||
Digest = digest,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public virtual TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class Reference
|
||||
{
|
||||
public string Tag { get; set; }
|
||||
|
||||
public string Digest { get; set; }
|
||||
|
||||
public string Repository { get; set; }
|
||||
|
||||
public string Domain { get; set; }
|
||||
}
|
||||
|
||||
// sha256:abc123...
|
||||
public class DigestReference : DockerReference
|
||||
{
|
||||
public string Digest { get; set; }
|
||||
|
||||
public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Digest;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Digest}";
|
||||
}
|
||||
|
||||
public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
return new TypedComponent.DockerReferenceComponent(this)
|
||||
else
|
||||
{
|
||||
Digest = this.Digest,
|
||||
return new RepositoryReference
|
||||
{
|
||||
Domain = domain,
|
||||
Repository = repository,
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrEmpty(digest))
|
||||
{
|
||||
return new TaggedReference
|
||||
{
|
||||
Domain = domain,
|
||||
Repository = repository,
|
||||
Tag = tag,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DualReference
|
||||
{
|
||||
Domain = domain,
|
||||
Repository = repository,
|
||||
Tag = tag,
|
||||
Digest = digest,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// docker.io/library/ubuntu@sha256:abc123...
|
||||
public class CanonicalReference : DockerReference
|
||||
public virtual TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
public string Domain { get; set; }
|
||||
|
||||
public string Repository { get; set; }
|
||||
|
||||
public string Digest { get; set; }
|
||||
|
||||
public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Canonical;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Domain}/{this.Repository}@${this.Digest}";
|
||||
}
|
||||
|
||||
public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
return new TypedComponent.DockerReferenceComponent(this)
|
||||
{
|
||||
Domain = this.Domain,
|
||||
Digest = this.Digest,
|
||||
Repository = this.Repository,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// docker.io/library/ubuntu
|
||||
public class RepositoryReference : DockerReference
|
||||
{
|
||||
public string Domain { get; set; }
|
||||
|
||||
public string Repository { get; set; }
|
||||
|
||||
public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Repository;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Repository}";
|
||||
}
|
||||
|
||||
public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
return new TypedComponent.DockerReferenceComponent(this)
|
||||
{
|
||||
Domain = this.Domain,
|
||||
Repository = this.Repository,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// docker.io/library/ubuntu:latest
|
||||
public class TaggedReference : DockerReference
|
||||
{
|
||||
public string Domain { get; set; }
|
||||
|
||||
public string Repository { get; set; }
|
||||
|
||||
public string Tag { get; set; }
|
||||
|
||||
public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Tagged;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Domain}/{this.Repository}:${this.Tag}";
|
||||
}
|
||||
|
||||
public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
return new TypedComponent.DockerReferenceComponent(this)
|
||||
{
|
||||
Domain = this.Domain,
|
||||
Tag = this.Tag,
|
||||
Repository = this.Repository,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// docker.io/library/ubuntu:latest@sha256:abc123...
|
||||
public class DualReference : DockerReference
|
||||
{
|
||||
public string Domain { get; set; }
|
||||
|
||||
public string Repository { get; set; }
|
||||
|
||||
public string Tag { get; set; }
|
||||
|
||||
public string Digest { get; set; }
|
||||
|
||||
public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Dual;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Domain}/{this.Repository}:${this.Tag}@${this.Digest}";
|
||||
}
|
||||
|
||||
public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
return new TypedComponent.DockerReferenceComponent(this)
|
||||
{
|
||||
Domain = this.Domain,
|
||||
Digest = this.Digest,
|
||||
Tag = this.Tag,
|
||||
Repository = this.Repository,
|
||||
};
|
||||
}
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class Reference
|
||||
{
|
||||
public string Tag { get; set; }
|
||||
|
||||
public string Digest { get; set; }
|
||||
|
||||
public string Repository { get; set; }
|
||||
|
||||
public string Domain { get; set; }
|
||||
}
|
||||
|
||||
// sha256:abc123...
|
||||
public class DigestReference : DockerReference
|
||||
{
|
||||
public string Digest { get; set; }
|
||||
|
||||
public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Digest;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Digest}";
|
||||
}
|
||||
|
||||
public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
return new TypedComponent.DockerReferenceComponent(this)
|
||||
{
|
||||
Digest = this.Digest,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// docker.io/library/ubuntu@sha256:abc123...
|
||||
public class CanonicalReference : DockerReference
|
||||
{
|
||||
public string Domain { get; set; }
|
||||
|
||||
public string Repository { get; set; }
|
||||
|
||||
public string Digest { get; set; }
|
||||
|
||||
public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Canonical;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Domain}/{this.Repository}@${this.Digest}";
|
||||
}
|
||||
|
||||
public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
return new TypedComponent.DockerReferenceComponent(this)
|
||||
{
|
||||
Domain = this.Domain,
|
||||
Digest = this.Digest,
|
||||
Repository = this.Repository,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// docker.io/library/ubuntu
|
||||
public class RepositoryReference : DockerReference
|
||||
{
|
||||
public string Domain { get; set; }
|
||||
|
||||
public string Repository { get; set; }
|
||||
|
||||
public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Repository;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Repository}";
|
||||
}
|
||||
|
||||
public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
return new TypedComponent.DockerReferenceComponent(this)
|
||||
{
|
||||
Domain = this.Domain,
|
||||
Repository = this.Repository,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// docker.io/library/ubuntu:latest
|
||||
public class TaggedReference : DockerReference
|
||||
{
|
||||
public string Domain { get; set; }
|
||||
|
||||
public string Repository { get; set; }
|
||||
|
||||
public string Tag { get; set; }
|
||||
|
||||
public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Tagged;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Domain}/{this.Repository}:${this.Tag}";
|
||||
}
|
||||
|
||||
public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
return new TypedComponent.DockerReferenceComponent(this)
|
||||
{
|
||||
Domain = this.Domain,
|
||||
Tag = this.Tag,
|
||||
Repository = this.Repository,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// docker.io/library/ubuntu:latest@sha256:abc123...
|
||||
public class DualReference : DockerReference
|
||||
{
|
||||
public string Domain { get; set; }
|
||||
|
||||
public string Repository { get; set; }
|
||||
|
||||
public string Tag { get; set; }
|
||||
|
||||
public string Digest { get; set; }
|
||||
|
||||
public override DockerReferenceKind Kind { get; } = DockerReferenceKind.Dual;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Domain}/{this.Repository}:${this.Tag}@${this.Digest}";
|
||||
}
|
||||
|
||||
public override TypedComponent.DockerReferenceComponent ToTypedDockerReferenceComponent()
|
||||
{
|
||||
return new TypedComponent.DockerReferenceComponent(this)
|
||||
{
|
||||
Domain = this.Domain,
|
||||
Digest = this.Digest,
|
||||
Tag = this.Tag,
|
||||
Repository = this.Repository,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
using System.IO;
|
||||
|
@ -8,118 +8,117 @@ using System.Threading.Tasks.Dataflow;
|
|||
using Microsoft.ComponentDetection.Contracts.Internal;
|
||||
using Microsoft.ComponentDetection.Contracts.TypedComponent;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
/// <summary>Specialized base class for file based component detection.</summary>
|
||||
public abstract class FileComponentDetector : IComponentDetector
|
||||
{
|
||||
/// <summary>Specialized base class for file based component detection.</summary>
|
||||
public abstract class FileComponentDetector : IComponentDetector
|
||||
/// <summary>
|
||||
/// Gets or sets the factory for handing back component streams to File detectors. Injected automatically by MEF composition.
|
||||
/// </summary>
|
||||
[Import]
|
||||
public IComponentStreamEnumerableFactory ComponentStreamEnumerableFactory { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the logger for writing basic logging message to both console and file. Injected automatically by MEF composition.</summary>
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public IComponentRecorder ComponentRecorder { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string Id { get; }
|
||||
|
||||
/// <summary> Gets the search patterns used to produce the list of valid folders to scan. These patterns are evaluated with .Net's Directory.EnumerateFiles function. </summary>
|
||||
public abstract IList<string> SearchPatterns { get; }
|
||||
|
||||
/// <summary>Gets the categories this detector is considered a member of. Used by the DetectorCategories arg to include detectors.</summary>
|
||||
public abstract IEnumerable<string> Categories { get; }
|
||||
|
||||
/// <summary>Gets the supported component types. </summary>
|
||||
public abstract IEnumerable<ComponentType> SupportedComponentTypes { get; }
|
||||
|
||||
/// <summary>Gets the version of this component detector. </summary>
|
||||
public abstract int Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder names that will be skipped by the Component Detector.
|
||||
/// </summary>
|
||||
protected virtual IList<string> SkippedFolders => new List<string> { };
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the active scan request -- only populated after a ScanDirectoryAsync is invoked. If ScanDirectoryAsync is overridden,
|
||||
/// the overrider should ensure this property is populated.
|
||||
/// </summary>
|
||||
protected ScanRequest CurrentScanRequest { get; set; }
|
||||
|
||||
[Import]
|
||||
public IObservableDirectoryWalkerFactory Scanner { get; set; }
|
||||
|
||||
public bool NeedsAutomaticRootDependencyCalculation { get; protected set; }
|
||||
|
||||
protected Dictionary<string, string> Telemetry { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
protected IObservable<IComponentStream> ComponentStreams { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async virtual Task<IndividualDetectorScanResult> ExecuteDetectorAsync(ScanRequest request)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the factory for handing back component streams to File detectors. Injected automatically by MEF composition.
|
||||
/// </summary>
|
||||
[Import]
|
||||
public IComponentStreamEnumerableFactory ComponentStreamEnumerableFactory { get; set; }
|
||||
this.ComponentRecorder = request.ComponentRecorder;
|
||||
this.Scanner.Initialize(request.SourceDirectory, request.DirectoryExclusionPredicate, 1);
|
||||
return await this.ScanDirectoryAsync(request);
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the logger for writing basic logging message to both console and file. Injected automatically by MEF composition.</summary>
|
||||
[Import]
|
||||
public ILogger Logger { get; set; }
|
||||
private Task<IndividualDetectorScanResult> ScanDirectoryAsync(ScanRequest request)
|
||||
{
|
||||
this.CurrentScanRequest = request;
|
||||
|
||||
public IComponentRecorder ComponentRecorder { get; private set; }
|
||||
var filteredObservable = this.Scanner.GetFilteredComponentStreamObservable(request.SourceDirectory, this.SearchPatterns, request.ComponentRecorder);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract string Id { get; }
|
||||
this.Logger?.LogVerbose($"Registered {this.GetType().FullName}");
|
||||
return this.ProcessAsync(filteredObservable, request.DetectorArgs);
|
||||
}
|
||||
|
||||
/// <summary> Gets the search patterns used to produce the list of valid folders to scan. These patterns are evaluated with .Net's Directory.EnumerateFiles function. </summary>
|
||||
public abstract IList<string> SearchPatterns { get; }
|
||||
/// <summary>
|
||||
/// Gets the file streams for the Detector's declared <see cref="SearchPatterns"/> as an <see cref="IEnumerable{IComponentStream}"/>.
|
||||
/// </summary>
|
||||
/// <param name="sourceDirectory">The directory to search.</param>
|
||||
/// <param name="exclusionPredicate">The exclusion predicate function.</param>
|
||||
/// <returns>Awaitable task with enumerable streams <see cref="IEnumerable{IComponentStream}"/> for the declared detector. </returns>
|
||||
protected Task<IEnumerable<IComponentStream>> GetFileStreamsAsync(DirectoryInfo sourceDirectory, ExcludeDirectoryPredicate exclusionPredicate)
|
||||
{
|
||||
return Task.FromResult(this.ComponentStreamEnumerableFactory.GetComponentStreams(sourceDirectory, this.SearchPatterns, exclusionPredicate));
|
||||
}
|
||||
|
||||
/// <summary>Gets the categories this detector is considered a member of. Used by the DetectorCategories arg to include detectors.</summary>
|
||||
public abstract IEnumerable<string> Categories { get; }
|
||||
private async Task<IndividualDetectorScanResult> ProcessAsync(IObservable<ProcessRequest> processRequests, IDictionary<string, string> detectorArgs)
|
||||
{
|
||||
var processor = new ActionBlock<ProcessRequest>(async processRequest => await this.OnFileFound(processRequest, detectorArgs));
|
||||
|
||||
/// <summary>Gets the supported component types. </summary>
|
||||
public abstract IEnumerable<ComponentType> SupportedComponentTypes { get; }
|
||||
var preprocessedObserbable = await this.OnPrepareDetection(processRequests, detectorArgs);
|
||||
|
||||
/// <summary>Gets the version of this component detector. </summary>
|
||||
public abstract int Version { get; }
|
||||
await preprocessedObserbable.ForEachAsync(processRequest => processor.Post(processRequest));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder names that will be skipped by the Component Detector.
|
||||
/// </summary>
|
||||
protected virtual IList<string> SkippedFolders => new List<string> { };
|
||||
processor.Complete();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the active scan request -- only populated after a ScanDirectoryAsync is invoked. If ScanDirectoryAsync is overridden,
|
||||
/// the overrider should ensure this property is populated.
|
||||
/// </summary>
|
||||
protected ScanRequest CurrentScanRequest { get; set; }
|
||||
await processor.Completion;
|
||||
|
||||
[Import]
|
||||
public IObservableDirectoryWalkerFactory Scanner { get; set; }
|
||||
await this.OnDetectionFinished();
|
||||
|
||||
public bool NeedsAutomaticRootDependencyCalculation { get; protected set; }
|
||||
|
||||
protected Dictionary<string, string> Telemetry { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
protected IObservable<IComponentStream> ComponentStreams { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async virtual Task<IndividualDetectorScanResult> ExecuteDetectorAsync(ScanRequest request)
|
||||
return new IndividualDetectorScanResult
|
||||
{
|
||||
this.ComponentRecorder = request.ComponentRecorder;
|
||||
this.Scanner.Initialize(request.SourceDirectory, request.DirectoryExclusionPredicate, 1);
|
||||
return await this.ScanDirectoryAsync(request);
|
||||
}
|
||||
ResultCode = ProcessingResultCode.Success,
|
||||
AdditionalTelemetryDetails = this.Telemetry,
|
||||
};
|
||||
}
|
||||
|
||||
private Task<IndividualDetectorScanResult> ScanDirectoryAsync(ScanRequest request)
|
||||
{
|
||||
this.CurrentScanRequest = request;
|
||||
protected virtual Task<IObservable<ProcessRequest>> OnPrepareDetection(IObservable<ProcessRequest> processRequests, IDictionary<string, string> detectorArgs)
|
||||
{
|
||||
return Task.FromResult(processRequests);
|
||||
}
|
||||
|
||||
var filteredObservable = this.Scanner.GetFilteredComponentStreamObservable(request.SourceDirectory, this.SearchPatterns, request.ComponentRecorder);
|
||||
protected abstract Task OnFileFound(ProcessRequest processRequest, IDictionary<string, string> detectorArgs);
|
||||
|
||||
this.Logger?.LogVerbose($"Registered {this.GetType().FullName}");
|
||||
return this.ProcessAsync(filteredObservable, request.DetectorArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file streams for the Detector's declared <see cref="SearchPatterns"/> as an <see cref="IEnumerable{IComponentStream}"/>.
|
||||
/// </summary>
|
||||
/// <param name="sourceDirectory">The directory to search.</param>
|
||||
/// <param name="exclusionPredicate">The exclusion predicate function.</param>
|
||||
/// <returns>Awaitable task with enumerable streams <see cref="IEnumerable{IComponentStream}"/> for the declared detector. </returns>
|
||||
protected Task<IEnumerable<IComponentStream>> GetFileStreamsAsync(DirectoryInfo sourceDirectory, ExcludeDirectoryPredicate exclusionPredicate)
|
||||
{
|
||||
return Task.FromResult(this.ComponentStreamEnumerableFactory.GetComponentStreams(sourceDirectory, this.SearchPatterns, exclusionPredicate));
|
||||
}
|
||||
|
||||
private async Task<IndividualDetectorScanResult> ProcessAsync(IObservable<ProcessRequest> processRequests, IDictionary<string, string> detectorArgs)
|
||||
{
|
||||
var processor = new ActionBlock<ProcessRequest>(async processRequest => await this.OnFileFound(processRequest, detectorArgs));
|
||||
|
||||
var preprocessedObserbable = await this.OnPrepareDetection(processRequests, detectorArgs);
|
||||
|
||||
await preprocessedObserbable.ForEachAsync(processRequest => processor.Post(processRequest));
|
||||
|
||||
processor.Complete();
|
||||
|
||||
await processor.Completion;
|
||||
|
||||
await this.OnDetectionFinished();
|
||||
|
||||
return new IndividualDetectorScanResult
|
||||
{
|
||||
ResultCode = ProcessingResultCode.Success,
|
||||
AdditionalTelemetryDetails = this.Telemetry,
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual Task<IObservable<ProcessRequest>> OnPrepareDetection(IObservable<ProcessRequest> processRequests, IDictionary<string, string> detectorArgs)
|
||||
{
|
||||
return Task.FromResult(processRequests);
|
||||
}
|
||||
|
||||
protected abstract Task OnFileFound(ProcessRequest processRequest, IDictionary<string, string> detectorArgs);
|
||||
|
||||
protected virtual Task OnDetectionFinished()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
protected virtual Task OnDetectionFinished()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,74 +2,73 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing execution on a command line. Generally, methods on this service expect a command line environment to be
|
||||
/// where the detection tool is running, so all logic relying on them should gate on .IsCommandLineExecution().
|
||||
/// </summary>
|
||||
public interface ICommandLineInvocationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for managing execution on a command line. Generally, methods on this service expect a command line environment to be
|
||||
/// where the detection tool is running, so all logic relying on them should gate on .IsCommandLineExecution().
|
||||
/// Used to gate logic that requires a command line execution environment.
|
||||
/// </summary>
|
||||
public interface ICommandLineInvocationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to gate logic that requires a command line execution environment.
|
||||
/// </summary>
|
||||
/// <returns>True if it is a command line execution environment, false otherwise.</returns>
|
||||
bool IsCommandLineExecution();
|
||||
/// <returns>True if it is a command line execution environment, false otherwise.</returns>
|
||||
bool IsCommandLineExecution();
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the given command can be located -- in cases of absolute paths, this is a simple File.Exists. For non-absolute paths, all PATH entries are checked.
|
||||
/// </summary>
|
||||
/// <param name="command">The command name to execute. Environment variables like PATH on windows will also be considered if the command is not an absolute path. </param>
|
||||
/// <param name="additionalCandidateCommands">Other commands that could satisfy the need for the first command. Assumption is that they all share similar calling patterns.</param>
|
||||
/// <param name="workingDirectory">The directory under which to execute the command.</param>
|
||||
/// <param name="parameters">The parameters that should be passed to the command. The parameters will be space-joined.</param>
|
||||
/// <returns>Awaitable task with true if the command can be found in the local environment, false otherwise.</returns>
|
||||
Task<bool> CanCommandBeLocated(string command, IEnumerable<string> additionalCandidateCommands = null, DirectoryInfo workingDirectory = null, params string[] parameters);
|
||||
/// <summary>
|
||||
/// Checks to see if the given command can be located -- in cases of absolute paths, this is a simple File.Exists. For non-absolute paths, all PATH entries are checked.
|
||||
/// </summary>
|
||||
/// <param name="command">The command name to execute. Environment variables like PATH on windows will also be considered if the command is not an absolute path. </param>
|
||||
/// <param name="additionalCandidateCommands">Other commands that could satisfy the need for the first command. Assumption is that they all share similar calling patterns.</param>
|
||||
/// <param name="workingDirectory">The directory under which to execute the command.</param>
|
||||
/// <param name="parameters">The parameters that should be passed to the command. The parameters will be space-joined.</param>
|
||||
/// <returns>Awaitable task with true if the command can be found in the local environment, false otherwise.</returns>
|
||||
Task<bool> CanCommandBeLocated(string command, IEnumerable<string> additionalCandidateCommands = null, DirectoryInfo workingDirectory = null, params string[] parameters);
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the given command can be located -- in cases of absolute paths, this is a simple File.Exists. For non-absolute paths, all PATH entries are checked.
|
||||
/// </summary>
|
||||
/// <param name="command">The command name to execute. Environment variables like PATH on windows will also be considered if the command is not an absolute path. </param>
|
||||
/// <param name="additionalCandidateCommands">Other commands that could satisfy the need for the first command. Assumption is that they all share similar calling patterns.</param>
|
||||
/// <param name="parameters">The parameters that should be passed to the command. The parameters will be space-joined.</param>
|
||||
/// <returns>Awaitable task with true if the command can be found in the local environment, false otherwise.</returns>
|
||||
Task<bool> CanCommandBeLocated(string command, IEnumerable<string> additionalCandidateCommands = null, params string[] parameters);
|
||||
/// <summary>
|
||||
/// Checks to see if the given command can be located -- in cases of absolute paths, this is a simple File.Exists. For non-absolute paths, all PATH entries are checked.
|
||||
/// </summary>
|
||||
/// <param name="command">The command name to execute. Environment variables like PATH on windows will also be considered if the command is not an absolute path. </param>
|
||||
/// <param name="additionalCandidateCommands">Other commands that could satisfy the need for the first command. Assumption is that they all share similar calling patterns.</param>
|
||||
/// <param name="parameters">The parameters that should be passed to the command. The parameters will be space-joined.</param>
|
||||
/// <returns>Awaitable task with true if the command can be found in the local environment, false otherwise.</returns>
|
||||
Task<bool> CanCommandBeLocated(string command, IEnumerable<string> additionalCandidateCommands = null, params string[] parameters);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a command line command. If the command has not been located yet, CanCommandBeLocated will be invoked without the submitted parameters.
|
||||
/// </summary>
|
||||
/// <param name="command">The command name to execute. Environment variables like PATH on windows will also be considered if the command is not an absolute path. </param>
|
||||
/// <param name="additionalCandidateCommands">Other commands that could satisfy the need for the first command. Assumption is that they all share similar calling patterns.</param>
|
||||
/// <param name="workingDirectory">The directory under which to run the command.</param>
|
||||
/// <param name="parameters">The parameters that should be passed to the command. The parameters will be space-joined.</param>
|
||||
/// <returns>Awaitable task with the result of executing the command, including exit code.</returns>
|
||||
Task<CommandLineExecutionResult> ExecuteCommand(string command, IEnumerable<string> additionalCandidateCommands = null, DirectoryInfo workingDirectory = null, params string[] parameters);
|
||||
/// <summary>
|
||||
/// Executes a command line command. If the command has not been located yet, CanCommandBeLocated will be invoked without the submitted parameters.
|
||||
/// </summary>
|
||||
/// <param name="command">The command name to execute. Environment variables like PATH on windows will also be considered if the command is not an absolute path. </param>
|
||||
/// <param name="additionalCandidateCommands">Other commands that could satisfy the need for the first command. Assumption is that they all share similar calling patterns.</param>
|
||||
/// <param name="workingDirectory">The directory under which to run the command.</param>
|
||||
/// <param name="parameters">The parameters that should be passed to the command. The parameters will be space-joined.</param>
|
||||
/// <returns>Awaitable task with the result of executing the command, including exit code.</returns>
|
||||
Task<CommandLineExecutionResult> ExecuteCommand(string command, IEnumerable<string> additionalCandidateCommands = null, DirectoryInfo workingDirectory = null, params string[] parameters);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a command line command. If the command has not been located yet, CanCommandBeLocated will be invoked without the submitted parameters.
|
||||
/// </summary>
|
||||
/// <param name="command">The command name to execute. Environment variables like PATH on windows will also be considered if the command is not an absolute path. </param>
|
||||
/// <param name="additionalCandidateCommands">Other commands that could satisfy the need for the first command. Assumption is that they all share similar calling patterns.</param>
|
||||
/// <param name="parameters">The parameters that should be passed to the command. The parameters will be space-joined.</param>
|
||||
/// <returns>Awaitable task with the result of executing the command, including exit code.</returns>
|
||||
Task<CommandLineExecutionResult> ExecuteCommand(string command, IEnumerable<string> additionalCandidateCommands = null, params string[] parameters);
|
||||
}
|
||||
|
||||
public class CommandLineExecutionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets all standard output for the process execution.
|
||||
/// </summary>
|
||||
public string StdOut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets all standard error output for the process execution.
|
||||
/// </summary>
|
||||
public string StdErr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the process exit code for the executed process.
|
||||
/// </summary>
|
||||
public int ExitCode { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Executes a command line command. If the command has not been located yet, CanCommandBeLocated will be invoked without the submitted parameters.
|
||||
/// </summary>
|
||||
/// <param name="command">The command name to execute. Environment variables like PATH on windows will also be considered if the command is not an absolute path. </param>
|
||||
/// <param name="additionalCandidateCommands">Other commands that could satisfy the need for the first command. Assumption is that they all share similar calling patterns.</param>
|
||||
/// <param name="parameters">The parameters that should be passed to the command. The parameters will be space-joined.</param>
|
||||
/// <returns>Awaitable task with the result of executing the command, including exit code.</returns>
|
||||
Task<CommandLineExecutionResult> ExecuteCommand(string command, IEnumerable<string> additionalCandidateCommands = null, params string[] parameters);
|
||||
}
|
||||
|
||||
public class CommandLineExecutionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets all standard output for the process execution.
|
||||
/// </summary>
|
||||
public string StdOut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets all standard error output for the process execution.
|
||||
/// </summary>
|
||||
public string StdErr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the process exit code for the executed process.
|
||||
/// </summary>
|
||||
public int ExitCode { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,63 +1,62 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.ComponentDetection.Contracts.TypedComponent;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Basic interface required for something to satisfy component detection.
|
||||
/// If you are writing a File based component detector, you may prefer the<see cref="FileComponentDetector" /> class.
|
||||
/// </summary>
|
||||
public interface IComponentDetector
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic interface required for something to satisfy component detection.
|
||||
/// If you are writing a File based component detector, you may prefer the<see cref="FileComponentDetector" /> class.
|
||||
/// </summary>
|
||||
public interface IComponentDetector
|
||||
{
|
||||
/// <summary>Gets the id of the detector. Should be unique to the detector using a "namespace"-like prefix for your detectors is recommended. </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of categories this detector is a member of.
|
||||
/// Names of the <see cref="DetectorClass"/> enumeration comprise some of the built in categories.
|
||||
/// If the category "All" is specified, the detector will always run.
|
||||
/// </summary>
|
||||
IEnumerable<string> Categories { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of supported component type this detector is a member of. Names of the <see cref="DetectorClass"/> enumeration comprise some of the built in component type.
|
||||
/// </summary>
|
||||
IEnumerable<ComponentType> SupportedComponentTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of the component detector.
|
||||
/// </summary>
|
||||
int Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this detector needs automatic root dependency calculation or is going to be specified as part of RegisterUsage.
|
||||
/// </summary>
|
||||
bool NeedsAutomaticRootDependencyCalculation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Run the detector and return the result set of components found.
|
||||
/// </summary>
|
||||
/// <returns> Awaitable task with result of components found. </returns>
|
||||
Task<IndividualDetectorScanResult> ExecuteDetectorAsync(ScanRequest request);
|
||||
}
|
||||
/// <summary>Gets the id of the detector. Should be unique to the detector using a "namespace"-like prefix for your detectors is recommended. </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Component detectors implementing this interface are, by default, off. This is used during composition to opt detectors out of being on by default.
|
||||
/// If opted in, they should behave like a normal detector.
|
||||
/// Gets the set of categories this detector is a member of.
|
||||
/// Names of the <see cref="DetectorClass"/> enumeration comprise some of the built in categories.
|
||||
/// If the category "All" is specified, the detector will always run.
|
||||
/// </summary>
|
||||
public interface IDefaultOffComponentDetector : IComponentDetector
|
||||
{
|
||||
}
|
||||
IEnumerable<string> Categories { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Component detectors implementing this interface are in an experimental state.
|
||||
/// The detector processing service guarantees that:
|
||||
/// They should NOT return their components as part of the scan result or be allowed to run too long (e.g. 4 min or less).
|
||||
/// They SHOULD submit telemetry about how they ran.
|
||||
/// If opted in, they should behave like a normal detector.
|
||||
/// Gets the set of supported component type this detector is a member of. Names of the <see cref="DetectorClass"/> enumeration comprise some of the built in component type.
|
||||
/// </summary>
|
||||
public interface IExperimentalDetector : IComponentDetector
|
||||
{
|
||||
}
|
||||
IEnumerable<ComponentType> SupportedComponentTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of the component detector.
|
||||
/// </summary>
|
||||
int Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this detector needs automatic root dependency calculation or is going to be specified as part of RegisterUsage.
|
||||
/// </summary>
|
||||
bool NeedsAutomaticRootDependencyCalculation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Run the detector and return the result set of components found.
|
||||
/// </summary>
|
||||
/// <returns> Awaitable task with result of components found. </returns>
|
||||
Task<IndividualDetectorScanResult> ExecuteDetectorAsync(ScanRequest request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Component detectors implementing this interface are, by default, off. This is used during composition to opt detectors out of being on by default.
|
||||
/// If opted in, they should behave like a normal detector.
|
||||
/// </summary>
|
||||
public interface IDefaultOffComponentDetector : IComponentDetector
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Component detectors implementing this interface are in an experimental state.
|
||||
/// The detector processing service guarantees that:
|
||||
/// They should NOT return their components as part of the scan result or be allowed to run too long (e.g. 4 min or less).
|
||||
/// They SHOULD submit telemetry about how they ran.
|
||||
/// If opted in, they should behave like a normal detector.
|
||||
/// </summary>
|
||||
public interface IExperimentalDetector : IComponentDetector
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,108 +1,107 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
public interface IComponentRecorder
|
||||
{
|
||||
public interface IComponentRecorder
|
||||
{
|
||||
TypedComponent.TypedComponent GetComponent(string componentId);
|
||||
TypedComponent.TypedComponent GetComponent(string componentId);
|
||||
|
||||
IEnumerable<DetectedComponent> GetDetectedComponents();
|
||||
IEnumerable<DetectedComponent> GetDetectedComponents();
|
||||
|
||||
ISingleFileComponentRecorder CreateSingleFileComponentRecorder(string location);
|
||||
ISingleFileComponentRecorder CreateSingleFileComponentRecorder(string location);
|
||||
|
||||
IReadOnlyDictionary<string, IDependencyGraph> GetDependencyGraphsByLocation();
|
||||
}
|
||||
|
||||
public interface ISingleFileComponentRecorder
|
||||
{
|
||||
string ManifestFileLocation { get; }
|
||||
|
||||
IDependencyGraph DependencyGraph { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Add or Update a component. In case that a parent componentId is specified
|
||||
/// an edge is created between those components in the dependency graph.
|
||||
/// </summary>
|
||||
/// <param name="detectedComponent">Component to add.</param>
|
||||
/// <param name="isExplicitReferencedDependency">The value define if the component was referenced manually by the user in the location where the scanning is taking place.</param>
|
||||
/// <param name="parentComponentId">Id of the parent component.</param>
|
||||
/// <param name="isDevelopmentDependency">Boolean value indicating whether or not a component is a development-time dependency. Null implies that the value is unknown.</param>
|
||||
/// <param name="dependencyScope">Enum value indicating scope of the component. </param>
|
||||
void RegisterUsage(
|
||||
DetectedComponent detectedComponent,
|
||||
bool isExplicitReferencedDependency = false,
|
||||
string parentComponentId = null,
|
||||
bool? isDevelopmentDependency = null,
|
||||
DependencyScope? dependencyScope = null);
|
||||
|
||||
DetectedComponent GetComponent(string componentId);
|
||||
|
||||
void AddAdditionalRelatedFile(string relatedFilePath);
|
||||
|
||||
IReadOnlyDictionary<string, DetectedComponent> GetDetectedComponents();
|
||||
|
||||
IComponentRecorder GetParentComponentRecorder();
|
||||
}
|
||||
|
||||
public interface IDependencyGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the componentIds that are dependencies for a given componentId.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The component id to look up dependencies for.</param>
|
||||
/// <returns>The componentIds that are dependencies for a given componentId.</returns>
|
||||
IEnumerable<string> GetDependenciesForComponent(string componentId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all componentIds that are in the dependency graph.
|
||||
/// </summary>
|
||||
/// <returns>The componentIds that are part of the dependency graph.</returns>
|
||||
IEnumerable<string> GetComponents();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a componentId is an explicitly referenced dependency.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The componentId to check.</param>
|
||||
/// <returns>True if explicitly referenced, false otherwise.</returns>
|
||||
bool IsComponentExplicitlyReferenced(string componentId);
|
||||
|
||||
HashSet<string> GetAdditionalRelatedFiles();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a componentId is registered in the graph.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The componentId to check.</param>
|
||||
/// <returns>True if registered in the graph, false otherwise.</returns>
|
||||
bool Contains(string componentId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a componentId is a development dependency, and false if it is not.
|
||||
/// Null can be returned if a detector doesn't have confidence one way or the other.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The componentId to check.</param>
|
||||
/// <returns>True if a development dependency, false if not. Null when unknown.</returns>
|
||||
bool? IsDevelopmentDependency(string componentId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns DepedencyScope for the given componentId.
|
||||
/// Null can be returned if a detector doesn't have the scope infromation.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The componentId to check.</param>
|
||||
/// <returns> DependencyScope <see cref="DependencyScope"/> for the given componentId. </returns>
|
||||
DependencyScope? GetDependencyScope(string componentId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the component IDs of all explicitly referenced components.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable of the component IDs of all explicilty referenced components.</returns>
|
||||
IEnumerable<string> GetAllExplicitlyReferencedComponents();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the set of component ids that are explicit references to the given component id.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The leaf level component to find explicit references for.</param>
|
||||
/// <returns>A collection fo all explicit references to the given component.</returns>
|
||||
ICollection<string> GetExplicitReferencedDependencyIds(string componentId);
|
||||
}
|
||||
IReadOnlyDictionary<string, IDependencyGraph> GetDependencyGraphsByLocation();
|
||||
}
|
||||
|
||||
public interface ISingleFileComponentRecorder
|
||||
{
|
||||
string ManifestFileLocation { get; }
|
||||
|
||||
IDependencyGraph DependencyGraph { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Add or Update a component. In case that a parent componentId is specified
|
||||
/// an edge is created between those components in the dependency graph.
|
||||
/// </summary>
|
||||
/// <param name="detectedComponent">Component to add.</param>
|
||||
/// <param name="isExplicitReferencedDependency">The value define if the component was referenced manually by the user in the location where the scanning is taking place.</param>
|
||||
/// <param name="parentComponentId">Id of the parent component.</param>
|
||||
/// <param name="isDevelopmentDependency">Boolean value indicating whether or not a component is a development-time dependency. Null implies that the value is unknown.</param>
|
||||
/// <param name="dependencyScope">Enum value indicating scope of the component. </param>
|
||||
void RegisterUsage(
|
||||
DetectedComponent detectedComponent,
|
||||
bool isExplicitReferencedDependency = false,
|
||||
string parentComponentId = null,
|
||||
bool? isDevelopmentDependency = null,
|
||||
DependencyScope? dependencyScope = null);
|
||||
|
||||
DetectedComponent GetComponent(string componentId);
|
||||
|
||||
void AddAdditionalRelatedFile(string relatedFilePath);
|
||||
|
||||
IReadOnlyDictionary<string, DetectedComponent> GetDetectedComponents();
|
||||
|
||||
IComponentRecorder GetParentComponentRecorder();
|
||||
}
|
||||
|
||||
public interface IDependencyGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the componentIds that are dependencies for a given componentId.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The component id to look up dependencies for.</param>
|
||||
/// <returns>The componentIds that are dependencies for a given componentId.</returns>
|
||||
IEnumerable<string> GetDependenciesForComponent(string componentId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all componentIds that are in the dependency graph.
|
||||
/// </summary>
|
||||
/// <returns>The componentIds that are part of the dependency graph.</returns>
|
||||
IEnumerable<string> GetComponents();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a componentId is an explicitly referenced dependency.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The componentId to check.</param>
|
||||
/// <returns>True if explicitly referenced, false otherwise.</returns>
|
||||
bool IsComponentExplicitlyReferenced(string componentId);
|
||||
|
||||
HashSet<string> GetAdditionalRelatedFiles();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a componentId is registered in the graph.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The componentId to check.</param>
|
||||
/// <returns>True if registered in the graph, false otherwise.</returns>
|
||||
bool Contains(string componentId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a componentId is a development dependency, and false if it is not.
|
||||
/// Null can be returned if a detector doesn't have confidence one way or the other.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The componentId to check.</param>
|
||||
/// <returns>True if a development dependency, false if not. Null when unknown.</returns>
|
||||
bool? IsDevelopmentDependency(string componentId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns DepedencyScope for the given componentId.
|
||||
/// Null can be returned if a detector doesn't have the scope infromation.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The componentId to check.</param>
|
||||
/// <returns> DependencyScope <see cref="DependencyScope"/> for the given componentId. </returns>
|
||||
DependencyScope? GetDependencyScope(string componentId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the component IDs of all explicitly referenced components.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable of the component IDs of all explicilty referenced components.</returns>
|
||||
IEnumerable<string> GetAllExplicitlyReferencedComponents();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the set of component ids that are explicit references to the given component id.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The leaf level component to find explicit references for.</param>
|
||||
/// <returns>A collection fo all explicit references to the given component.</returns>
|
||||
ICollection<string> GetExplicitReferencedDependencyIds(string componentId);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
public interface IComponentStream
|
||||
{
|
||||
public interface IComponentStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the stream object that was discovered by the provided pattern to <see cref="IComponentStreamEnumerableFactory" /> />.
|
||||
/// </summary>
|
||||
Stream Stream { get; }
|
||||
/// <summary>
|
||||
/// Gets the stream object that was discovered by the provided pattern to <see cref="IComponentStreamEnumerableFactory" /> />.
|
||||
/// </summary>
|
||||
Stream Stream { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pattern that this stream matched. Ex: If *.bar was used to match Foo.bar, this field would contain *.bar.
|
||||
/// </summary>
|
||||
string Pattern { get; }
|
||||
/// <summary>
|
||||
/// Gets the pattern that this stream matched. Ex: If *.bar was used to match Foo.bar, this field would contain *.bar.
|
||||
/// </summary>
|
||||
string Pattern { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the location for this stream. Often a file path if not in test circumstances.
|
||||
/// </summary>
|
||||
string Location { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the location for this stream. Often a file path if not in test circumstances.
|
||||
/// </summary>
|
||||
string Location { get; }
|
||||
}
|
||||
|
|
|
@ -2,30 +2,29 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
{
|
||||
public interface IComponentStreamEnumerableFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns an enumerable of <see cref="IComponentStream"/> which are representative of the contents of underlying files that matched the
|
||||
/// provided search pattern and exclusion function. Each stream is disposed on the end of a single iteration of a foreach loop.
|
||||
/// </summary>
|
||||
/// <param name="directory">The directory to search "from", e.g. the top level directory being searched.</param>
|
||||
/// <param name="searchPatterns">The patterns to use in the search.</param>
|
||||
/// <param name="directoryExclusionPredicate">Predicate which indicates which directories should be excluded.</param>
|
||||
/// <param name="recursivelyScanDirectories">Indicates whether the streams should enumerate files from sub directories.</param>
|
||||
/// <returns> Enumerable of <see cref="IComponentStream"/> files that matched the given search pattern and directory exclusion predicate.</returns>
|
||||
IEnumerable<IComponentStream> GetComponentStreams(DirectoryInfo directory, IEnumerable<string> searchPatterns, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true);
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerable of <see cref="IComponentStream"/> which are representative of the contents of underlying files that matched the
|
||||
/// provided search and exclusion functions. Each stream is disposed on the end of a single iteration of a foreach loop.
|
||||
/// </summary>
|
||||
/// <param name="directory">The directory to search "from", e.g. the top level directory being searched.</param>
|
||||
/// <param name="fileMatchingPredicate">Predicate which indicates what files should be included.</param>
|
||||
/// <param name="directoryExclusionPredicate">Predicate which indicates which directories should be excluded.</param>
|
||||
/// <param name="recursivelyScanDirectories">Indicates whether the streams should enumerate files from sub directories.</param>
|
||||
/// <returns> Enumerable of <see cref="IComponentStream"/> files that matched the given file matching predicate and directory exclusion predicate. </returns>
|
||||
IEnumerable<IComponentStream> GetComponentStreams(DirectoryInfo directory, Func<FileInfo, bool> fileMatchingPredicate, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true);
|
||||
}
|
||||
public interface IComponentStreamEnumerableFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns an enumerable of <see cref="IComponentStream"/> which are representative of the contents of underlying files that matched the
|
||||
/// provided search pattern and exclusion function. Each stream is disposed on the end of a single iteration of a foreach loop.
|
||||
/// </summary>
|
||||
/// <param name="directory">The directory to search "from", e.g. the top level directory being searched.</param>
|
||||
/// <param name="searchPatterns">The patterns to use in the search.</param>
|
||||
/// <param name="directoryExclusionPredicate">Predicate which indicates which directories should be excluded.</param>
|
||||
/// <param name="recursivelyScanDirectories">Indicates whether the streams should enumerate files from sub directories.</param>
|
||||
/// <returns> Enumerable of <see cref="IComponentStream"/> files that matched the given search pattern and directory exclusion predicate.</returns>
|
||||
IEnumerable<IComponentStream> GetComponentStreams(DirectoryInfo directory, IEnumerable<string> searchPatterns, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerable of <see cref="IComponentStream"/> which are representative of the contents of underlying files that matched the
|
||||
/// provided search and exclusion functions. Each stream is disposed on the end of a single iteration of a foreach loop.
|
||||
/// </summary>
|
||||
/// <param name="directory">The directory to search "from", e.g. the top level directory being searched.</param>
|
||||
/// <param name="fileMatchingPredicate">Predicate which indicates what files should be included.</param>
|
||||
/// <param name="directoryExclusionPredicate">Predicate which indicates which directories should be excluded.</param>
|
||||
/// <param name="recursivelyScanDirectories">Indicates whether the streams should enumerate files from sub directories.</param>
|
||||
/// <returns> Enumerable of <see cref="IComponentStream"/> files that matched the given file matching predicate and directory exclusion predicate. </returns>
|
||||
IEnumerable<IComponentStream> GetComponentStreams(DirectoryInfo directory, Func<FileInfo, bool> fileMatchingPredicate, ExcludeDirectoryPredicate directoryExclusionPredicate, bool recursivelyScanDirectories = true);
|
||||
}
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
public interface IDetectorDependencies
|
||||
{
|
||||
public interface IDetectorDependencies
|
||||
{
|
||||
ILogger Logger { get; set; }
|
||||
ILogger Logger { get; set; }
|
||||
|
||||
IComponentStreamEnumerableFactory ComponentStreamEnumerableFactory { get; set; }
|
||||
IComponentStreamEnumerableFactory ComponentStreamEnumerableFactory { get; set; }
|
||||
|
||||
IPathUtilityService PathUtilityService { get; set; }
|
||||
IPathUtilityService PathUtilityService { get; set; }
|
||||
|
||||
ICommandLineInvocationService CommandLineInvocationService { get; set; }
|
||||
ICommandLineInvocationService CommandLineInvocationService { get; set; }
|
||||
|
||||
IFileUtilityService FileUtilityService { get; set; }
|
||||
IFileUtilityService FileUtilityService { get; set; }
|
||||
|
||||
IObservableDirectoryWalkerFactory DirectoryWalkerFactory { get; set; }
|
||||
IObservableDirectoryWalkerFactory DirectoryWalkerFactory { get; set; }
|
||||
|
||||
IDockerService DockerService { get; set; }
|
||||
IDockerService DockerService { get; set; }
|
||||
|
||||
IEnvironmentVariableService EnvironmentVariableService { get; set; }
|
||||
}
|
||||
IEnvironmentVariableService EnvironmentVariableService { get; set; }
|
||||
}
|
||||
|
|
|
@ -3,20 +3,19 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.ComponentDetection.Contracts.BcdeModels;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
public interface IDockerService
|
||||
{
|
||||
public interface IDockerService
|
||||
{
|
||||
Task<bool> CanRunLinuxContainersAsync(CancellationToken cancellationToken = default);
|
||||
Task<bool> CanRunLinuxContainersAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> CanPingDockerAsync(CancellationToken cancellationToken = default);
|
||||
Task<bool> CanPingDockerAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> ImageExistsLocallyAsync(string image, CancellationToken cancellationToken = default);
|
||||
Task<bool> ImageExistsLocallyAsync(string image, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> TryPullImageAsync(string image, CancellationToken cancellationToken = default);
|
||||
Task<bool> TryPullImageAsync(string image, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<ContainerDetails> InspectImageAsync(string image, CancellationToken cancellationToken = default);
|
||||
Task<ContainerDetails> InspectImageAsync(string image, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<(string Stdout, string Stderr)> CreateAndRunContainerAsync(string image, IList<string> command, CancellationToken cancellationToken = default);
|
||||
}
|
||||
Task<(string Stdout, string Stderr)> CreateAndRunContainerAsync(string image, IList<string> command, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
public interface IEnvironmentVariableService
|
||||
{
|
||||
public interface IEnvironmentVariableService
|
||||
{
|
||||
bool DoesEnvironmentVariableExist(string name);
|
||||
bool DoesEnvironmentVariableExist(string name);
|
||||
|
||||
string GetEnvironmentVariable(string name);
|
||||
string GetEnvironmentVariable(string name);
|
||||
|
||||
bool IsEnvironmentVariableValueTrue(string name);
|
||||
}
|
||||
bool IsEnvironmentVariableValueTrue(string name);
|
||||
}
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps some common file operations for easier testability. This interface is *only used by the command line driven app*.
|
||||
/// </summary>
|
||||
public interface IFileUtilityService
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps some common file operations for easier testability. This interface is *only used by the command line driven app*.
|
||||
/// </summary>
|
||||
public interface IFileUtilityService
|
||||
{
|
||||
string ReadAllText(string filePath);
|
||||
string ReadAllText(string filePath);
|
||||
|
||||
string ReadAllText(FileInfo file);
|
||||
string ReadAllText(FileInfo file);
|
||||
|
||||
bool Exists(string fileName);
|
||||
bool Exists(string fileName);
|
||||
|
||||
Stream MakeFileStream(string fileName);
|
||||
}
|
||||
Stream MakeFileStream(string fileName);
|
||||
}
|
||||
|
|
|
@ -1,58 +1,57 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
/// <summary>Simple abstraction around console/output file logging for component detection.</summary>
|
||||
public interface ILogger
|
||||
{
|
||||
/// <summary>Simple abstraction around console/output file logging for component detection.</summary>
|
||||
public interface ILogger
|
||||
{
|
||||
/// <summary>Creates a logical separation (e.g. newline) between different log messages.</summary>
|
||||
void LogCreateLoggingGroup();
|
||||
/// <summary>Creates a logical separation (e.g. newline) between different log messages.</summary>
|
||||
void LogCreateLoggingGroup();
|
||||
|
||||
/// <summary>Logs a warning message, outputting if configured verbosity is higher than Quiet.</summary>
|
||||
/// <param name="message">The message to output.</param>
|
||||
void LogWarning(string message);
|
||||
/// <summary>Logs a warning message, outputting if configured verbosity is higher than Quiet.</summary>
|
||||
/// <param name="message">The message to output.</param>
|
||||
void LogWarning(string message);
|
||||
|
||||
/// <summary>Logs an informational message, outputting if configured verbosity is higher than Quiet.</summary>
|
||||
/// <param name="message">The message to output.</param>
|
||||
void LogInfo(string message);
|
||||
/// <summary>Logs an informational message, outputting if configured verbosity is higher than Quiet.</summary>
|
||||
/// <param name="message">The message to output.</param>
|
||||
void LogInfo(string message);
|
||||
|
||||
/// <summary>Logs a verbose message, outputting if configured verbosity is at least Verbose.</summary>
|
||||
/// <param name="message">The message to output.</param>
|
||||
void LogVerbose(string message);
|
||||
/// <summary>Logs a verbose message, outputting if configured verbosity is at least Verbose.</summary>
|
||||
/// <param name="message">The message to output.</param>
|
||||
void LogVerbose(string message);
|
||||
|
||||
/// <summary>Logs an error message, outputting for all verbosity levels.</summary>
|
||||
/// <param name="message">The message to output.</param>
|
||||
void LogError(string message);
|
||||
/// <summary>Logs an error message, outputting for all verbosity levels.</summary>
|
||||
/// <param name="message">The message to output.</param>
|
||||
void LogError(string message);
|
||||
|
||||
/// <summary>Logs a specially formatted message if a file read failed, outputting if configured verbosity is at least Verbose.</summary>
|
||||
/// <param name="filePath">The file path responsible for the file reading failure.</param>
|
||||
/// <param name="e">The exception encountered when reading a file.</param>
|
||||
void LogFailedReadingFile(string filePath, Exception e);
|
||||
/// <summary>Logs a specially formatted message if a file read failed, outputting if configured verbosity is at least Verbose.</summary>
|
||||
/// <param name="filePath">The file path responsible for the file reading failure.</param>
|
||||
/// <param name="e">The exception encountered when reading a file.</param>
|
||||
void LogFailedReadingFile(string filePath, Exception e);
|
||||
|
||||
/// <summary>Logs a specially formatted message if an exception has occurred.</summary>
|
||||
/// <param name="e">The exception to log the occurance of.</param>
|
||||
/// <param name="isError">Whether or not the exception represents a true error case (e.g. unexpected) vs. expected.</param>
|
||||
/// <param name="printException">Indicate if the exception is going to be fully printed.</param>
|
||||
/// <param name="callerMemberName">Implicity populated arg, provides the member name of the calling function to the log message.</param>
|
||||
/// <param name="callerLineNumber">Implicitly populated arg, provides calling line number.</param>
|
||||
void LogException(
|
||||
Exception e,
|
||||
bool isError,
|
||||
bool printException = false,
|
||||
[CallerMemberName] string callerMemberName = "",
|
||||
[CallerLineNumber] int callerLineNumber = 0);
|
||||
/// <summary>Logs a specially formatted message if an exception has occurred.</summary>
|
||||
/// <param name="e">The exception to log the occurance of.</param>
|
||||
/// <param name="isError">Whether or not the exception represents a true error case (e.g. unexpected) vs. expected.</param>
|
||||
/// <param name="printException">Indicate if the exception is going to be fully printed.</param>
|
||||
/// <param name="callerMemberName">Implicity populated arg, provides the member name of the calling function to the log message.</param>
|
||||
/// <param name="callerLineNumber">Implicitly populated arg, provides calling line number.</param>
|
||||
void LogException(
|
||||
Exception e,
|
||||
bool isError,
|
||||
bool printException = false,
|
||||
[CallerMemberName] string callerMemberName = "",
|
||||
[CallerLineNumber] int callerLineNumber = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Log a warning to the build console, adding it to the build summary and turning the build yellow.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to display alongside the warning.</param>
|
||||
void LogBuildWarning(string message);
|
||||
/// <summary>
|
||||
/// Log a warning to the build console, adding it to the build summary and turning the build yellow.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to display alongside the warning.</param>
|
||||
void LogBuildWarning(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Log an error to the build console, adding it to the build summary and turning the build red.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to display alongside the warning.</param>
|
||||
void LogBuildError(string message);
|
||||
}
|
||||
/// <summary>
|
||||
/// Log an error to the build console, adding it to the build summary and turning the build red.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to display alongside the warning.</param>
|
||||
void LogBuildError(string message);
|
||||
}
|
||||
|
|
|
@ -3,14 +3,13 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using Microsoft.ComponentDetection.Contracts.Internal;
|
||||
|
||||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
public delegate bool ExcludeDirectoryPredicate(ReadOnlySpan<char> nameOfDirectoryToConsider, ReadOnlySpan<char> pathOfParentOfDirectoryToConsider);
|
||||
|
||||
public interface IObservableDirectoryWalkerFactory
|
||||
{
|
||||
public delegate bool ExcludeDirectoryPredicate(ReadOnlySpan<char> nameOfDirectoryToConsider, ReadOnlySpan<char> pathOfParentOfDirectoryToConsider);
|
||||
void Initialize(DirectoryInfo root, ExcludeDirectoryPredicate directoryExclusionPredicate, int count, IEnumerable<string> filePatterns = null);
|
||||
|
||||
public interface IObservableDirectoryWalkerFactory
|
||||
{
|
||||
void Initialize(DirectoryInfo root, ExcludeDirectoryPredicate directoryExclusionPredicate, int count, IEnumerable<string> filePatterns = null);
|
||||
|
||||
IObservable<ProcessRequest> GetFilteredComponentStreamObservable(DirectoryInfo root, IEnumerable<string> patterns, IComponentRecorder componentRecorder);
|
||||
}
|
||||
IObservable<ProcessRequest> GetFilteredComponentStreamObservable(DirectoryInfo root, IEnumerable<string> patterns, IComponentRecorder componentRecorder);
|
||||
}
|
||||
|
|
|
@ -1,33 +1,32 @@
|
|||
namespace Microsoft.ComponentDetection.Contracts
|
||||
namespace Microsoft.ComponentDetection.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps some common folder operations, shared across command line app and service.
|
||||
/// </summary>
|
||||
public interface IPathUtilityService
|
||||
{
|
||||
string GetParentDirectory(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Wraps some common folder operations, shared across command line app and service.
|
||||
/// Given a path, resolve the underlying path, traversing any symlinks (man 2 lstat :D ).
|
||||
/// </summary>
|
||||
public interface IPathUtilityService
|
||||
{
|
||||
string GetParentDirectory(string path);
|
||||
/// <param name="path">Path that needs to be resolved. </param>
|
||||
/// <returns> Returns a string of the underlying path. </returns>
|
||||
string ResolvePhysicalPath(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Given a path, resolve the underlying path, traversing any symlinks (man 2 lstat :D ).
|
||||
/// </summary>
|
||||
/// <param name="path">Path that needs to be resolved. </param>
|
||||
/// <returns> Returns a string of the underlying path. </returns>
|
||||
string ResolvePhysicalPath(string path);
|
||||
/// <summary>
|
||||
/// Returns true when the below file path exists under the above file path.
|
||||
/// </summary>
|
||||
/// <param name="aboveFilePath">The top file path. </param>
|
||||
/// <param name="belowFilePath">The file path to find within the top file path. </param>
|
||||
/// <returns> Return a bool. True, if below file path is found under above file path, otherwise false. </returns>
|
||||
bool IsFileBelowAnother(string aboveFilePath, string belowFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the below file path exists under the above file path.
|
||||
/// </summary>
|
||||
/// <param name="aboveFilePath">The top file path. </param>
|
||||
/// <param name="belowFilePath">The file path to find within the top file path. </param>
|
||||
/// <returns> Return a bool. True, if below file path is found under above file path, otherwise false. </returns>
|
||||
bool IsFileBelowAnother(string aboveFilePath, string belowFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if file name matches pattern.
|
||||
/// </summary>
|
||||
/// <param name="searchPattern">Search pattern.</param>
|
||||
/// <param name="fileName">File name without directory.</param>
|
||||
/// <returns>Returns true if file name matches a pattern, otherwise false. </returns>
|
||||
bool MatchesPattern(string searchPattern, string fileName);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns true if file name matches pattern.
|
||||
/// </summary>
|
||||
/// <param name="searchPattern">Search pattern.</param>
|
||||
/// <param name="fileName">File name without directory.</param>
|
||||
/// <returns>Returns true if file name matches a pattern, otherwise false. </returns>
|
||||
bool MatchesPattern(string searchPattern, string fileName);
|
||||
}
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче