Skip to content

Commit

Permalink
Implemented spectral report execution on build (#7)
Browse files Browse the repository at this point in the history
* Implemented spectral report execution on build

* Added additonal log

* Implement chmod x permission for binaries on non-windows

* Extracted method to assign permission

* Throw exception if execute is unsuccessful

* Removed exec path return and fixed chmod cmd

* Address partial download and skip if already exist
  • Loading branch information
ruvyas authored Nov 3, 2023
1 parent 2a31f92 commit 3008c6c
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 33 deletions.
30 changes: 18 additions & 12 deletions src/Workleap.OpenApi.MSBuild/HttpClientWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
using System.Net;

namespace Workleap.OpenApi.MSBuild;

internal sealed class HttpClientWrapper : IHttpClientWrapper, IDisposable
{
private readonly HttpClient _httpClient = new();
private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(10) };

public async Task DownloadFileToDestinationAsync(string url, string destination, CancellationToken cancellationToken)
{
using var responseStream = await this._httpClient.GetStreamAsync(url);

if (responseStream == null)
try
{
using var retryResponseStream = await this._httpClient.GetStreamAsync(url);
if (retryResponseStream != null)
using var responseStream = await this._httpClient.GetStreamAsync(url);

if (responseStream == null)
{
await SaveFileFromResponseAsync(destination, retryResponseStream, cancellationToken);
using var retryResponseStream = await this._httpClient.GetStreamAsync(url);
if (retryResponseStream != null)
{
await SaveFileFromResponseAsync(destination, retryResponseStream, cancellationToken);
}
else
{
throw new OpenApiTaskFailedException("Spectral could not be downloaded.");
}
}
else
{
throw new OpenApiTaskFailedException("Spectral could not be downloaded.");
await SaveFileFromResponseAsync(destination, responseStream, cancellationToken);
}
}
else
catch (Exception)
{
await SaveFileFromResponseAsync(destination, responseStream, cancellationToken);
File.Delete(destination);
throw;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/Workleap.OpenApi.MSBuild/ISpectralManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ namespace Workleap.OpenApi.MSBuild;
public interface ISpectralManager
{
public Task InstallSpectralAsync(CancellationToken cancellationToken);

public Task RunSpectralAsync(IEnumerable<string> swaggerDocumentPaths, string rulesetUrl, CancellationToken cancellationToken);
}
4 changes: 2 additions & 2 deletions src/Workleap.OpenApi.MSBuild/ISwaggerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ namespace Workleap.OpenApi.MSBuild;

public interface ISwaggerManager
{
Task RunSwaggerAsync(string[] openApiSwaggerDocumentNames, CancellationToken cancellationToken);
Task<IEnumerable<string>> RunSwaggerAsync(string[] openApiSwaggerDocumentNames, CancellationToken cancellationToken);

Task InstallSwaggerCliAsync(CancellationToken cancellationToken);

Task GenerateOpenApiSpecAsync(string swaggerExePath, string outputOpenApiSpecPath, string documentName, CancellationToken cancellationToken);
Task<string> GenerateOpenApiSpecAsync(string swaggerExePath, string outputOpenApiSpecPath, string documentName, CancellationToken cancellationToken);
}
68 changes: 59 additions & 9 deletions src/Workleap.OpenApi.MSBuild/SpectralManager.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Runtime.InteropServices;
using Microsoft.Build.Framework;

namespace Workleap.OpenApi.MSBuild;

Expand All @@ -9,23 +8,49 @@ internal sealed class SpectralManager : ISpectralManager

private readonly ILoggerWrapper _loggerWrapper;
private readonly IHttpClientWrapper _httpClientWrapper;
private readonly string _toolDirectory;

public SpectralManager(LoggerWrapper loggerWrapper, string openApiToolsDirectoryPath, IHttpClientWrapper httpClientWrapper)
private readonly string _spectralDirectory;
private readonly string _openApiToolsDirectory;
private readonly IProcessWrapper _processWrapper;

public SpectralManager(ILoggerWrapper loggerWrapper, string openApiToolsDirectoryPath, IHttpClientWrapper httpClientWrapper, IProcessWrapper processWrapper)
{
this._loggerWrapper = loggerWrapper;
this._httpClientWrapper = httpClientWrapper;
this._toolDirectory = Path.Combine(openApiToolsDirectoryPath, "spectral", SpectralVersion);
this._openApiToolsDirectory = openApiToolsDirectoryPath;
this._spectralDirectory = Path.Combine(openApiToolsDirectoryPath, "spectral", SpectralVersion);
this._processWrapper = processWrapper;
}

private string ExecutablePath { get; set; } = string.Empty;

public async Task InstallSpectralAsync(CancellationToken cancellationToken)
{
Directory.CreateDirectory(this._toolDirectory);
Directory.CreateDirectory(this._spectralDirectory);

this.ExecutablePath = GetSpectralFileName();
var url = $"https://github.com/stoplightio/spectral/releases/download/v{SpectralVersion}/{this.ExecutablePath}";
var destination = Path.Combine(this._spectralDirectory, this.ExecutablePath);

if (!File.Exists(destination))
{
await this._httpClientWrapper.DownloadFileToDestinationAsync(url, destination, cancellationToken);
}
}

var executableFileName = GetSpectralFileName();
var url = $"https://github.com/stoplightio/spectral/releases/download/v{SpectralVersion}/{executableFileName}";
public async Task RunSpectralAsync(IEnumerable<string> swaggerDocumentPaths, string rulesetUrl, CancellationToken cancellationToken)
{
this._loggerWrapper.LogMessage("Starting Spectral report generation.");

var spectralExecutePath = Path.Combine(this._spectralDirectory, this.ExecutablePath);
var reportsPath = Path.Combine(this._openApiToolsDirectory, "reports");
Directory.CreateDirectory(reportsPath);

await this._httpClientWrapper.DownloadFileToDestinationAsync(url, Path.Combine(this._toolDirectory, executableFileName), cancellationToken);
foreach (var documentPath in swaggerDocumentPaths)
{
var documentName = Path.GetFileNameWithoutExtension(documentPath);
var outputSpectralReportName = $"spectral-{documentName}.html";
await this.GenerateSpectralReport(spectralExecutePath, documentPath, rulesetUrl, Path.Combine(reportsPath, outputSpectralReportName), cancellationToken);
}
}

private static string GetSpectralFileName()
Expand Down Expand Up @@ -86,4 +111,29 @@ private static string GetArchitecture()

throw new OpenApiTaskFailedException("Unknown processor architecture encountered");
}

private async Task GenerateSpectralReport(string spectralExecutePath, string swaggerDocumentPath, string rulesetUrl, string htmlReportPath, CancellationToken cancellationToken)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
await this.AssignExecutePermission(spectralExecutePath, cancellationToken);
}

var exitCode = await this._processWrapper.RunProcessAsync(spectralExecutePath, new[] { "lint", swaggerDocumentPath, "--ruleset", rulesetUrl, "--format", "html", "--output.html", htmlReportPath }, cancellationToken);
if (exitCode != 0)
{
throw new OpenApiTaskFailedException($"Spectral report for {swaggerDocumentPath} could not be created.");
}

this._loggerWrapper.LogMessage("Spectral report generated. {0}", htmlReportPath);
}

