diff --git a/src/ReleaseHistory.md b/src/ReleaseHistory.md index 0bc8ef08d..1d0a6b03c 100644 --- a/src/ReleaseHistory.md +++ b/src/ReleaseHistory.md @@ -11,6 +11,7 @@ * FEATURE: The Multitool `query` command can now evaluate properties in the result and rule property bags, for example `sarif query "properties.confidence:f > 0.95 AND rule.properties.category == 'security'"` * FEATURE: The validation rule `SARIF1004.ExpressUriBaseIdsCorrectly` now verifies that if an `artifactLocation.uri` is a relative reference, it does not begin with a slash. [#2090](https://github.com/microsoft/sarif-sdk/issues/2090) * BUGFIX: GitHub policy should not turn off any note level rules. [#2089](https://github.com/microsoft/sarif-sdk/issues/2089) +* FEATURE: Add `apply-policy` command to Multitool. [#2118](https://github.com/microsoft/sarif-sdk/pull/2118) ## **v2.3.6** [Sdk](https://www.nuget.org/packages/Sarif.Sdk/2.3.6) | [Driver](https://www.nuget.org/packages/Sarif.Driver/2.3.6) | [Converters](https://www.nuget.org/packages/Sarif.Converters/2.3.6) | [Multitool](https://www.nuget.org/packages/Sarif.Multitool/2.3.6) | [Multitool Library](https://www.nuget.org/packages/Sarif.Multitool.Library/2.3.6) * BUGFIX: Restore multitool client app package build. diff --git a/src/Sarif.Multitool.Library/ApplyPolicyCommand.cs b/src/Sarif.Multitool.Library/ApplyPolicyCommand.cs new file mode 100644 index 000000000..ee068186b --- /dev/null +++ b/src/Sarif.Multitool.Library/ApplyPolicyCommand.cs @@ -0,0 +1,65 @@ +// 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.Diagnostics; +using Microsoft.CodeAnalysis.Sarif.Driver; +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Multitool +{ + public class ApplyPolicyCommand : CommandBase + { + private readonly IFileSystem _fileSystem; + + public ApplyPolicyCommand(IFileSystem fileSystem = null) + { + _fileSystem = fileSystem ?? FileSystem.Instance; + } + + public int Run(ApplyPolicyOptions applyPolicyOptions) + { + try + { + Console.WriteLine($"Applying policy '{applyPolicyOptions.InputFilePath}' => '{applyPolicyOptions.OutputFilePath}'..."); + Stopwatch w = Stopwatch.StartNew(); + + bool valid = ValidateOptions(applyPolicyOptions); + if (!valid) { return FAILURE; } + + SarifLog actualLog = ReadSarifFile(_fileSystem, applyPolicyOptions.InputFilePath); + + actualLog.ApplyPolicies(); + + string fileName = CommandUtilities.GetTransformedOutputFileName(applyPolicyOptions); + + Formatting formatting = applyPolicyOptions.PrettyPrint + ? Formatting.Indented + : Formatting.None; + + WriteSarifFile(_fileSystem, actualLog, fileName, formatting); + + w.Stop(); + Console.WriteLine($"Rewrite completed in {w.Elapsed}."); + } + catch (Exception ex) + { + Console.WriteLine(ex); + return FAILURE; + } + + return SUCCESS; + } + + private bool ValidateOptions(ApplyPolicyOptions applyPolicyOptions) + { + bool valid = true; + + valid &= applyPolicyOptions.Validate(); + + valid &= DriverUtilities.ReportWhetherOutputFileCanBeCreated(applyPolicyOptions.OutputFilePath, applyPolicyOptions.Force, _fileSystem); + + return valid; + } + } +} \ No newline at end of file diff --git a/src/Sarif.Multitool.Library/ApplyPolicyOptions.cs b/src/Sarif.Multitool.Library/ApplyPolicyOptions.cs new file mode 100644 index 000000000..b539dc330 --- /dev/null +++ b/src/Sarif.Multitool.Library/ApplyPolicyOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using CommandLine; +using Microsoft.CodeAnalysis.Sarif.Driver; + +namespace Microsoft.CodeAnalysis.Sarif.Multitool +{ + [Verb("apply-policy", HelpText = "Apply policies from SARIF log.")] + public class ApplyPolicyOptions : SingleFileOptionsBase + { + } +} diff --git a/src/Sarif.Multitool/Program.cs b/src/Sarif.Multitool/Program.cs index 3ec226a8d..090df87a3 100644 --- a/src/Sarif.Multitool/Program.cs +++ b/src/Sarif.Multitool/Program.cs @@ -13,6 +13,7 @@ internal static class Program public static int Main(string[] args) { return Parser.Default.ParseArguments< + ApplyPolicyOptions, ExportValidationConfigurationOptions, ExportValidationDocumentationOptions, ExportValidationRulesMetadataOptions, @@ -29,6 +30,7 @@ public static int Main(string[] args) ResultMatchSetOptions, FileWorkItemsOptions>(args) .MapResult( + (ApplyPolicyOptions options) => new ApplyPolicyCommand().Run(options), (ExportValidationConfigurationOptions options) => new ExportValidationConfigurationCommand().Run(options), (ExportValidationDocumentationOptions options) => new ExportValidationDocumentationCommand().Run(options), (ExportValidationRulesMetadataOptions options) => new ExportValidationRulesMetadataCommand().Run(options), diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/ApplyPolicyCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/ApplyPolicyCommandTests.cs new file mode 100644 index 000000000..2b3349e9d --- /dev/null +++ b/src/Test.UnitTests.Sarif.Multitool.Library/ApplyPolicyCommandTests.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using FluentAssertions; +using Xunit; + +namespace Microsoft.CodeAnalysis.Sarif.Multitool +{ + public class ApplyPolicyCommandTests + { + private static readonly ResourceExtractor Extractor = new ResourceExtractor(typeof(ApplyPolicyCommandTests)); + + [Fact] + public void WhenInputContainsOnePolicy_ShouldSucceed() + { + string path = "WithPolicy.sarif"; + File.WriteAllText(path, Extractor.GetResourceText($"ApplyPolicyCommand.{path}")); + + // Verify log loads, has correct Result count, and spot check a Result + SarifLog log = ExecuteTest(path); + log.Runs[0].Results.Count.Should().Be(1); + log.Runs[0].Results[0].Level.Should().Be(FailureLevel.Error); + } + + [Fact] + public void WhenInputContainsMultiplePolicies_ShouldApplyPoliciesInOrder() + { + string path = "WithPolicy2.sarif"; + File.WriteAllText(path, Extractor.GetResourceText($"ApplyPolicyCommand.{path}")); + + // Verify log loads, has correct Result count, and spot check a Result + SarifLog log = ExecuteTest(path); + log.Runs[0].Results.Count.Should().Be(1); + log.Runs[0].Results[0].Level.Should().Be(FailureLevel.Note); + } + + private SarifLog ExecuteTest(string path) + { + var options = new ApplyPolicyOptions + { + InputFilePath = path, + OutputFilePath = path, + Force = true + }; + + // Verify command returned success + int returnCode = new ApplyPolicyCommand().Run(options); + returnCode.Should().Be(0); + + // Verify SARIF output log exists + File.Exists(path).Should().BeTrue(); + + return SarifLog.Load(path); + } + } +} diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/Test.UnitTests.Sarif.Multitool.Library.csproj b/src/Test.UnitTests.Sarif.Multitool.Library/Test.UnitTests.Sarif.Multitool.Library.csproj index 576e9b8eb..f35719565 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/Test.UnitTests.Sarif.Multitool.Library.csproj +++ b/src/Test.UnitTests.Sarif.Multitool.Library/Test.UnitTests.Sarif.Multitool.Library.csproj @@ -30,6 +30,8 @@ + + diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/TestData/ApplyPolicyCommand/WithPolicy.sarif b/src/Test.UnitTests.Sarif.Multitool.Library/TestData/ApplyPolicyCommand/WithPolicy.sarif new file mode 100644 index 000000000..77e3cd544 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Multitool.Library/TestData/ApplyPolicyCommand/WithPolicy.sarif @@ -0,0 +1,52 @@ +{ + "$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.1", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "Test", + "version": "1.0.0", + "rules": [ + { + "id": "TEST0001", + "name": "Test", + "shortDescription": { + "text": "Test description." + }, + "messageStrings": { + "default": { + "text": "Test description." + } + } + } + ] + } + }, + "results": [ + { + "ruleId": "TEST0001", + "ruleIndex": 0, + "message": { + "text": "Test text." + } + } + ], + "columnKind": "utf16CodeUnits", + "policies": [ + { + "name": "TEST Policy", + "version": "1.0.0", + "rules": [ + { + "id": "TEST0001", + "defaultConfiguration": { + "level": "error" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/TestData/ApplyPolicyCommand/WithPolicy2.sarif b/src/Test.UnitTests.Sarif.Multitool.Library/TestData/ApplyPolicyCommand/WithPolicy2.sarif new file mode 100644 index 000000000..c10342f52 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Multitool.Library/TestData/ApplyPolicyCommand/WithPolicy2.sarif @@ -0,0 +1,64 @@ +{ + "$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.1", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "Test", + "version": "1.0.0", + "rules": [ + { + "id": "TEST0001", + "name": "Test", + "shortDescription": { + "text": "Test description." + }, + "messageStrings": { + "default": { + "text": "Test description." + } + } + } + ] + } + }, + "results": [ + { + "ruleId": "TEST0001", + "ruleIndex": 0, + "message": { + "text": "Test text." + } + } + ], + "columnKind": "utf16CodeUnits", + "policies": [ + { + "name": "TEST Policy", + "version": "1.0.0", + "rules": [ + { + "id": "TEST0001", + "defaultConfiguration": { + "level": "error" + } + } + ] + }, + { + "name": "TEST Policy 2", + "version": "1.0.0", + "rules": [ + { + "id": "TEST0001", + "defaultConfiguration": { + "level": "note" + } + } + ] + } + ] + } + ] +} \ No newline at end of file