Skip to content

Commit

Permalink
Merge pull request #368 from borisf94/feature/borisf/add-command-line…
Browse files Browse the repository at this point in the history
…-for-custom-rules-path

Added the ability to specify custom rules.json path
  • Loading branch information
reynoldsa authored Aug 1, 2024
2 parents 582d919 + 479664d commit f3ee0ce
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 19 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Argument | Description
`-o` or `--output-file-path` | **(Required if `--report-format` is *Sarif*)** File path to output SARIF results to.
**(Optional)** `-v` or `--verbose` | Shows details about the analysis
**(Optional)** `--include-non-security-rules` | Run all the rules against the templates, including non-security rules
**(Optional)** `--custom-json-rules-path` | The [JSON rules file](docs/authoring-json-rules.md) to use against the templates.<br/>If not specified, will use the [default JSON rule set that is shipped with the tool](docs/built-in-rules.md#json-based-rules).

Template Analyzer runs the [configured rules](#understanding-and-customizing-rules) against the provided template and its corresponding [template parameters](https://docs.microsoft.com/azure/azure-resource-manager/templates/parameter-files), if specified. If no template parameters are specified, then Template Analyzer will check if templates with the [general naming standards defined by Microsoft](https://learn.microsoft.com/azure/azure-resource-manager/templates/parameter-files#file-name) are present in the same folder, otherwise it generates the minimum number of placeholder parameters to properly evaluate [template functions](https://docs.microsoft.com/azure/azure-resource-manager/templates/template-functions) in the template.

Expand Down
45 changes: 38 additions & 7 deletions src/Analyzer.Cli.FunctionalTests/CommandLineParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Azure.Templates.Analyzer.Cli;
using Microsoft.Azure.Templates.Analyzer.Core;
using Microsoft.Azure.Templates.Analyzer.Types;
using Microsoft.Azure.Templates.Analyzer.Utilities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -37,7 +39,7 @@ public void TestInit()
[DataRow("Invalid.bicep", ExitCode.ErrorInvalidBicepTemplate, DisplayName = "Path exists, invalid Bicep template")]
public void AnalyzeTemplate_ValidInputValues_ReturnExpectedExitCode(string relativeTemplatePath, ExitCode expectedExitCode, params string[] additionalCliOptions)
{
var args = new string[] { "analyze-template" , GetFilePath(relativeTemplatePath)};
var args = new string[] { "analyze-template", GetFilePath(relativeTemplatePath) };
args = args.Concat(additionalCliOptions).ToArray();
var result = _commandLineParser.InvokeCommandLineAPIAsync(args);

Expand All @@ -64,7 +66,7 @@ public void AnalyzeTemplate_UseConfigurationFileOption_ReturnExpectedExitCodeUsi
{
var templatePath = GetFilePath(relativeTemplatePath);
var configurationPath = GetFilePath("Configuration.json");
var args = new string[] { "analyze-template", templatePath, "--config-file-path", configurationPath};
var args = new string[] { "analyze-template", templatePath, "--config-file-path", configurationPath };
var result = _commandLineParser.InvokeCommandLineAPIAsync(args);

Assert.AreEqual((int)ExitCode.Violation, result.Result);
Expand All @@ -81,7 +83,7 @@ public void AnalyzeTemplate_ReportFormatAsSarif_ReturnExpectedExitCodeUsingOptio
var result = _commandLineParser.InvokeCommandLineAPIAsync(args);

Assert.AreEqual((int)ExitCode.Violation, result.Result);

File.Delete(outputFilePath);
}

Expand Down Expand Up @@ -117,6 +119,35 @@ public void AnalyzeTemplate_ValidInputValues_AnalyzesUsingAutoDetectedParameters
StringAssert.Contains(outputWriter.ToString(), "Parameters File: " + Path.Combine(Directory.GetCurrentDirectory(), "Tests", "ToTestSeparateParametersFile", "TemplateWithSeparateParametersFile.parameters.json"));
}

[TestMethod]
public void AnalyzeTemplate_ValidInputValues_AnalyzesUsingCustomJSONRulesPath()
{
var rulesDir = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
"Rules");
var rulesFile = Path.Combine(rulesDir, "BuiltInRules.json");
var customJSONRulesPath = Path.Combine(rulesDir, "MovedRules.json");
var templatePath = GetFilePath(Path.Combine("ToTestSeparateParametersFile", "TemplateWithSeparateParametersFile.bicep"));

var args = new string[] {
"analyze-template", templatePath,
"--custom-json-rules-path", customJSONRulesPath
};

// Move rules file
File.Move(rulesFile, customJSONRulesPath, overwrite: true);

try
{
var result = _commandLineParser.InvokeCommandLineAPIAsync(args);
Assert.AreEqual((int)ExitCode.Success, result.Result);
}
finally
{
File.Move(customJSONRulesPath, rulesFile, overwrite: true);
}
}

[TestMethod]
public void AnalyzeDirectory_ValidInputValues_AnalyzesExpectedNumberOfFiles()
{
Expand Down Expand Up @@ -161,7 +192,7 @@ public void AnalyzeDirectory_ValidInputValues_ReturnExpectedExitCode(bool useTes

args = args.Concat(additionalCliOptions).ToArray();
var result = _commandLineParser.InvokeCommandLineAPIAsync(args);

Assert.AreEqual((int)expectedExitCode, result.Result);
}

Expand All @@ -170,7 +201,7 @@ public void AnalyzeDirectory_DirectoryWithInvalidTemplates_LogsExpectedErrorInSa
{
var outputFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Output.sarif");
var directoryToAnalyze = GetFilePath("ToTestSarifNotifications");

var args = new string[] { "analyze-directory", directoryToAnalyze, "--report-format", "Sarif", "--output-file-path", outputFilePath };

var result = _commandLineParser.InvokeCommandLineAPIAsync(args);
Expand Down Expand Up @@ -228,7 +259,7 @@ public void AnalyzeDirectory_ExecutionWithErrorAndWarning_PrintsExpectedMessages
$"{Environment.NewLine}\t\t1 instance of: {errorMessage1}" +
$"{Environment.NewLine}\t\t1 instance of: {errorMessage2}";
}

expectedLogSummary += ($"{Environment.NewLine}{Environment.NewLine}\t1 Warning" +
$"{Environment.NewLine}\t{(multipleErrors ? "2 Errors" : "1 Error")}{Environment.NewLine}");

Expand Down Expand Up @@ -320,7 +351,7 @@ public void FilterRules_ValidConfig_RulesFiltered(bool isBicep, string configNam
}
})
.ToString());

