Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the ability to specify custom rules.json path #368

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
borisf94 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading