Skip to content

Commit 477409a

Browse files
authored
Users/jcfiorenzano23/go 117 experiment (#1359)
* Created experimental go detector * Created experiment configuration * Added unit tests * Registered the interfaces * Resolved issue resolving ILogger * Updated detector version and using the env var to determine if the graph should run
1 parent a209393 commit 477409a

File tree

15 files changed

+968
-380
lines changed

15 files changed

+968
-380
lines changed

src/Microsoft.ComponentDetection.Contracts/FileComponentDetector.cs

+17
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,28 @@ private async Task<IndividualDetectorScanResult> ProcessAsync(
139139
};
140140
}
141141

142+
/// <summary>
143+
/// Auxliary method executed before the actual scanning of a given file takes place.
144+
/// This method can be used to modify or create new ProcessRequests that later will
145+
/// be used by the scanner to extract the components.
146+
/// </summary>
147+
/// <param name="processRequests">Process requests that triggered a given scanner.</param>
148+
/// <param name="detectorArgs">Arguments used by the detector.</param>
149+
/// <param name="cancellationToken">Cancellation token.</param>
150+
/// <returns>Returns the process requests that will be used by the scanner.</returns>
142151
protected virtual Task<IObservable<ProcessRequest>> OnPrepareDetectionAsync(IObservable<ProcessRequest> processRequests, IDictionary<string, string> detectorArgs, CancellationToken cancellationToken = default)
143152
{
144153
return Task.FromResult(processRequests);
145154
}
146155

156+
/// <summary>
157+
/// This method called for each individual process request.
158+
/// It is responsible for the actual scanning of the components.
159+
/// </summary>
160+
/// <param name="processRequest">Process request that contains information to execute the component scan.</param>
161+
/// <param name="detectorArgs">Arguments for the detector.</param>
162+
/// <param name="cancellationToken">Cancellation token.</param>
163+
/// <returns>A task representing the asynchronous operation.</returns>
147164
protected abstract Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs, CancellationToken cancellationToken = default);
148165

