diff --git a/scripts/Projects.psm1 b/scripts/Projects.psm1 index 1f5e2dffb..cd71d6b46 100644 --- a/scripts/Projects.psm1 +++ b/scripts/Projects.psm1 @@ -13,7 +13,7 @@ $Frameworks = @{} $Frameworks.NetFx = @("net472") # Frameworks for which we build libraries. -$Frameworks.Library = @("netstandard2.0") + $Frameworks.NetFx +$Frameworks.Library = @("netstandard2.0") + @("netstandard2.1") + $Frameworks.NetFx # Frameworks for which we build applications. $Frameworks.Application = @("netcoreapp3.1") + $Frameworks.NetFx diff --git a/src/Sarif.Multitool.Library/KustoCommand.cs b/src/Sarif.Multitool.Library/KustoCommand.cs new file mode 100644 index 000000000..cf856021f --- /dev/null +++ b/src/Sarif.Multitool.Library/KustoCommand.cs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; + +using Kusto.Data; +using Kusto.Data.Common; +using Kusto.Data.Net.Client; + +using Microsoft.CodeAnalysis.Sarif.Driver; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Multitool +{ + public class KustoCommand : CommandBase + { + private ICslQueryProvider _kustoClient; + private KustoOptions _options; + + public int Run(KustoOptions options) + { + try + { + if (!options.Validate()) + { + return FAILURE; + } + + _options = options; + + InitializeKustoClient(); + + (List, List) sarifResults = RetrieveResultsFromKusto(); + var sarifLog = new SarifLog + { + Runs = new[] + { + new Run + { + Tool = new Tool + { + Driver = new ToolComponent + { + Name = "spam", + Rules = sarifResults.Item2, + } + }, + Results = sarifResults.Item1, + } + } + }; + + WriteSarifFile(FileSystem, sarifLog, options.OutputFilePath, options.Minify); + } + catch (Exception ex) + { + Console.WriteLine(ex); + return FAILURE; + } + finally + { + _kustoClient?.Dispose(); + } + return SUCCESS; + } + + private void InitializeKustoClient() + { + KustoConnectionStringBuilder connection = new KustoConnectionStringBuilder(_options.HostAddress) + .WithAadApplicationKeyAuthentication( + Environment.GetEnvironmentVariable("AppClientId"), + Environment.GetEnvironmentVariable("AppSecret"), + Environment.GetEnvironmentVariable("AuthorityId")); + + _kustoClient = KustoClientFactory.CreateCslQueryProvider(connection); + } + + private (List, List) RetrieveResultsFromKusto() + { + Dictionary dataReaderIndex = new Dictionary(); + var results = new List(); + var rules = new Dictionary(); + + using IDataReader dataReader = _kustoClient.ExecuteQuery( + _options.Database, + _options.Query, + new ClientRequestProperties { ClientRequestId = Guid.NewGuid().ToString() }); + while (dataReader.Read()) + { + try + { + string itemPathUri = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "ItemPathUri")); + string organizationName = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "OrganizationName")); + string projectName = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "ProjectName")); + string repositoryName = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "RepositoryName")); + string regionSnippet = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "RegionSnippet")); + string validationFingerprint = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "ValidationFingerprint")); + string globalFingerprint = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "GlobalFingerprint")); + string ruleId = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "RuleId")); + string ruleName = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "RuleName")); + int regionStartLine = dataReader.GetInt32(GetIndex(dataReader, dataReaderIndex, "RegionStartLine")); + int regionEndLine = dataReader.GetInt32(GetIndex(dataReader, dataReaderIndex, "RegionEndLine")); + int regionStartColumn = dataReader.GetInt32(GetIndex(dataReader, dataReaderIndex, "RegionStartColumn")); + int regionEndColumn = dataReader.GetInt32(GetIndex(dataReader, dataReaderIndex, "RegionEndColumn")); + int regionCharOffset = dataReader.GetInt32(GetIndex(dataReader, dataReaderIndex, "RegionCharOffset")); + int regionCharLength = dataReader.GetInt32(GetIndex(dataReader, dataReaderIndex, "RegionCharLength")); + string resultKind = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "ResultKind")); + string level = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "Level")); + //string resultMessageText = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "ResultMessageText")); + string result = dataReader.GetString(GetIndex(dataReader, dataReaderIndex, "Result")); + + Result resultObj = JsonConvert.DeserializeObject(result); + // Removing this, because the ruleIndex might not be in the correct place. + resultObj.RuleIndex = -1; + resultObj.Level = (FailureLevel)Enum.Parse(typeof(FailureLevel), level); + resultObj.Kind = (ResultKind)Enum.Parse(typeof(ResultKind), resultKind); + resultObj.Locations.Add(new Location + { + PhysicalLocation = new PhysicalLocation + { + Region = new Region + { + CharLength = regionCharLength, + CharOffset = regionCharOffset, + StartColumn = regionStartColumn, + StartLine = regionStartLine, + EndColumn = regionEndColumn, + EndLine = regionEndLine, + Snippet = new ArtifactContent + { + Text = regionSnippet + } + }, + ArtifactLocation = new ArtifactLocation + { + Uri = new Uri(itemPathUri) + } + }, + }); + resultObj.SetProperty("organizationName", organizationName); + resultObj.SetProperty("projectName", projectName); + resultObj.SetProperty("repositoryName", repositoryName); + resultObj.Fingerprints.Add("ValidationFingerprint", validationFingerprint); + resultObj.Fingerprints.Add("GlobalFingerprint", globalFingerprint); + + if (!rules.ContainsKey(ruleId)) + { + rules.Add(ruleId, new ReportingDescriptor + { + Id = ruleId, + Name = ruleName, + }); + } + + results.Add(resultObj); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + + return (results, rules.Values.ToList()); + } + + private static int GetIndex(IDataReader dataReader, Dictionary dataReaderIndex, string key) + { + if (!dataReaderIndex.TryGetValue(key, out int index)) + { + dataReaderIndex[key] = index = dataReader.GetOrdinal(key); + } + + return index; + } + } +} diff --git a/src/Sarif.Multitool.Library/KustoOptions.cs b/src/Sarif.Multitool.Library/KustoOptions.cs new file mode 100644 index 000000000..d859fb3f9 --- /dev/null +++ b/src/Sarif.Multitool.Library/KustoOptions.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +using CommandLine; + +using Microsoft.CodeAnalysis.Sarif.Driver; + +namespace Microsoft.CodeAnalysis.Sarif.Multitool +{ + [Verb("kusto", HelpText = "Export a SARIF file from a kusto query.")] + public class KustoOptions : CommonOptionsBase + { + [Value( + 0, + HelpText = "Output path for exported SARIF", + Required = true)] + public string OutputFilePath { get; set; } + + [Option( + "host-address", + HelpText = "The host address from where we will fetch the data.", + Required = true)] + public string HostAddress { get; set; } + + [Option( + "database", + HelpText = "The database that we will connect.", + Required = true)] + public string Database { get; set; } + + [Option( + "query", + HelpText = "The query that will be used to generate the SARIF.", + Required = true)] + public string Query { get; set; } + + public bool Validate() + { + string appClientId = Environment.GetEnvironmentVariable("AppClientId"); + string appSecret = Environment.GetEnvironmentVariable("AppSecret"); + string authorityId = Environment.GetEnvironmentVariable("AuthorityId"); + + if (string.IsNullOrEmpty(appClientId) || + string.IsNullOrEmpty(appSecret) || + string.IsNullOrEmpty(authorityId)) + { + return false; + } + + return true; + } + } +} diff --git a/src/Sarif.Multitool.Library/Sarif.Multitool.Library.csproj b/src/Sarif.Multitool.Library/Sarif.Multitool.Library.csproj index 368f1879b..e45912ffe 100644 --- a/src/Sarif.Multitool.Library/Sarif.Multitool.Library.csproj +++ b/src/Sarif.Multitool.Library/Sarif.Multitool.Library.csproj @@ -8,16 +8,17 @@ - netstandard2.0 + netstandard2.1 $(TargetFrameworks);net472 Microsoft.CodeAnalysis.Sarif.Multitool - + - + + @@ -49,7 +50,7 @@ - + @@ -61,5 +62,4 @@ - - + \ No newline at end of file diff --git a/src/Sarif.Multitool/Program.cs b/src/Sarif.Multitool/Program.cs index 1f41c72c1..3bb28586b 100644 --- a/src/Sarif.Multitool/Program.cs +++ b/src/Sarif.Multitool/Program.cs @@ -30,6 +30,7 @@ public static int Main(string[] args) ExportValidationConfigurationOptions, ExportValidationRulesMetadataOptions, FileWorkItemsOptions, + KustoOptions, ResultMatchingOptions, MergeOptions, PageOptions, @@ -63,6 +64,7 @@ public static int Main(string[] args) (ExportValidationConfigurationOptions options) => new ExportValidationConfigurationCommand().Run(options), (ExportValidationRulesMetadataOptions options) => new ExportValidationRulesMetadataCommand().Run(options), (FileWorkItemsOptions fileWorkItemsOptions) => new FileWorkItemsCommand().Run(fileWorkItemsOptions), + (KustoOptions options) => new KustoCommand().Run(options), (ResultMatchingOptions baselineOptions) => new ResultMatchingCommand().Run(baselineOptions), (MergeOptions mergeOptions) => new MergeCommand().Run(mergeOptions), (PageOptions pageOptions) => new PageCommand().Run(pageOptions),