private async Task AssignExecutePermission(string spectralExecutePath, CancellationToken cancellationToken)
{
var chmodExitCode = await this._processWrapper.RunProcessAsync("chmod", new[] { "+x", spectralExecutePath }, cancellationToken);
if (chmodExitCode != 0)
{
throw new OpenApiTaskFailedException($"Failed to provide execute permission to {spectralExecutePath}");
}
}
}
17 changes: 12 additions & 5 deletions src/Workleap.OpenApi.MSBuild/SwaggerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,32 @@ internal sealed class SwaggerManager : ISwaggerManager
private readonly ILoggerWrapper _loggerWrapper;
private readonly string _openApiWebApiAssemblyPath;
private readonly string _swaggerDirectory;
private readonly string _openApiToolsDirectoryPath;

public SwaggerManager(IProcessWrapper processWrapper, ILoggerWrapper loggerWrapper, string openApiToolsDirectoryPath, string openApiWebApiAssemblyPath)
{
this._processWrapper = processWrapper;
this._loggerWrapper = loggerWrapper;
this._openApiWebApiAssemblyPath = openApiWebApiAssemblyPath;
this._openApiToolsDirectoryPath = openApiToolsDirectoryPath;
this._swaggerDirectory = Path.Combine(openApiToolsDirectoryPath, "swagger", SwaggerVersion);
}

