refactor: use file scoped namespaces (#398)

This commit is contained in:
Jamie Magee 2023-01-05 09:10:56 -08:00 коммит произвёл GitHub
Родитель fee6fb8f1e
Коммит 0f3fa8a844
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
314 изменённых файлов: 22557 добавлений и 22873 удалений

Просмотреть файл

@ -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);
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше