Skip to content

Commit 8b55e6f

Browse files
committed
[IDP-1772] Display Spectral report in ADO CI build extension
Don't use string length Fix typo fix typo Generate spectral reports in stylish format instead of html Use txt format use pretty Try html and using script Use pretty and stylish Remove references to html Update readme
1 parent b6a0398 commit 8b55e6f

File tree

6 files changed

+86
-31
lines changed

6 files changed

+86
-31
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ For the TLDR version:
1717

1818
- The entry point is `ValidateOpenApiTask.ExecuteAsync()` and will be executed after the referencing project is built. This is defined in `./src/Workleap.OpenApi.MSBuild/msbuild/tools/Workleap.OpenApi.MSBuild.targets` as a `UsingTask.TaskName`
1919
- The default value are defined in the property group on the target `ValidateOpenApi` in this file `./src/Workleap.OpenApi.MSBuild/msbuild/tools/Workleap.OpenApi.MSBuild.targets`
20-
- Users can select whether to validate the API with frontend or backend ruleset depending how they configure the `OpenApiServiceProfile` MSBuild property ([`backend` (default)](https://github.com/gsoft-inc/wl-api-guidelines/blob/main/.spectral.backend.yaml) or [`frontend`](https://github.com/gsoft-inc/wl-api-guidelines/blob/main/.spectral.frontend.yaml)).
20+
- Users can select whether to validate the API with frontend or backend ruleset depending on how they configure the `OpenApiServiceProfile` MSBuild property ([`backend` (default)](https://github.com/gsoft-inc/wl-api-guidelines/blob/main/.spectral.backend.yaml) or [`frontend`](https://github.com/gsoft-inc/wl-api-guidelines/blob/main/.spectral.frontend.yaml)).
21+
- Users can configure their CI enviroment for reports depending on how they configure the `OpenApiCiReportEnvironment` MSBuild property (`ado` (default)).
2122

2223
## How to test locally
2324

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Workleap.OpenApi.MSBuild.Spectral;
2+
3+
internal sealed class AdoCiReportRenderer : ICiReportRenderer
4+
{
5+
public async Task AttachReportToBuildAsync(string reportPath)
6+
{
7+
// Attach the report to the build summary
8+
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_NAME")))
9+
{
10+
Console.WriteLine("##vso[task.addattachment type=Distributedtask.Core.Summary;name=Spectral results;]{0}", reportPath);
11+
}
12+
}
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Workleap.OpenApi.MSBuild.Spectral;
2+
3+
internal interface ICiReportRenderer
4+
{
5+
public Task AttachReportToBuildAsync(string reportPath);
6+
}

src/Workleap.OpenApi.MSBuild/Spectral/SpectralRunner.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ internal sealed class SpectralRunner
1010
private readonly ILoggerWrapper _loggerWrapper;
1111
private readonly IProcessWrapper _processWrapper;
1212
private readonly DiffCalculator _diffCalculator;
13+
private readonly ICiReportRenderer _ciReportRenderer;
1314
private readonly string _spectralDirectory;
1415
private readonly string _openApiReportsDirectoryPath;
1516

1617
public SpectralRunner(
1718
ILoggerWrapper loggerWrapper,
1819
IProcessWrapper processWrapper,
19-
DiffCalculator diffCalculator,
20+
DiffCalculator diffCalculator,
21+
ICiReportRenderer ciReportRenderer,
2022
string openApiToolsDirectoryPath,
2123
string openApiReportsDirectoryPath)
2224
{
@@ -25,6 +27,7 @@ public SpectralRunner(
2527
this._spectralDirectory = Path.Combine(openApiToolsDirectoryPath, "spectral", SpectralVersion);
2628
this._processWrapper = processWrapper;
2729
this._diffCalculator = diffCalculator;
30+
this._ciReportRenderer = ciReportRenderer;
2831
}
2932

3033
public async Task RunSpectralAsync(IReadOnlyCollection<string> openApiDocumentPaths, string spectralExecutablePath, string spectralRulesetPath, CancellationToken cancellationToken)
@@ -50,19 +53,20 @@ public async Task RunSpectralAsync(IReadOnlyCollection<string> openApiDocumentPa
5053
foreach (var documentPath in openApiDocumentPaths)
5154
{
5255
var documentName = Path.GetFileNameWithoutExtension(documentPath);
53-
var htmlReportPath = this.GetReportPath(documentPath);
56+
var spectralReportPath = this.GetReportPath(documentPath);
5457

5558
this._loggerWrapper.LogMessage("\n *** Spectral: Validating {0} against ruleset ***", MessageImportance.High, documentName);
5659
this._loggerWrapper.LogMessage("- File path: {0}", MessageImportance.High, documentPath);
5760
this._loggerWrapper.LogMessage("- Ruleset : {0}\n", MessageImportance.High, spectralRulesetPath);
5861

59-
if (File.Exists(htmlReportPath))
62+
if (File.Exists(spectralReportPath))
6063
{
61-
this._loggerWrapper.LogMessage("\nDeleting existing report: {0}", messageArgs: htmlReportPath);
62-
File.Delete(htmlReportPath);
64+
this._loggerWrapper.LogMessage("\nDeleting existing report: {0}", messageArgs: spectralReportPath);
65+
File.Delete(spectralReportPath);
6366
}
6467

65-
await this.GenerateSpectralReport(spectralExecutePath, documentPath, spectralRulesetPath, htmlReportPath, cancellationToken);
68+
await this.GenerateSpectralReport(spectralExecutePath, documentPath, spectralRulesetPath, spectralReportPath, cancellationToken);
69+
await this._ciReportRenderer.AttachReportToBuildAsync(spectralReportPath);
6670
this._loggerWrapper.LogMessage("\n ****************************************************************", MessageImportance.High);
6771
}
6872

@@ -87,11 +91,11 @@ private async Task<bool> ShouldRunSpectral(string spectralRulesetPath, IReadOnly
8791
private string GetReportPath(string documentPath)
8892
{
8993
var documentName = Path.GetFileNameWithoutExtension(documentPath);
90-
var outputSpectralReportName = $"spectral-{documentName}.html";
94+
var outputSpectralReportName = $"spectral-{documentName}.txt";
9195
return Path.Combine(this._openApiReportsDirectoryPath, outputSpectralReportName);
9296
}
9397

94-
private async Task GenerateSpectralReport(string spectralExecutePath, string swaggerDocumentPath, string rulesetPath, string htmlReportPath, CancellationToken cancellationToken)
98+
private async Task GenerateSpectralReport(string spectralExecutePath, string swaggerDocumentPath, string rulesetPath, string spectralReportPath, CancellationToken cancellationToken)
9599
{
96100
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
97101
{
@@ -100,25 +104,25 @@ private async Task GenerateSpectralReport(string spectralExecutePath, string swa
100104
}
101105

102106
this._loggerWrapper.LogMessage("Running Spectral...", MessageImportance.Normal);
103-
var result = await this._processWrapper.RunProcessAsync(spectralExecutePath, new[] { "lint", swaggerDocumentPath, "--ruleset", rulesetPath, "--format", "html", "--format", "pretty", "--output.html", htmlReportPath, "--fail-severity=warn", "--verbose" }, cancellationToken);
107+
var result = await this._processWrapper.RunProcessAsync(spectralExecutePath, new[] { "lint", swaggerDocumentPath, "--ruleset", rulesetPath, "--format", "stylish", "--output", spectralReportPath, "--fail-severity=warn", "--verbose" }, cancellationToken);
104108

105109
this._loggerWrapper.LogMessage(result.StandardOutput, MessageImportance.High);
106110
if (!string.IsNullOrEmpty(result.StandardError))
107111
{
108112
this._loggerWrapper.LogWarning(result.StandardError);
109113
}
110114

111-
if (!File.Exists(htmlReportPath))
115+
if (!File.Exists(spectralReportPath))
112116
{
113117
throw new OpenApiTaskFailedException($"Spectral report for {swaggerDocumentPath} could not be created. Please check the CONSOLE output above for more details.");
114118
}
115119

116120
if (result.ExitCode != 0)
117121
{
118-
this._loggerWrapper.LogWarning($"Spectral scan detected violation of ruleset. Please check the report [{htmlReportPath}] for more details.");
122+
this._loggerWrapper.LogWarning($"Spectral scan detected violation of ruleset. Please check the report [{spectralReportPath}] for more details.");
119123
}
120124

121-
this._loggerWrapper.LogMessage("Spectral report generated. {0}", messageArgs: htmlReportPath);
125+
this._loggerWrapper.LogMessage("Spectral report generated. {0}", messageArgs: spectralReportPath);
122126
}
123127

124128
private async Task AssignExecutePermission(string spectralExecutePath, CancellationToken cancellationToken)

src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public sealed class ValidateOpenApiTask : CancelableAsyncTask
1111
private const string ContractFirst = "ContractFirst"; // For backward compatibility
1212
private const string Backend = "backend";
1313
private const string Frontend = "frontend";
14+
private const string Ado = "ado";
1415

1516
/// <summary>
1617
/// 2 supported modes:
@@ -21,13 +22,20 @@ public sealed class ValidateOpenApiTask : CancelableAsyncTask
2122
public string OpenApiDevelopmentMode { get; set; } = string.Empty;
2223

2324
/// <summary>
24-
/// 2 supported profiles]:
25+
/// 2 supported profiles:
2526
/// - backend (default): Uses the backend ruleset to validate the API spec
2627
/// - frontend: Uses the frontend ruleset to validate the API spec
2728
/// </summary>
2829
[Required]
2930
public string OpenApiServiceProfile { get; set; } = string.Empty;
3031

32+
/// <summary>
33+
/// 1 supported CI environment for Spectral report export:
34+
/// - ado (default): Exports the Spectral report in an ADO compatible format
35+
/// </summary>
36+
[Required]
37+
public string OpenApiCiReportEnvironment { get; set; } = string.Empty;
38+
3139
/// <summary>When Development mode is ValidateContract, will validate if the specification match the code.</summary>
3240
[Required]
3341
public bool OpenApiCompareCodeAgainstSpecFile { get; set; } = false;
@@ -64,24 +72,9 @@ protected override async Task<bool> ExecuteAsync(CancellationToken cancellationT
6472

6573
loggerWrapper.LogMessage("\n******** Starting {0} ********\n", MessageImportance.Normal, nameof(ValidateOpenApiTask));
6674

67-
var reportsPath = Path.Combine(this.OpenApiToolsDirectoryPath, "reports");
68-
var processWrapper = new ProcessWrapper(this.StartupAssemblyPath);
69-
var swaggerManager = new SwaggerManager(loggerWrapper, processWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiWebApiAssemblyPath);
70-
var diffCalculator = new DiffCalculator(Path.Combine(this.OpenApiToolsDirectoryPath, "spectral-state"));
71-
72-
var httpClientWrapper = new HttpClientWrapper();
73-
74-
var spectralRulesetManager = new SpectralRulesetManager(loggerWrapper, httpClientWrapper, this.OpenApiServiceProfile, this.OpenApiSpectralRulesetUrl);
75-
var spectralInstaller = new SpectralInstaller(loggerWrapper, this.OpenApiToolsDirectoryPath, httpClientWrapper);
76-
var spectralManager = new SpectralRunner(loggerWrapper, processWrapper, diffCalculator, this.OpenApiToolsDirectoryPath, reportsPath);
77-
var oasdiffManager = new OasdiffManager(loggerWrapper, processWrapper, this.OpenApiToolsDirectoryPath, httpClientWrapper);
78-
var specGeneratorManager = new SpecGeneratorManager(loggerWrapper);
79-
80-
var generateContractProcess = new GenerateContractProcess(loggerWrapper, spectralInstaller, spectralRulesetManager, spectralManager, swaggerManager, specGeneratorManager, oasdiffManager);
81-
var validateContractProcess = new ValidateContractProcess(loggerWrapper, spectralInstaller, spectralRulesetManager, spectralManager, swaggerManager, oasdiffManager);
82-
8375
loggerWrapper.LogMessage("{0} = '{1}'", MessageImportance.Normal, nameof(this.OpenApiDevelopmentMode), this.OpenApiDevelopmentMode);
8476
loggerWrapper.LogMessage("{0} = '{1}'", MessageImportance.Normal, nameof(this.OpenApiServiceProfile), this.OpenApiServiceProfile);
77+
loggerWrapper.LogMessage("{0} = '{1}'", MessageImportance.Normal, nameof(this.OpenApiCiReportEnvironment), this.OpenApiCiReportEnvironment);
8578
loggerWrapper.LogMessage("{0} = '{1}'", MessageImportance.Normal, nameof(this.OpenApiCompareCodeAgainstSpecFile), this.OpenApiCompareCodeAgainstSpecFile);
8679
loggerWrapper.LogMessage("{0} = '{1}'", MessageImportance.Low, nameof(this.OpenApiTreatWarningsAsErrors), this.OpenApiTreatWarningsAsErrors);
8780
loggerWrapper.LogMessage("{0} = '{1}'", MessageImportance.Low, nameof(this.OpenApiWebApiAssemblyPath), this.OpenApiWebApiAssemblyPath);
@@ -93,7 +86,6 @@ protected override async Task<bool> ExecuteAsync(CancellationToken cancellationT
9386
if (this.OpenApiSpecificationFiles.Length != this.OpenApiSwaggerDocumentNames.Length)
9487
{
9588
loggerWrapper.LogWarning("You must provide the same amount of OpenAPI documents file names and swagger document file names.");
96-
9789
return false;
9890
}
9991

@@ -103,6 +95,30 @@ protected override async Task<bool> ExecuteAsync(CancellationToken cancellationT
10395
return false;
10496
}
10597

98+
if (!this.OpenApiCiReportEnvironment.Equals(Ado, StringComparison.Ordinal))
99+
{
100+
loggerWrapper.LogWarning("Invalid value of '{0}' for {1}. Allowed value is {2}", this.OpenApiCiReportEnvironment, nameof(this.OpenApiCiReportEnvironment), Ado);
101+
return false;
102+
}
103+
104+
var reportsPath = Path.Combine(this.OpenApiToolsDirectoryPath, "reports");
105+
var processWrapper = new ProcessWrapper(this.StartupAssemblyPath);
106+
var swaggerManager = new SwaggerManager(loggerWrapper, processWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiWebApiAssemblyPath);
107+
var diffCalculator = new DiffCalculator(Path.Combine(this.OpenApiToolsDirectoryPath, "spectral-state"));
108+
109+
var httpClientWrapper = new HttpClientWrapper();
110+
111+
var spectralRulesetManager = new SpectralRulesetManager(loggerWrapper, httpClientWrapper, this.OpenApiServiceProfile, this.OpenApiSpectralRulesetUrl);
112+
var spectralInstaller = new SpectralInstaller(loggerWrapper, this.OpenApiToolsDirectoryPath, httpClientWrapper);
113+
114+
var ciReportRenderer = this.InitializeCiReportRenderer();
115+
var spectralManager = new SpectralRunner(loggerWrapper, processWrapper, diffCalculator, ciReportRenderer, this.OpenApiToolsDirectoryPath, reportsPath);
116+
var oasdiffManager = new OasdiffManager(loggerWrapper, processWrapper, this.OpenApiToolsDirectoryPath, httpClientWrapper);
117+
var specGeneratorManager = new SpecGeneratorManager(loggerWrapper);
118+
119+
var generateContractProcess = new GenerateContractProcess(loggerWrapper, spectralInstaller, spectralRulesetManager, spectralManager, swaggerManager, specGeneratorManager, oasdiffManager);
120+
var validateContractProcess = new ValidateContractProcess(loggerWrapper, spectralInstaller, spectralRulesetManager, spectralManager, swaggerManager, oasdiffManager);
121+
106122
try
107123
{
108124
await this.GeneratePublicNugetSource();
@@ -161,4 +177,16 @@ private async Task GeneratePublicNugetSource()
161177
File.WriteAllText(path, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n <packageSources>\n <clear />\n <add key=\"nuget\" value=\"https://api.nuget.org/v3/index.json\" />\n </packageSources>\n</configuration>");
162178
}
163179
}
180+
181+
// In the future, we will want to support both ADO and Github CI environments
182+
private AdoCiReportRenderer InitializeCiReportRenderer()
183+
{
184+
switch (this.OpenApiCiReportEnvironment)
185+
{
186+
case Ado:
187+
return new AdoCiReportRenderer();
188+
default:
189+
return new AdoCiReportRenderer();
190+
}
191+
}
164192
}

src/Workleap.OpenApi.MSBuild/msbuild/tools/Workleap.OpenApi.MSBuild.targets

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<!-- We use this property when developing this MSBuild task in an IDE, -->
77
<!-- so we can reference the task assembly that is built in its bin folder instead of the one in the distributed NuGet package -->
88
<OpenApiDebuggingEnabled Condition="'$(OpenApiDebuggingEnabled)' == ''">false</OpenApiDebuggingEnabled>
9+
<!-- We use this property to determine the format for spectral report exports for the CI environments -->
10+
<OpenApiCiReportEnvironment Condition="'$(OpenApiCiReportEnvironment)' == ''">ado</OpenApiCiReportEnvironment>
911
<OpenApiTouchFileName>openapi_spec_validated.txt</OpenApiTouchFileName>
1012
</PropertyGroup>
1113

@@ -83,6 +85,7 @@
8385
OpenApiSpecificationFiles="$(OpenApiSpecificationFiles)"
8486
OpenApiTreatWarningsAsErrors="$(OpenApiTreatWarningsAsErrors)"
8587
OpenApiServiceProfile="$(OpenApiServiceProfile)"
88+
OpenApiCiReportEnvironment="$(OpenApiCiReportEnvironment)"
8689
/>
8790
<Touch Files="$(IntermediateOutputPath)$(OpenApiTouchFileName)" AlwaysCreate="true" />
8891
</Target>

0 commit comments

Comments
 (0)