149166
protected virtual Task OnDetectionFinishedAsync()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
namespace Microsoft.ComponentDetection.Detectors.Go;
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Reactive.Linq;
8+
using System.Text.RegularExpressions;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Microsoft.ComponentDetection.Common;
12+
using Microsoft.ComponentDetection.Common.Telemetry.Records;
13+
using Microsoft.ComponentDetection.Contracts;
14+
using Microsoft.ComponentDetection.Contracts.Internal;
15+
using Microsoft.ComponentDetection.Contracts.TypedComponent;
16+
using Microsoft.Extensions.Logging;
17+
18+
public class Go117ComponentDetector : FileComponentDetector, IExperimentalDetector
19+
{
20+
private readonly HashSet<string> projectRoots = [];
21+
22+
private readonly ICommandLineInvocationService commandLineInvocationService;
23+
private readonly IGoParserFactory goParserFactory;
24+
private readonly IEnvironmentVariableService envVarService;
25+
26+
public Go117ComponentDetector(
27+
IComponentStreamEnumerableFactory componentStreamEnumerableFactory,
28+
IObservableDirectoryWalkerFactory walkerFactory,
29+
ICommandLineInvocationService commandLineInvocationService,
30+
IEnvironmentVariableService envVarService,
31+
ILogger<GoComponentDetector> logger,
32+
IFileUtilityService fileUtilityService,
33+
IGoParserFactory goParserFactory)
34+
{
35+
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory;
36+
this.Scanner = walkerFactory;
37+
this.commandLineInvocationService = commandLineInvocationService;
38+
this.Logger = logger;
39+
this.goParserFactory = goParserFactory;
40+
this.envVarService = envVarService;
41+
}
42+
43+
public override string Id => "Go117";
44+
45+
public override IEnumerable<string> Categories => [Enum.GetName(typeof(DetectorClass), DetectorClass.GoMod)];
46+
47+
public override IList<string> SearchPatterns { get; } = ["go.mod", "go.sum"];
48+
49+
public override IEnumerable<ComponentType> SupportedComponentTypes { get; } = [ComponentType.Go];
50+
51+
public override int Version => 1;
52+
53+
protected override Task<IObservable<ProcessRequest>> OnPrepareDetectionAsync(
54+
IObservable<ProcessRequest> processRequests,
55+
IDictionary<string, string> detectorArgs,
56+
CancellationToken cancellationToken = default)
57+
{
58+
var goModProcessRequests = processRequests.Where(processRequest =>
59+
{
60+
if (Path.GetFileName(processRequest.ComponentStream.Location) != "go.sum")
61+
{
62+
return true;
63+
}
64+
65+
var goModFile = this.FindAdjacentGoModComponentStreams(processRequest).FirstOrDefault();
66+
67+
try
68+
{
69+
if (goModFile == null)
70+
{
71+
this.Logger.LogDebug(
72+
"go.sum file found without an adjacent go.mod file. Location: {Location}",
73+
processRequest.ComponentStream.Location);
74+
75+
return true;
76+
}
77+
78+
return GoDetectorUtils.ShouldRemoveGoSumFromDetection(goSumFilePath: processRequest.ComponentStream.Location, goModFile, this.Logger);
79+
}
80+
finally
81+
{
82+
goModFile?.Stream.Dispose();
83+
}
84+
});
85+
86+
return Task.FromResult(goModProcessRequests);
87+
}
88+
89+
protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs, CancellationToken cancellationToken = default)
90+
{
91+
var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder;
92+
var file = processRequest.ComponentStream;
93+
94+
var projectRootDirectory = Directory.GetParent(file.Location);
95+
if (this.projectRoots.Any(path => projectRootDirectory.FullName.StartsWith(path)))
96+
{
97+
return;
98+
}
99+
100+
var record = new GoGraphTelemetryRecord();
101+
var fileExtension = Path.GetExtension(file.Location).ToUpperInvariant();
102+
switch (fileExtension)
103+
{
104+
case ".MOD":
105+
{
106+
this.Logger.LogDebug("Found Go.mod: {Location}", file.Location);
107+
await this.goParserFactory.CreateParser(GoParserType.GoMod, this.Logger).ParseAsync(singleFileComponentRecorder, file, record);
108+
109+
if (await this.ShouldRunGoGraphAsync())
110+
{
111+
await GoDependencyGraphUtility.GenerateAndPopulateDependencyGraphAsync(
112+
this.commandLineInvocationService,
113+
this.Logger,
114+
singleFileComponentRecorder,
115+
projectRootDirectory.FullName,
116+
record,
117+
cancellationToken);
118+
}
119+
120+
break;
121+
}
122+
123+
case ".SUM":
124+
{
125+
this.Logger.LogDebug("Found Go.sum: {Location}", file.Location);
126+
await this.goParserFactory.CreateParser(GoParserType.GoSum, this.Logger).ParseAsync(singleFileComponentRecorder, file, record);
127+
break;
128+
}
129+
130+
default:
131+
{
132+
throw new InvalidOperationException("Unexpected file type detected in go detector");
133+
}
134+
}
135+
}
136+
137+
private bool IsGoCliManuallyDisabled()
138+
{
139+
return this.envVarService.IsEnvironmentVariableValueTrue("DisableGoCliScan");
140+
}
141+
142+
private async Task<bool> ShouldRunGoGraphAsync()
143+
{
144+
if (this.IsGoCliManuallyDisabled())
145+
{
146+
return false;
147+
}
148+
149+
var goVersion = await this.GetGoVersionAsync();
150+
if (goVersion == null)
151+
{
152+
return false;
153+
}
154+
155+
return goVersion >= new Version(1, 11);
156+
}
157+
158+
private async Task<Version> GetGoVersionAsync()
159+
{
160+
var processExecution = await this.commandLineInvocationService.ExecuteCommandAsync("go", null, null, cancellationToken: default, new List<string> { "version" }.ToArray());
161+
if (processExecution.ExitCode != 0)
162+
{
163+
return null;
164+
}
165+
166+
// Define the regular expression pattern to match the version number
167+
var versionPattern = @"go version go(\d+\.\d+\.\d+)";
168+
var match = Regex.Match(processExecution.StdOut, versionPattern);
169+
170+
if (match.Success)
171+
{
172+
// Extract the version number from the match
173+
var versionStr = match.Groups[1].Value;
174+
return new Version(versionStr);
175+
}
176+
177+
return null;
178+
}
179+
180+
private IEnumerable<ComponentStream> FindAdjacentGoModComponentStreams(ProcessRequest processRequest) =>
181+
this.ComponentStreamEnumerableFactory.GetComponentStreams(
182+
new FileInfo(processRequest.ComponentStream.Location).Directory,
183+
["go.mod"],
184+
(_, _) => false,
185+
false)
186+
.Select(x =>
187+
{
188+
// The stream will be disposed at the end of this method, so we need to copy it to a new stream.
189+
var memoryStream = new MemoryStream();
190+
191+
x.Stream.CopyTo(memoryStream);
192+
memoryStream.Position = 0;
193+
194+
return new ComponentStream
195+
{
196+
Stream = memoryStream,
197+
Location = x.Location,
198+
Pattern = x.Pattern,
199+
};
200+
});
201+
}

0 commit comments

Comments
 (0)