public async Task RunSwaggerAsync(string[] openApiSwaggerDocumentNames, CancellationToken cancellationToken)
public async Task<IEnumerable<string>> RunSwaggerAsync(string[] openApiSwaggerDocumentNames, CancellationToken cancellationToken)
{
var swaggerExePath = Path.Combine(this._swaggerDirectory, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "swagger.exe" : "swagger");
var taskList = new List<Task<string>>();

foreach (var documentName in openApiSwaggerDocumentNames)
{
var outputOpenApiSpecName = $"openapi-{documentName.ToLowerInvariant()}.yaml";

var outputOpenApiSpecPath = Path.Combine(this._swaggerDirectory, outputOpenApiSpecName);
var outputOpenApiSpecPath = Path.Combine(this._openApiToolsDirectoryPath, outputOpenApiSpecName);

await this.GenerateOpenApiSpecAsync(swaggerExePath, outputOpenApiSpecPath, documentName, cancellationToken);
taskList.Add(this.GenerateOpenApiSpecAsync(swaggerExePath, outputOpenApiSpecPath, documentName, cancellationToken));
}

return await Task.WhenAll(taskList);
}

public async Task InstallSwaggerCliAsync(CancellationToken cancellationToken)
Expand All @@ -55,13 +60,15 @@ public async Task InstallSwaggerCliAsync(CancellationToken cancellationToken)
}
}

public async Task GenerateOpenApiSpecAsync(string swaggerExePath, string outputOpenApiSpecPath, string documentName, CancellationToken cancellationToken)
public async Task<string> GenerateOpenApiSpecAsync(string swaggerExePath, string outputOpenApiSpecPath, string documentName, CancellationToken cancellationToken)
{
var exitCode = await this._processWrapper.RunProcessAsync(swaggerExePath, new[] { "tofile", "--output", outputOpenApiSpecPath, "--yaml", this._openApiWebApiAssemblyPath, documentName }, cancellationToken);

if (exitCode != 0)
{
throw new OpenApiTaskFailedException("OpenApi file could not be created.");
throw new OpenApiTaskFailedException($"OpenApi file {outputOpenApiSpecPath} could not be created.");
}

return outputOpenApiSpecPath;
}
}
11 changes: 6 additions & 5 deletions src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Framework;

namespace Workleap.OpenApi.MSBuild;

Expand Down Expand Up @@ -28,10 +28,10 @@ protected override async Task<bool> ExecuteAsync(CancellationToken cancellationT
{
var loggerWrapper = new LoggerWrapper(this.Log);
var processWrapper = new ProcessWrapper(this.OpenApiToolsDirectoryPath);
var swaggerManager = new SwaggerManager(processWrapper, loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath);
var swaggerManager = new SwaggerManager(processWrapper, loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiWebApiAssemblyPath);
using var httpClientWrapper = new HttpClientWrapper();

var spectralManager = new SpectralManager(loggerWrapper, this.OpenApiToolsDirectoryPath, httpClientWrapper);
var spectralManager = new SpectralManager(loggerWrapper, this.OpenApiToolsDirectoryPath, httpClientWrapper, processWrapper);

this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiWebApiAssemblyPath), this.OpenApiWebApiAssemblyPath);
this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiToolsDirectoryPath), this.OpenApiToolsDirectoryPath);
Expand All @@ -53,11 +53,12 @@ protected override async Task<bool> ExecuteAsync(CancellationToken cancellationT
await swaggerManager.InstallSwaggerCliAsync(cancellationToken);
await spectralManager.InstallSpectralAsync(cancellationToken);

// await swaggerManager.RunSwaggerAsync(this.OpenApiSwaggerDocumentNames, cancellationToken);
var swaggerDocPaths = await swaggerManager.RunSwaggerAsync(this.OpenApiSwaggerDocumentNames, cancellationToken);
await spectralManager.RunSpectralAsync(swaggerDocPaths, this.OpenApiSpectralRulesetUrl, cancellationToken);
}
catch (OpenApiTaskFailedException e)
{
this.Log.LogWarning("An error occured while validating the OpenAPI specification: {0}", e.Message);
this.Log.LogWarning("An error occurred while validating the OpenAPI specification: {0}", e.Message);
}

return true;
Expand Down

0 comments on commit 3008c6c

Please sign in to comment.