if (specifyInCommand)
{
args = args.Concat(new[] { "--config-file-path", configName }).ToArray();
Expand Down
21 changes: 14 additions & 7 deletions src/Analyzer.Cli/CommandLineParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ private void SetupCommonOptionsForCommands(List<Command> commands)

new Option(
"--include-non-security-rules",
"Run all the rules against the templates, including non-security rules")
"Run all the rules against the templates, including non-security rules"),

new Option<FileInfo>(
"--custom-json-rules-path",
"The JSON rules file to use against the templates. If not specified, will use the default rule set that is shipped with the tool.")
};

commands.ForEach(c => options.ForEach(c.AddOption));
Expand All @@ -157,7 +161,8 @@ private int AnalyzeTemplateCommandHandler(
ReportFormat reportFormat,
FileInfo outputFilePath,
bool includeNonSecurityRules,
bool verbose)
bool verbose,
FileInfo customJsonRulesPath)
{
// Check that template file paths exist
if (!templateFilePath.Exists)
Expand All @@ -166,7 +171,7 @@ private int AnalyzeTemplateCommandHandler(
return (int)ExitCode.ErrorInvalidPath;
}

var setupResult = SetupAnalysis(configFilePath, directoryToAnalyze: null, reportFormat, outputFilePath, includeNonSecurityRules, verbose);
var setupResult = SetupAnalysis(configFilePath, directoryToAnalyze: null, reportFormat, outputFilePath, includeNonSecurityRules, verbose, customJsonRulesPath);
if (setupResult != ExitCode.Success)
{
return (int)setupResult;
Expand Down Expand Up @@ -203,15 +208,16 @@ private int AnalyzeDirectoryCommandHandler(
ReportFormat reportFormat,
FileInfo outputFilePath,
bool includeNonSecurityRules,
bool verbose)
bool verbose,
FileInfo customJsonRulesPath)
{
if (!directoryPath.Exists)
{
Console.Error.WriteLine("Invalid directory: {0}", directoryPath);
return (int)ExitCode.ErrorInvalidPath;
}

var setupResult = SetupAnalysis(configFilePath, directoryPath, reportFormat, outputFilePath, includeNonSecurityRules, verbose);
var setupResult = SetupAnalysis(configFilePath, directoryPath, reportFormat, outputFilePath, includeNonSecurityRules, verbose, customJsonRulesPath);
if (setupResult != ExitCode.Success)
{
return (int)setupResult;
Expand Down Expand Up @@ -278,7 +284,8 @@ private ExitCode SetupAnalysis(
ReportFormat reportFormat,
FileInfo outputFilePath,
bool includeNonSecurityRules,
bool verbose)
bool verbose,
FileInfo customJsonRulesPath)
{
// Output file path must be specified if SARIF was chosen as the report format
if (reportFormat == ReportFormat.Sarif && outputFilePath == null)
Expand All @@ -290,7 +297,7 @@ private ExitCode SetupAnalysis(
this.reportWriter = GetReportWriter(reportFormat, outputFilePath, directoryToAnalyze?.FullName);
CreateLoggers(verbose);

this.templateAnalyzer = TemplateAnalyzer.Create(includeNonSecurityRules, this.logger);
this.templateAnalyzer = TemplateAnalyzer.Create(includeNonSecurityRules, this.logger, customJsonRulesPath);

if (!TryReadConfigurationFile(configurationFile, out var config))
{
Expand Down
24 changes: 24 additions & 0 deletions src/Analyzer.Core.UnitTests/TemplateAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,30 @@ public void FilterRules_ValidConfiguration_NoExceptionThrown()
TemplateAnalyzer.Create(false).FilterRules(new ConfigurationDefinition());
}

[TestMethod]
public void CustomRulesFileIsProvided_NoExceptionThrown()
{
var rulesDir = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
"Rules");
var rulesFile = Path.Combine(rulesDir, "BuiltInRules.json");
var movedFile = Path.Combine(rulesDir, "MovedRules.json");

// Move rules file
File.Move(rulesFile, movedFile, overwrite: true);

var customRulesFile = new FileInfo(movedFile);

try
{
TemplateAnalyzer.Create(false, null, customRulesFile);
}
finally
{
File.Move(movedFile, rulesFile, overwrite: true);
}
}

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void FilterRules_ConfigurationNull_ExceptionThrown()
Expand Down
15 changes: 10 additions & 5 deletions src/Analyzer.Core/TemplateAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ private TemplateAnalyzer(JsonRuleEngine jsonRuleEngine, PowerShellRuleEngine pow
/// </summary>
/// <param name="includeNonSecurityRules">Whether or not to run also non-security rules against the template.</param>
/// <param name="logger">A logger to report errors and debug information</param>
/// <param name="customJsonRulesPath">An optional custom rules json file path.</param>
/// <returns>A new <see cref="TemplateAnalyzer"/> instance.</returns>
public static TemplateAnalyzer Create(bool includeNonSecurityRules, ILogger logger = null)
public static TemplateAnalyzer Create(bool includeNonSecurityRules, ILogger logger = null, FileInfo customJsonRulesPath = null)
{
string rules;
try
{
rules = LoadRules();
rules = LoadRules(customJsonRulesPath);
}
catch (Exception e)
{
Expand Down Expand Up @@ -224,12 +225,16 @@ private IEnumerable<IEvaluation> AnalyzeAllIncludedTemplates(string populatedTem
}
}

private static string LoadRules()
private static string LoadRules(FileInfo rulesFile)
{
return File.ReadAllText(
Path.Combine(
rulesFile ??= new FileInfo(Path.Combine(
Path.GetDirectoryName(AppContext.BaseDirectory),
"Rules/BuiltInRules.json"));

using var fileStream = rulesFile.OpenRead();
using var streamReader = new StreamReader(fileStream);

return streamReader.ReadToEnd();
}

/// <summary>
Expand Down

0 comments on commit f3ee0ce

Please sign in to comment.