forked from microsoft/component-detection
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathOrchestrator.cs
309 lines (262 loc) · 13.1 KB
/
Orchestrator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
using System;
using System.Collections.Generic;
using System.Composition;
using System.Composition.Hosting;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
using Microsoft.ComponentDetection.Common;
using Microsoft.ComponentDetection.Common.Exceptions;
using Microsoft.ComponentDetection.Common.Telemetry;
using Microsoft.ComponentDetection.Common.Telemetry.Records;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.BcdeModels;
using Microsoft.ComponentDetection.Orchestrator.ArgumentSets;
using Microsoft.ComponentDetection.Orchestrator.Services;
using Microsoft.ComponentDetection.Orchestrator.Services.GraphTranslation;
using Newtonsoft.Json;
namespace Microsoft.ComponentDetection.Orchestrator
{
public class Orchestrator
{
private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
public ScanResult Load(string[] args)
{
ArgumentHelper argumentHelper = new ArgumentHelper { ArgumentSets = new[] { new BaseArguments() } };
BaseArguments baseArguments = null;
var parserResult = argumentHelper.ParseArguments<BaseArguments>(args, true);
parserResult.WithParsed(x => baseArguments = x);
if (parserResult.Tag == ParserResultType.NotParsed)
{
// Blank args for this part of the loader, all things are optional and default to false / empty / null
baseArguments = new BaseArguments();
}
IEnumerable<string> additionalDITargets = baseArguments.AdditionalDITargets ?? Enumerable.Empty<string>();
// Load all types from Common (where Logger lives) and our executing assembly.
var configuration = new ContainerConfiguration()
.WithAssembly(typeof(Logger).Assembly)
.WithAssembly(Assembly.GetExecutingAssembly());
foreach (var assemblyPath in additionalDITargets)
{
var assemblies = Assembly.LoadFrom(assemblyPath);
AddAssembliesWithType<ITelemetryService>(assemblies, configuration);
AddAssembliesWithType<IGraphTranslationService>(assemblies, configuration);
}
using (var container = configuration.CreateContainer())
{
container.SatisfyImports(this);
container.SatisfyImports(TelemetryRelay.Instance);
}
TelemetryRelay.Instance.SetTelemetryMode(baseArguments.DebugTelemetry ? TelemetryMode.Debug : TelemetryMode.Production);
bool shouldFailureBeSuppressed = false;
// Don't use the using pattern here so we can take care not to clobber the stack
var returnResult = BcdeExecutionTelemetryRecord.Track(
(record) =>
{
var executionResult = HandleCommand(args, record);
if (executionResult.ResultCode == ProcessingResultCode.PartialSuccess)
{
shouldFailureBeSuppressed = true;
record.HiddenExitCode = (int)executionResult.ResultCode;
}
return executionResult;
}, true);
// The order of these things is a little weird, but done this way mostly to prevent any of the logic inside if blocks from being duplicated
if (shouldFailureBeSuppressed)
{
Logger.LogInfo("The scan had some detections complete while others encountered errors. The log file should indicate any issues that happened during the scan.");
}
if (returnResult.ResultCode == ProcessingResultCode.TimeoutError)
{
// If we have a timeout we need to tear the run down as a CYA -- this expected to fix the problem of not responding detection runs (e.g. really long runs that don't terminate when the timeout is reached).
// Current suspicion is that we're able to get to this point in the code without child processes cleanly cleaned up.
// Observation also shows that doing this is terminating the process significantly more quickly in local executions.
Environment.Exit(shouldFailureBeSuppressed ? 0 : (int)returnResult.ResultCode);
}
if (shouldFailureBeSuppressed)
{
returnResult.ResultCode = ProcessingResultCode.Success;
}
// We should not have input errors at this point, return it as an Error
if (returnResult.ResultCode == ProcessingResultCode.InputError)
{
returnResult.ResultCode = ProcessingResultCode.Error;
}
return returnResult;
}
private static void AddAssembliesWithType<T>(Assembly assembly, ContainerConfiguration containerConfiguration)
{
assembly.GetTypes()
.Where(x => typeof(T).IsAssignableFrom(x)).ToList()
.ForEach(service => containerConfiguration = containerConfiguration.WithPart(service));
}
[ImportMany]
private static IEnumerable<IArgumentHandlingService> ArgumentHandlers { get; set; }
[Import]
private static Logger Logger { get; set; }
[Import]
private static FileWritingService FileWritingService { get; set; }
[Import]
private static IArgumentHelper ArgumentHelper { get; set; }
public ScanResult HandleCommand(string[] args, BcdeExecutionTelemetryRecord telemetryRecord)
{
var scanResult = new ScanResult()
{
ResultCode = ProcessingResultCode.Error,
};
var parsedArguments = ArgumentHelper.ParseArguments(args)
.WithParsed<IScanArguments>(argumentSet =>
{
CommandLineArgumentsExporter.ArgumentsForDelayedInjection = argumentSet;
// Don't set production telemetry if we are running the build task in DevFabric. 0.36.0 is set in the task.json for the build task in development, but is calculated during deployment for production.
TelemetryConstants.CorrelationId = argumentSet.CorrelationId;
telemetryRecord.Command = GetVerb(argumentSet);
scanResult = SafelyExecute(telemetryRecord, () =>
{
GenerateEnvironmentSpecificTelemetry(telemetryRecord);
telemetryRecord.Arguments = JsonConvert.SerializeObject(argumentSet);
FileWritingService.Init(argumentSet.Output);
Logger.Init(argumentSet.Verbosity);
Logger.LogInfo($"Run correlation id: {TelemetryConstants.CorrelationId.ToString()}");
return Dispatch(argumentSet, CancellationToken.None).GetAwaiter().GetResult();
});
})
.WithNotParsed(errors =>
{
if (errors.Any(e => e is HelpVerbRequestedError))
{
telemetryRecord.Command = "help";
scanResult.ResultCode = ProcessingResultCode.Success;
}
});
if (parsedArguments.Tag == ParserResultType.NotParsed)
{
// If the parsing failed, we already outputted an error.
// so just quit.
return scanResult;
}
telemetryRecord.ExitCode = (int)scanResult.ResultCode;
return scanResult;
}
private void GenerateEnvironmentSpecificTelemetry(BcdeExecutionTelemetryRecord telemetryRecord)
{
telemetryRecord.AgentOSDescription = RuntimeInformation.OSDescription;
if (IsLinux && RuntimeInformation.OSDescription.Contains("Ubuntu", StringComparison.InvariantCultureIgnoreCase))
{
const string LibSslDetailsKey = "LibSslDetailsKey";
var agentOSMeaningfulDetails = new Dictionary<string, string> { { LibSslDetailsKey, "FailedToFetch" } };
var taskTimeout = TimeSpan.FromSeconds(20);
try
{
var getLibSslPackages = Task.Run(() =>
{
var startInfo = new ProcessStartInfo("apt", "list --installed") { RedirectStandardOutput = true };
Process process = new Process { StartInfo = startInfo };
process.Start();
string aptListResult = null;
var task = Task.Run(() => aptListResult = process.StandardOutput.ReadToEnd());
task.Wait();
process.WaitForExit();
return string.Join(Environment.NewLine, aptListResult.Split(Environment.NewLine).Where(x => x.Contains("libssl")));
});
if (!getLibSslPackages.Wait(taskTimeout))
{
throw new TimeoutException($"The execution did not complete in the alotted time ({taskTimeout} seconds) and has been terminated prior to completion");
}
agentOSMeaningfulDetails[LibSslDetailsKey] = getLibSslPackages.GetAwaiter().GetResult();
}
catch (Exception ex)
{
agentOSMeaningfulDetails[LibSslDetailsKey] += Environment.NewLine + ex.ToString();
}
finally
{
telemetryRecord.AgentOSMeaningfulDetails = JsonConvert.SerializeObject(agentOSMeaningfulDetails);
}
}
}
private string GetVerb(IScanArguments argumentSet)
{
var verbAttribute = argumentSet.GetType().GetCustomAttribute<VerbAttribute>();
return verbAttribute.Name;
}
private async Task<ScanResult> Dispatch(IScanArguments arguments, CancellationToken cancellation)
{
var scanResult = new ScanResult()
{
ResultCode = ProcessingResultCode.Error,
};
if (ArgumentHandlers == null)
{
Logger.LogError("No argument handling services were registered.");
return scanResult;
}
foreach (var handler in ArgumentHandlers)
{
if (handler.CanHandle(arguments))
{
try
{
var timeout = arguments.Timeout == 0 ? TimeSpan.FromMilliseconds(-1) : TimeSpan.FromSeconds(arguments.Timeout);
scanResult = await AsyncExecution.ExecuteWithTimeoutAsync(() => handler.Handle(arguments), timeout, cancellation);
}
catch (TimeoutException timeoutException)
{
Logger.LogError(timeoutException.Message);
scanResult.ResultCode = ProcessingResultCode.TimeoutError;
}
return scanResult;
}
}
Logger.LogError("No handlers for the provided Argument Set were found.");
return scanResult;
}
private ScanResult SafelyExecute(BcdeExecutionTelemetryRecord record, Func<ScanResult> wrappedInvocation)
{
try
{
return wrappedInvocation();
}
catch (Exception ae)
{
var result = new ScanResult()
{
ResultCode = ProcessingResultCode.Error,
};
var e = ae.GetBaseException();
if (e is InvalidUserInputException)
{
Logger.LogError($"Something bad happened, is everything configured correctly?");
Logger.LogException(e, isError: true, printException: true);
record.ErrorMessage = e.ToString();
result.ResultCode = ProcessingResultCode.InputError;
return result;
}
else
{
// On an exception, return error to dotnet core
Logger.LogError($"There was an unexpected error: ");
Logger.LogException(e, isError: true);
StringBuilder errorMessage = new StringBuilder();
errorMessage.AppendLine(e.ToString());
if (e is ReflectionTypeLoadException refEx && refEx.LoaderExceptions != null)
{
foreach (var loaderException in refEx.LoaderExceptions)
{
var loaderExceptionString = loaderException.ToString();
Logger.LogError(loaderExceptionString);
errorMessage.AppendLine(loaderExceptionString);
}
}
record.ErrorMessage = errorMessage.ToString();
return result;
}
}
}
}
}