From 90452c39fd8dd987ac7ed04edec9ff827e00ce54 Mon Sep 17 00:00:00 2001 From: Rushikesh Vyas Date: Fri, 27 Oct 2023 16:36:05 -0400 Subject: [PATCH 01/12] Updated MS Task with swagger/swashbuckle install --- .gitignore | 1 + .../ValidateOpenApiTask.cs | 87 ++++++++++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0a70ab9..7aff56f 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ bld/ [Oo]bj/ [Ll]og/ [Ll]ogs/ +[Tt]ool/ # Visual Studio 2015/2017 cache/options directory .vs/ diff --git a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs index 3e4d456..59fc914 100644 --- a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs +++ b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs @@ -1,4 +1,7 @@ -using Microsoft.Build.Framework; +using System.Runtime.InteropServices; +using CliWrap; +using CliWrap.Buffered; +using Microsoft.Build.Framework; namespace Workleap.OpenApi.MSBuild; @@ -24,14 +27,92 @@ public sealed class ValidateOpenApiTask : CancelableAsyncTask [Required] public string[] OpenApiSpecificationFiles { get; set; } = Array.Empty(); - protected override Task ExecuteAsync(CancellationToken cancellationToken) + protected override async Task ExecuteAsync(CancellationToken cancellationToken) { this.Log.LogMessage(MessageImportance.High, "OpenApiWebApiAssemblyPath = '{0}'", this.OpenApiWebApiAssemblyPath); this.Log.LogMessage(MessageImportance.High, "OpenApiToolsDirectoryPath = '{0}'", this.OpenApiToolsDirectoryPath); this.Log.LogMessage(MessageImportance.High, "OpenApiSpectralRulesetUrl = '{0}'", this.OpenApiSpectralRulesetUrl); this.Log.LogMessage(MessageImportance.High, "OpenApiSwaggerDocumentNames = '{0}'", string.Join(", ", this.OpenApiSwaggerDocumentNames)); this.Log.LogMessage(MessageImportance.High, "OpenApiSpecificationFiles = '{0}'", string.Join(", ", this.OpenApiSpecificationFiles)); + + this.OpenApiSwaggerDocumentNames = this.OpenApiSwaggerDocumentNames is { Length: > 0 } + ? new HashSet(this.OpenApiSwaggerDocumentNames, StringComparer.Ordinal).ToArray() + : new[] { "v1" }; + + try + { + Directory.Delete(this.OpenApiToolsDirectoryPath, recursive: true); + } + catch (DirectoryNotFoundException) + { + } + + var swaggerDirPath = Path.Combine(this.OpenApiToolsDirectoryPath, "swagger"); + Directory.CreateDirectory(swaggerDirPath); + + await this.GeneratePublicNugetSource(); - return Task.FromResult(true); + // Install Swagger CLI + await this.InstallSwaggerCliAsync(swaggerDirPath, cancellationToken); + + var swaggerExePath = Path.Combine(swaggerDirPath, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "swagger.exe" : "swagger"); + + foreach (var documentName in this.OpenApiSwaggerDocumentNames) + { + var outputOpenApiSpecName = $"openapi-{documentName.ToLowerInvariant()}-{Guid.NewGuid().ToString("N").Substring(0, 6)}.yaml"; + var outputOpenApiSpecPath = Path.Combine(swaggerDirPath, outputOpenApiSpecName); + await this.GenerateOpenApiSpecAsync(swaggerExePath, outputOpenApiSpecPath, documentName, cancellationToken); + } + + // Install spectral + + // Install oasdiff + return true; + } + + private async Task GeneratePublicNugetSource() + { + if (!File.Exists(Path.Combine(this.OpenApiToolsDirectoryPath, "nuget.config"))) + { + using var outputFile = new StreamWriter(Path.Combine(this.OpenApiToolsDirectoryPath, "nuget.config"), true); + await outputFile.WriteLineAsync( + "\n\n \n \n \n \n"); + } + } + + private async Task InstallSwaggerCliAsync(string swaggerDirPath, CancellationToken cancellationToken) + { + var retryCount = 0; + while (retryCount < 2) + { + var exitCode = await this.RunProcessAsync("dotnet", new[] { "tool", "update", "Swashbuckle.AspNetCore.Cli", "--tool-path", swaggerDirPath }, cancellationToken); + + if (exitCode != 0 && retryCount != 1) + { + this.Log.LogMessage(MessageImportance.High, "Swashbuckle download failed. Retrying once more..."); + retryCount++; + continue; + } + + break; + } + } + + private async Task GenerateOpenApiSpecAsync(string swaggerExePath, string outputOpenApiSpecPath, string documentName, CancellationToken cancellationToken) + => await this.RunProcessAsync(swaggerExePath, new[] { "tofile", "--output", outputOpenApiSpecPath, "--yaml", this.OpenApiWebApiAssemblyPath, documentName }, cancellationToken); + + private async Task RunProcessAsync(string filename, string[] arguments, CancellationToken cancellationToken) + { + var result = await Cli.Wrap(filename) + .WithWorkingDirectory(this.OpenApiToolsDirectoryPath) + .WithValidation(CommandResultValidation.None) + .WithArguments(arguments) + .ExecuteBufferedAsync(cancellationToken); + + this.Log.LogMessage(MessageImportance.High, "stdout = '{0}'", result.StandardOutput); + this.Log.LogMessage(MessageImportance.High, "stderr = '{0}'", result.StandardError); + this.Log.LogMessage(MessageImportance.High, "exit code = '{0}'", result.ExitCode); + + return result.ExitCode; } } \ No newline at end of file From a27de8f727d789de9a7513d3041c48d49f37c5e9 Mon Sep 17 00:00:00 2001 From: Xavier Date: Tue, 31 Oct 2023 09:23:38 -0400 Subject: [PATCH 02/12] WIP, split by concerns, DI not done yet,unit tests not done yet --- .../Exceptions/OpenAPITaskFailedException.cs | 8 ++ .../ILoggerWrapper.cs | 8 ++ .../IProcessWrapper.cs | 6 + .../ISwaggerManager.cs | 10 ++ src/Workleap.OpenApi.MSBuild/LoggerWrapper.cs | 13 ++ src/Workleap.OpenApi.MSBuild/OpenApiPaths.cs | 5 + .../ProcessWrapper.cs | 25 ++++ .../SwaggerManager.cs | 70 +++++++++++ .../ValidateOpenApiTask.cs | 116 ++++++++---------- 9 files changed, 193 insertions(+), 68 deletions(-) create mode 100644 src/Workleap.OpenApi.MSBuild/Exceptions/OpenAPITaskFailedException.cs create mode 100644 src/Workleap.OpenApi.MSBuild/ILoggerWrapper.cs create mode 100644 src/Workleap.OpenApi.MSBuild/IProcessWrapper.cs create mode 100644 src/Workleap.OpenApi.MSBuild/ISwaggerManager.cs create mode 100644 src/Workleap.OpenApi.MSBuild/LoggerWrapper.cs create mode 100644 src/Workleap.OpenApi.MSBuild/OpenApiPaths.cs create mode 100644 src/Workleap.OpenApi.MSBuild/ProcessWrapper.cs create mode 100644 src/Workleap.OpenApi.MSBuild/SwaggerManager.cs diff --git a/src/Workleap.OpenApi.MSBuild/Exceptions/OpenAPITaskFailedException.cs b/src/Workleap.OpenApi.MSBuild/Exceptions/OpenAPITaskFailedException.cs new file mode 100644 index 0000000..b9c0246 --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/Exceptions/OpenAPITaskFailedException.cs @@ -0,0 +1,8 @@ +namespace Workleap.OpenApi.MSBuild.Exceptions; + +public class OpenApiTaskFailedException : Exception +{ + public OpenApiTaskFailedException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/ILoggerWrapper.cs b/src/Workleap.OpenApi.MSBuild/ILoggerWrapper.cs new file mode 100644 index 0000000..2a5cf05 --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/ILoggerWrapper.cs @@ -0,0 +1,8 @@ +using Microsoft.Build.Utilities; + +namespace Workleap.OpenApi.MSBuild; + +public interface ILoggerWrapper +{ + TaskLoggingHelper Helper { get; set; } +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/IProcessWrapper.cs b/src/Workleap.OpenApi.MSBuild/IProcessWrapper.cs new file mode 100644 index 0000000..7040fff --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/IProcessWrapper.cs @@ -0,0 +1,6 @@ +namespace Workleap.OpenApi.MSBuild; + +public interface IProcessWrapper +{ + public Task RunProcessAsync(string filename, string[] arguments, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/ISwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/ISwaggerManager.cs new file mode 100644 index 0000000..d58c4f7 --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/ISwaggerManager.cs @@ -0,0 +1,10 @@ +namespace Workleap.OpenApi.MSBuild; + +public interface ISwaggerManager +{ + Task RunSwaggerAsync(string[] openApiSwaggerDocumentNames, CancellationToken cancellationToken); + + Task InstallSwaggerCliAsync(CancellationToken cancellationToken); + + Task GenerateOpenApiSpecAsync(string swaggerExePath, string outputOpenApiSpecPath, string documentName, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/LoggerWrapper.cs b/src/Workleap.OpenApi.MSBuild/LoggerWrapper.cs new file mode 100644 index 0000000..f7587d1 --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/LoggerWrapper.cs @@ -0,0 +1,13 @@ +using Microsoft.Build.Utilities; + +namespace Workleap.OpenApi.MSBuild; + +internal sealed class LoggerWrapper : ILoggerWrapper +{ + public LoggerWrapper(TaskLoggingHelper helper) + { + this.Helper = helper; + } + + public TaskLoggingHelper Helper { get; set; } +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/OpenApiPaths.cs b/src/Workleap.OpenApi.MSBuild/OpenApiPaths.cs new file mode 100644 index 0000000..1fca914 --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/OpenApiPaths.cs @@ -0,0 +1,5 @@ +namespace Workleap.OpenApi.MSBuild; + +internal sealed class OpenApiPaths +{ +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/ProcessWrapper.cs b/src/Workleap.OpenApi.MSBuild/ProcessWrapper.cs new file mode 100644 index 0000000..bbeb9f6 --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/ProcessWrapper.cs @@ -0,0 +1,25 @@ +using CliWrap; +using CliWrap.Buffered; + +namespace Workleap.OpenApi.MSBuild; + +internal sealed class ProcessWrapper : IProcessWrapper +{ + private readonly string _workingDirectory; + + public ProcessWrapper(string workingDirectory) + { + this._workingDirectory = workingDirectory; + } + + public async Task RunProcessAsync(string filename, string[] arguments, CancellationToken cancellationToken) + { + var result = await Cli.Wrap(filename) + .WithWorkingDirectory(this._workingDirectory) + .WithValidation(CommandResultValidation.None) + .WithArguments(arguments) + .ExecuteBufferedAsync(cancellationToken); + + return result.ExitCode; + } +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs new file mode 100644 index 0000000..d884a04 --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -0,0 +1,70 @@ +using System.Runtime.InteropServices; +using Microsoft.Build.Framework; +using Workleap.OpenApi.MSBuild.Exceptions; + +namespace Workleap.OpenApi.MSBuild; + +internal sealed class SwaggerManager : ISwaggerManager +{ + private const string SwaggerVersion = "6.5.0"; + private readonly IProcessWrapper _processWrapper; + private readonly ILoggerWrapper _loggerWrapper; + private readonly string _openApiWebApiAssemblyPath; + private readonly string _swaggerDirectory; + + public SwaggerManager(IProcessWrapper processWrapper, ILoggerWrapper loggerWrapper, string openApiToolsDirectoryPath, string openApiWebApiAssemblyPath) + { + this._processWrapper = processWrapper; + this._loggerWrapper = loggerWrapper; + this._openApiWebApiAssemblyPath = openApiWebApiAssemblyPath; + this._swaggerDirectory = Path.Combine(openApiToolsDirectoryPath, $"swagger/{SwaggerVersion}"); + } + + public async Task RunSwaggerAsync(string[] openApiSwaggerDocumentNames, CancellationToken cancellationToken) + { + var swaggerExePath = Path.Combine(this._swaggerDirectory, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "swagger.exe" : "swagger"); + + foreach (var documentName in openApiSwaggerDocumentNames) + { + var outputOpenApiSpecName = $"openapi-{documentName.ToLowerInvariant()}.yaml"; + + // IDP-686, this is not the right path, should be in another folder named reports + var outputOpenApiSpecPath = Path.Combine(this._swaggerDirectory, outputOpenApiSpecName); + + await this.GenerateOpenApiSpecAsync(swaggerExePath, outputOpenApiSpecPath, documentName, cancellationToken); + } + } + + public async Task InstallSwaggerCliAsync(CancellationToken cancellationToken) + { + var retryCount = 0; + while (retryCount < 2) + { + var exitCode = await this._processWrapper.RunProcessAsync("dotnet", new[] { "tool", "update", "Swashbuckle.AspNetCore.Cli", "--tool-path", this._swaggerDirectory, "--version", "6.5.0" }, cancellationToken); + + if (exitCode != 0 && retryCount != 1) + { + this._loggerWrapper.Helper.LogWarning("Swashbuckle download failed. Retrying once more..."); + retryCount++; + continue; + } + + if (retryCount == 1 && exitCode != 0) + { + throw new OpenApiTaskFailedException("Swashbuckle CLI could not be installed."); + } + + break; + } + } + + public async Task 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."); + } + } +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs index 59fc914..eaabf0c 100644 --- a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs +++ b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs @@ -2,11 +2,30 @@ using CliWrap; using CliWrap.Buffered; using Microsoft.Build.Framework; +using Workleap.OpenApi.MSBuild.Exceptions; namespace Workleap.OpenApi.MSBuild; public sealed class ValidateOpenApiTask : CancelableAsyncTask { + private ILoggerWrapper _loggerWrapper; + private IProcessWrapper _processWrapper; + private ISwaggerManager _swaggerManager; + + public ValidateOpenApiTask(ILoggerWrapper loggerWrapper, IProcessWrapper processWrapper, ISwaggerManager swaggerManager) + { + this._loggerWrapper = loggerWrapper; + this._processWrapper = processWrapper; + this._swaggerManager = swaggerManager; + } + + public ValidateOpenApiTask() + { + this._loggerWrapper = new LoggerWrapper(this.Log); + this._processWrapper = new ProcessWrapper(this.OpenApiToolsDirectoryPath); + this._swaggerManager = new SwaggerManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); + } + /// The path of the ASP.NET Core project being built. [Required] public string OpenApiWebApiAssemblyPath { get; set; } = string.Empty; @@ -29,90 +48,51 @@ public sealed class ValidateOpenApiTask : CancelableAsyncTask protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - this.Log.LogMessage(MessageImportance.High, "OpenApiWebApiAssemblyPath = '{0}'", this.OpenApiWebApiAssemblyPath); - this.Log.LogMessage(MessageImportance.High, "OpenApiToolsDirectoryPath = '{0}'", this.OpenApiToolsDirectoryPath); - this.Log.LogMessage(MessageImportance.High, "OpenApiSpectralRulesetUrl = '{0}'", this.OpenApiSpectralRulesetUrl); - this.Log.LogMessage(MessageImportance.High, "OpenApiSwaggerDocumentNames = '{0}'", string.Join(", ", this.OpenApiSwaggerDocumentNames)); - this.Log.LogMessage(MessageImportance.High, "OpenApiSpecificationFiles = '{0}'", string.Join(", ", this.OpenApiSpecificationFiles)); - - this.OpenApiSwaggerDocumentNames = this.OpenApiSwaggerDocumentNames is { Length: > 0 } - ? new HashSet(this.OpenApiSwaggerDocumentNames, StringComparer.Ordinal).ToArray() - : new[] { "v1" }; - - try + this._loggerWrapper = new LoggerWrapper(this.Log); + this._processWrapper = new ProcessWrapper(this.OpenApiToolsDirectoryPath); + this._swaggerManager = new SwaggerManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); + + this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiWebApiAssemblyPath), this.OpenApiWebApiAssemblyPath); + this.Log.LogMessage(MessageImportance.Low, "OpenApiToolsDirectoryPath = '{0}'", this.OpenApiToolsDirectoryPath); + this.Log.LogMessage(MessageImportance.Low, "OpenApiSpectralRulesetUrl = '{0}'", this.OpenApiSpectralRulesetUrl); + this.Log.LogMessage(MessageImportance.Low, "OpenApiSwaggerDocumentNames = '{0}'", string.Join(", ", this.OpenApiSwaggerDocumentNames)); + this.Log.LogMessage(MessageImportance.Low, "OpenApiSpecificationFiles = '{0}'", string.Join(", ", this.OpenApiSpecificationFiles)); + + if (this.OpenApiSpecificationFiles.Length != this.OpenApiSwaggerDocumentNames.Length) { - Directory.Delete(this.OpenApiToolsDirectoryPath, recursive: true); + this.Log.LogWarning("OpenApiSpecificationFiles and OpenApiSwaggerDocumentNames should have the same lenght", this.OpenApiWebApiAssemblyPath); + + return false; } - catch (DirectoryNotFoundException) + + try { - } - - var swaggerDirPath = Path.Combine(this.OpenApiToolsDirectoryPath, "swagger"); - Directory.CreateDirectory(swaggerDirPath); - - await this.GeneratePublicNugetSource(); + await this.GeneratePublicNugetSource(); - // Install Swagger CLI - await this.InstallSwaggerCliAsync(swaggerDirPath, cancellationToken); + // Install Swagger CLI + await this._swaggerManager.InstallSwaggerCliAsync(cancellationToken); + await this._swaggerManager.RunSwaggerAsync(this.OpenApiSwaggerDocumentNames, cancellationToken); - var swaggerExePath = Path.Combine(swaggerDirPath, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "swagger.exe" : "swagger"); + // Install spectral - foreach (var documentName in this.OpenApiSwaggerDocumentNames) + // Install oasdiff + } + catch (OpenApiTaskFailedException e) { - var outputOpenApiSpecName = $"openapi-{documentName.ToLowerInvariant()}-{Guid.NewGuid().ToString("N").Substring(0, 6)}.yaml"; - var outputOpenApiSpecPath = Path.Combine(swaggerDirPath, outputOpenApiSpecName); - await this.GenerateOpenApiSpecAsync(swaggerExePath, outputOpenApiSpecPath, documentName, cancellationToken); + this.Log.LogWarning("OpenApi validation could not be done. {0}", e.Message); } - // Install spectral - - // Install oasdiff return true; } private async Task GeneratePublicNugetSource() { + Directory.CreateDirectory(this.OpenApiToolsDirectoryPath); + if (!File.Exists(Path.Combine(this.OpenApiToolsDirectoryPath, "nuget.config"))) { - using var outputFile = new StreamWriter(Path.Combine(this.OpenApiToolsDirectoryPath, "nuget.config"), true); - await outputFile.WriteLineAsync( - "\n\n \n \n \n \n"); + var path = Path.Combine(this.OpenApiToolsDirectoryPath, "nuget.config"); + File.WriteAllText(path, "\n\n \n \n \n \n"); } } - - private async Task InstallSwaggerCliAsync(string swaggerDirPath, CancellationToken cancellationToken) - { - var retryCount = 0; - while (retryCount < 2) - { - var exitCode = await this.RunProcessAsync("dotnet", new[] { "tool", "update", "Swashbuckle.AspNetCore.Cli", "--tool-path", swaggerDirPath }, cancellationToken); - - if (exitCode != 0 && retryCount != 1) - { - this.Log.LogMessage(MessageImportance.High, "Swashbuckle download failed. Retrying once more..."); - retryCount++; - continue; - } - - break; - } - } - - private async Task GenerateOpenApiSpecAsync(string swaggerExePath, string outputOpenApiSpecPath, string documentName, CancellationToken cancellationToken) - => await this.RunProcessAsync(swaggerExePath, new[] { "tofile", "--output", outputOpenApiSpecPath, "--yaml", this.OpenApiWebApiAssemblyPath, documentName }, cancellationToken); - - private async Task RunProcessAsync(string filename, string[] arguments, CancellationToken cancellationToken) - { - var result = await Cli.Wrap(filename) - .WithWorkingDirectory(this.OpenApiToolsDirectoryPath) - .WithValidation(CommandResultValidation.None) - .WithArguments(arguments) - .ExecuteBufferedAsync(cancellationToken); - - this.Log.LogMessage(MessageImportance.High, "stdout = '{0}'", result.StandardOutput); - this.Log.LogMessage(MessageImportance.High, "stderr = '{0}'", result.StandardError); - this.Log.LogMessage(MessageImportance.High, "exit code = '{0}'", result.ExitCode); - - return result.ExitCode; - } } \ No newline at end of file From ec247e6e553a29448c578bbb4ef94dbee7577307 Mon Sep 17 00:00:00 2001 From: Xavier Date: Tue, 31 Oct 2023 09:27:01 -0400 Subject: [PATCH 03/12] Rename exception --- ...penAPITaskFailedException.cs => OpenApiTaskFailedException.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Workleap.OpenApi.MSBuild/Exceptions/{OpenAPITaskFailedException.cs => OpenApiTaskFailedException.cs} (100%) diff --git a/src/Workleap.OpenApi.MSBuild/Exceptions/OpenAPITaskFailedException.cs b/src/Workleap.OpenApi.MSBuild/Exceptions/OpenApiTaskFailedException.cs similarity index 100% rename from src/Workleap.OpenApi.MSBuild/Exceptions/OpenAPITaskFailedException.cs rename to src/Workleap.OpenApi.MSBuild/Exceptions/OpenApiTaskFailedException.cs From 3133fea0c51b2f8816a4e6d30917ce78ab0ecd77 Mon Sep 17 00:00:00 2001 From: Xavier Date: Tue, 31 Oct 2023 10:03:48 -0400 Subject: [PATCH 04/12] Change log format --- src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs index eaabf0c..210ecae 100644 --- a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs +++ b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs @@ -53,10 +53,10 @@ protected override async Task ExecuteAsync(CancellationToken cancellationT this._swaggerManager = new SwaggerManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiWebApiAssemblyPath), this.OpenApiWebApiAssemblyPath); - this.Log.LogMessage(MessageImportance.Low, "OpenApiToolsDirectoryPath = '{0}'", this.OpenApiToolsDirectoryPath); - this.Log.LogMessage(MessageImportance.Low, "OpenApiSpectralRulesetUrl = '{0}'", this.OpenApiSpectralRulesetUrl); - this.Log.LogMessage(MessageImportance.Low, "OpenApiSwaggerDocumentNames = '{0}'", string.Join(", ", this.OpenApiSwaggerDocumentNames)); - this.Log.LogMessage(MessageImportance.Low, "OpenApiSpecificationFiles = '{0}'", string.Join(", ", this.OpenApiSpecificationFiles)); + this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiToolsDirectoryPath), this.OpenApiToolsDirectoryPath); + this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiSpectralRulesetUrl), this.OpenApiSpectralRulesetUrl); + this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiSwaggerDocumentNames), string.Join(", ", this.OpenApiSwaggerDocumentNames)); + this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiSpecificationFiles), string.Join(", ", this.OpenApiSpecificationFiles)); if (this.OpenApiSpecificationFiles.Length != this.OpenApiSwaggerDocumentNames.Length) { From cc2788b7060edf7f8745e88c098fd0c741867c58 Mon Sep 17 00:00:00 2001 From: Xavier Date: Wed, 1 Nov 2023 09:35:21 -0400 Subject: [PATCH 05/12] Install spectral --- .../ISpectralManager.cs | 6 + .../SpectralManager.cs | 127 ++++++++++++++++++ .../ValidateOpenApiTask.cs | 10 +- 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 src/Workleap.OpenApi.MSBuild/ISpectralManager.cs create mode 100644 src/Workleap.OpenApi.MSBuild/SpectralManager.cs diff --git a/src/Workleap.OpenApi.MSBuild/ISpectralManager.cs b/src/Workleap.OpenApi.MSBuild/ISpectralManager.cs new file mode 100644 index 0000000..11fd5db --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/ISpectralManager.cs @@ -0,0 +1,6 @@ +namespace Workleap.OpenApi.MSBuild; + +public interface ISpectralManager +{ + public Task InstallSpectralAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/SpectralManager.cs b/src/Workleap.OpenApi.MSBuild/SpectralManager.cs new file mode 100644 index 0000000..274f2bf --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/SpectralManager.cs @@ -0,0 +1,127 @@ +using System.Runtime.InteropServices; +using Microsoft.Build.Framework; +using Workleap.OpenApi.MSBuild.Exceptions; + +namespace Workleap.OpenApi.MSBuild; + +internal sealed class SpectralManager : ISpectralManager +{ + private const string SpectralVersion = "6.11.0"; + + private readonly IProcessWrapper _processWrapper; + private readonly ILoggerWrapper _loggerWrapper; + private readonly string _openApiToolsDirectoryPath; + private readonly string _toolDirectory; + + public SpectralManager(IProcessWrapper processWrapper, ILoggerWrapper loggerWrapper, string openApiToolsDirectoryPath, string openApiWebApiAssemblyPath) + { + this._processWrapper = processWrapper; + this._loggerWrapper = loggerWrapper; + this._openApiToolsDirectoryPath = openApiToolsDirectoryPath; + this._toolDirectory = Path.Combine(openApiToolsDirectoryPath, $"spectral/{SpectralVersion}"); + } + + public async Task InstallSpectralAsync(CancellationToken cancellationToken) + { + this.CreateRequiredDirectories(); + var executableFileName = this.GetSpectralFileName(); + + var url = $"https://github.com/stoplightio/spectral/releases/download/v{SpectralVersion}/{executableFileName}"; + + await this.DownloadFileAsync(url, $"{this._toolDirectory}/{executableFileName}"); + } + + private void CreateRequiredDirectories() + { + Directory.CreateDirectory(Path.Combine(this._openApiToolsDirectoryPath, "spectral")); + Directory.CreateDirectory(this._toolDirectory); + } + + private string GetSpectralFileName() + { + var osType = GetOperatingSystem(); + var architecture = GetArchitecture(); + + if (osType == "linux") + { + var distro = File.Exists("/etc/os-release") ? File.ReadAllText("/etc/os-release") : string.Empty; + if (distro.Contains("Alpine Linux")) + { + osType = "alpine"; + this._loggerWrapper.Helper.LogMessage(MessageImportance.Low, "Installing on Alpine Linux."); + } + } + + var fileName = $"spectral-{osType}-{architecture}"; + + if (osType == "win") + { + fileName = $"spectral.exe"; + this._loggerWrapper.Helper.LogMessage(MessageImportance.Low, "Installing on Windows."); + } + + return fileName; + } + + private async Task DownloadFileAsync(string url, string destination) + { + if (File.Exists(destination)) + { + this._loggerWrapper.Helper.LogMessage(MessageImportance.Low, "File already exist"); + + return; + } + + using var httpClient = new HttpClient(); + using var response = await httpClient.GetAsync(url); + response.EnsureSuccessStatusCode(); + + byte[] fileContents; + if (!response.IsSuccessStatusCode) + { + using var retryResponse = await httpClient.GetAsync(url); + fileContents = await retryResponse.Content.ReadAsByteArrayAsync(); + } + else + { + fileContents = await response.Content.ReadAsByteArrayAsync(); + } + + File.WriteAllBytes(destination, fileContents); + } + + private static string GetOperatingSystem() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return "linux"; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return "macos"; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return "win"; + } + + return "unknown"; + } + + private static string GetArchitecture() + { + if (RuntimeInformation.OSArchitecture == Architecture.X64) + { + return "x64"; + } + + if (RuntimeInformation.OSArchitecture == Architecture.Arm64) + { + return "arm64"; + } + + return "unknown"; + } +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs index 210ecae..193c36c 100644 --- a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs +++ b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs @@ -11,12 +11,14 @@ public sealed class ValidateOpenApiTask : CancelableAsyncTask private ILoggerWrapper _loggerWrapper; private IProcessWrapper _processWrapper; private ISwaggerManager _swaggerManager; + private ISpectralManager _spectralManager; - public ValidateOpenApiTask(ILoggerWrapper loggerWrapper, IProcessWrapper processWrapper, ISwaggerManager swaggerManager) + public ValidateOpenApiTask(ILoggerWrapper loggerWrapper, IProcessWrapper processWrapper, ISwaggerManager swaggerManager, ISpectralManager spectralManager) { this._loggerWrapper = loggerWrapper; this._processWrapper = processWrapper; this._swaggerManager = swaggerManager; + this._spectralManager = spectralManager; } public ValidateOpenApiTask() @@ -24,6 +26,7 @@ public ValidateOpenApiTask() this._loggerWrapper = new LoggerWrapper(this.Log); this._processWrapper = new ProcessWrapper(this.OpenApiToolsDirectoryPath); this._swaggerManager = new SwaggerManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); + this._spectralManager = new SpectralManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); } /// The path of the ASP.NET Core project being built. @@ -51,6 +54,7 @@ protected override async Task ExecuteAsync(CancellationToken cancellationT this._loggerWrapper = new LoggerWrapper(this.Log); this._processWrapper = new ProcessWrapper(this.OpenApiToolsDirectoryPath); this._swaggerManager = new SwaggerManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); + this._spectralManager = new SpectralManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiWebApiAssemblyPath), this.OpenApiWebApiAssemblyPath); this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiToolsDirectoryPath), this.OpenApiToolsDirectoryPath); @@ -71,9 +75,11 @@ protected override async Task ExecuteAsync(CancellationToken cancellationT // Install Swagger CLI await this._swaggerManager.InstallSwaggerCliAsync(cancellationToken); - await this._swaggerManager.RunSwaggerAsync(this.OpenApiSwaggerDocumentNames, cancellationToken); + + // await this._swaggerManager.RunSwaggerAsync(this.OpenApiSwaggerDocumentNames, cancellationToken); // Install spectral + await this._spectralManager.InstallSpectralAsync(cancellationToken); // Install oasdiff } From a12b59d4709512aa0f297f95a0786e49a2a39948 Mon Sep 17 00:00:00 2001 From: Xavier Date: Wed, 1 Nov 2023 10:35:42 -0400 Subject: [PATCH 06/12] Fix parameter --- src/Workleap.OpenApi.MSBuild/SpectralManager.cs | 2 +- src/Workleap.OpenApi.MSBuild/SwaggerManager.cs | 1 - src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Workleap.OpenApi.MSBuild/SpectralManager.cs b/src/Workleap.OpenApi.MSBuild/SpectralManager.cs index 274f2bf..ff8dbea 100644 --- a/src/Workleap.OpenApi.MSBuild/SpectralManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SpectralManager.cs @@ -13,7 +13,7 @@ internal sealed class SpectralManager : ISpectralManager private readonly string _openApiToolsDirectoryPath; private readonly string _toolDirectory; - public SpectralManager(IProcessWrapper processWrapper, ILoggerWrapper loggerWrapper, string openApiToolsDirectoryPath, string openApiWebApiAssemblyPath) + public SpectralManager(IProcessWrapper processWrapper, ILoggerWrapper loggerWrapper, string openApiToolsDirectoryPath) { this._processWrapper = processWrapper; this._loggerWrapper = loggerWrapper; diff --git a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs index d884a04..6fcf244 100644 --- a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -28,7 +28,6 @@ public async Task RunSwaggerAsync(string[] openApiSwaggerDocumentNames, Cancella { var outputOpenApiSpecName = $"openapi-{documentName.ToLowerInvariant()}.yaml"; - // IDP-686, this is not the right path, should be in another folder named reports var outputOpenApiSpecPath = Path.Combine(this._swaggerDirectory, outputOpenApiSpecName); await this.GenerateOpenApiSpecAsync(swaggerExePath, outputOpenApiSpecPath, documentName, cancellationToken); diff --git a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs index 193c36c..5949040 100644 --- a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs +++ b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs @@ -25,8 +25,8 @@ public ValidateOpenApiTask() { this._loggerWrapper = new LoggerWrapper(this.Log); this._processWrapper = new ProcessWrapper(this.OpenApiToolsDirectoryPath); - this._swaggerManager = new SwaggerManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); - this._spectralManager = new SpectralManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); + this._swaggerManager = new SwaggerManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiWebApiAssemblyPath); + this._spectralManager = new SpectralManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath); } /// The path of the ASP.NET Core project being built. From 9dbc3f710e6dfcc11c056ec327bd4340b76bcc8c Mon Sep 17 00:00:00 2001 From: Xavier Date: Wed, 1 Nov 2023 11:01:55 -0400 Subject: [PATCH 07/12] Fix parameter --- src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs index 5949040..2794916 100644 --- a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs +++ b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs @@ -54,7 +54,7 @@ protected override async Task ExecuteAsync(CancellationToken cancellationT this._loggerWrapper = new LoggerWrapper(this.Log); this._processWrapper = new ProcessWrapper(this.OpenApiToolsDirectoryPath); this._swaggerManager = new SwaggerManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); - this._spectralManager = new SpectralManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); + this._spectralManager = new SpectralManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath); this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiWebApiAssemblyPath), this.OpenApiWebApiAssemblyPath); this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiToolsDirectoryPath), this.OpenApiToolsDirectoryPath); From e16fae71a1a624ec8b918cc7bce631bf23ffcc92 Mon Sep 17 00:00:00 2001 From: Xavier Date: Wed, 1 Nov 2023 12:03:24 -0400 Subject: [PATCH 08/12] Pair review changes --- src/Workleap.OpenApi.MSBuild/OpenApiPaths.cs | 5 --- .../SpectralManager.cs | 31 +++++++++++----- .../ValidateOpenApiTask.cs | 35 ++++--------------- 3 files changed, 29 insertions(+), 42 deletions(-) delete mode 100644 src/Workleap.OpenApi.MSBuild/OpenApiPaths.cs diff --git a/src/Workleap.OpenApi.MSBuild/OpenApiPaths.cs b/src/Workleap.OpenApi.MSBuild/OpenApiPaths.cs deleted file mode 100644 index 1fca914..0000000 --- a/src/Workleap.OpenApi.MSBuild/OpenApiPaths.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Workleap.OpenApi.MSBuild; - -internal sealed class OpenApiPaths -{ -} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/SpectralManager.cs b/src/Workleap.OpenApi.MSBuild/SpectralManager.cs index ff8dbea..8bc21b3 100644 --- a/src/Workleap.OpenApi.MSBuild/SpectralManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SpectralManager.cs @@ -28,7 +28,7 @@ public async Task InstallSpectralAsync(CancellationToken cancellationToken) var url = $"https://github.com/stoplightio/spectral/releases/download/v{SpectralVersion}/{executableFileName}"; - await this.DownloadFileAsync(url, $"{this._toolDirectory}/{executableFileName}"); + await this.DownloadFileAsync(url, $"{this._toolDirectory}/{executableFileName}", cancellationToken); } private void CreateRequiredDirectories() @@ -56,14 +56,14 @@ private string GetSpectralFileName() if (osType == "win") { - fileName = $"spectral.exe"; + fileName = "spectral.exe"; this._loggerWrapper.Helper.LogMessage(MessageImportance.Low, "Installing on Windows."); } return fileName; } - private async Task DownloadFileAsync(string url, string destination) + private async Task DownloadFileAsync(string url, string destination, CancellationToken cancellationToken) { if (File.Exists(destination)) { @@ -73,21 +73,34 @@ private async Task DownloadFileAsync(string url, string destination) } using var httpClient = new HttpClient(); - using var response = await httpClient.GetAsync(url); + using var response = await httpClient.GetAsync(url, cancellationToken); response.EnsureSuccessStatusCode(); - byte[] fileContents; if (!response.IsSuccessStatusCode) { - using var retryResponse = await httpClient.GetAsync(url); - fileContents = await retryResponse.Content.ReadAsByteArrayAsync(); + using var retryResponse = await httpClient.GetAsync(url, cancellationToken); + + if (retryResponse.IsSuccessStatusCode) + { + await SaveFileFromResponseAsync(destination, retryResponse, cancellationToken); + } + else + { + throw new OpenApiTaskFailedException("Spectral could not be installed."); + } } else { - fileContents = await response.Content.ReadAsByteArrayAsync(); + await SaveFileFromResponseAsync(destination, response, cancellationToken); } + } + + private static async Task SaveFileFromResponseAsync(string destination, HttpResponseMessage response, CancellationToken cancellationToken) + { + using var fileTarget = new FileStream(destination, FileMode.Create, FileAccess.Write, FileShare.None); + using var fileStream = await response.Content.ReadAsStreamAsync(); - File.WriteAllBytes(destination, fileContents); + await fileStream.CopyToAsync(fileTarget, 1024, cancellationToken); } private static string GetOperatingSystem() diff --git a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs index 2794916..6d37067 100644 --- a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs +++ b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs @@ -8,27 +8,6 @@ namespace Workleap.OpenApi.MSBuild; public sealed class ValidateOpenApiTask : CancelableAsyncTask { - private ILoggerWrapper _loggerWrapper; - private IProcessWrapper _processWrapper; - private ISwaggerManager _swaggerManager; - private ISpectralManager _spectralManager; - - public ValidateOpenApiTask(ILoggerWrapper loggerWrapper, IProcessWrapper processWrapper, ISwaggerManager swaggerManager, ISpectralManager spectralManager) - { - this._loggerWrapper = loggerWrapper; - this._processWrapper = processWrapper; - this._swaggerManager = swaggerManager; - this._spectralManager = spectralManager; - } - - public ValidateOpenApiTask() - { - this._loggerWrapper = new LoggerWrapper(this.Log); - this._processWrapper = new ProcessWrapper(this.OpenApiToolsDirectoryPath); - this._swaggerManager = new SwaggerManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiWebApiAssemblyPath); - this._spectralManager = new SpectralManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath); - } - /// The path of the ASP.NET Core project being built. [Required] public string OpenApiWebApiAssemblyPath { get; set; } = string.Empty; @@ -51,10 +30,10 @@ public ValidateOpenApiTask() protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - this._loggerWrapper = new LoggerWrapper(this.Log); - this._processWrapper = new ProcessWrapper(this.OpenApiToolsDirectoryPath); - this._swaggerManager = new SwaggerManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); - this._spectralManager = new SpectralManager(this._processWrapper, this._loggerWrapper, this.OpenApiToolsDirectoryPath); + var loggerWrapper = new LoggerWrapper(this.Log); + var processWrapper = new ProcessWrapper(this.OpenApiToolsDirectoryPath); + var swaggerManager = new SwaggerManager(processWrapper, loggerWrapper, this.OpenApiToolsDirectoryPath, this.OpenApiToolsDirectoryPath); + var spectralManager = new SpectralManager(processWrapper, loggerWrapper, this.OpenApiToolsDirectoryPath); this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiWebApiAssemblyPath), this.OpenApiWebApiAssemblyPath); this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiToolsDirectoryPath), this.OpenApiToolsDirectoryPath); @@ -74,12 +53,12 @@ protected override async Task ExecuteAsync(CancellationToken cancellationT await this.GeneratePublicNugetSource(); // Install Swagger CLI - await this._swaggerManager.InstallSwaggerCliAsync(cancellationToken); + await swaggerManager.InstallSwaggerCliAsync(cancellationToken); - // await this._swaggerManager.RunSwaggerAsync(this.OpenApiSwaggerDocumentNames, cancellationToken); + // await swaggerManager.RunSwaggerAsync(this.OpenApiSwaggerDocumentNames, cancellationToken); // Install spectral - await this._spectralManager.InstallSpectralAsync(cancellationToken); + await spectralManager.InstallSpectralAsync(cancellationToken); // Install oasdiff } From 9122cbffda82cc59393db39442fce7855aa8f8b2 Mon Sep 17 00:00:00 2001 From: Rushikesh Vyas Date: Thu, 2 Nov 2023 01:33:05 -0400 Subject: [PATCH 09/12] Addressed few of the PR comments --- .../{Exceptions => }/OpenApiTaskFailedException.cs | 2 +- src/Workleap.OpenApi.MSBuild/SpectralManager.cs | 12 ++++-------- src/Workleap.OpenApi.MSBuild/SwaggerManager.cs | 6 ++---- src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs | 10 +++------- 4 files changed, 10 insertions(+), 20 deletions(-) rename src/Workleap.OpenApi.MSBuild/{Exceptions => }/OpenApiTaskFailedException.cs (74%) diff --git a/src/Workleap.OpenApi.MSBuild/Exceptions/OpenApiTaskFailedException.cs b/src/Workleap.OpenApi.MSBuild/OpenApiTaskFailedException.cs similarity index 74% rename from src/Workleap.OpenApi.MSBuild/Exceptions/OpenApiTaskFailedException.cs rename to src/Workleap.OpenApi.MSBuild/OpenApiTaskFailedException.cs index b9c0246..56b0066 100644 --- a/src/Workleap.OpenApi.MSBuild/Exceptions/OpenApiTaskFailedException.cs +++ b/src/Workleap.OpenApi.MSBuild/OpenApiTaskFailedException.cs @@ -1,4 +1,4 @@ -namespace Workleap.OpenApi.MSBuild.Exceptions; +namespace Workleap.OpenApi.MSBuild; public class OpenApiTaskFailedException : Exception { diff --git a/src/Workleap.OpenApi.MSBuild/SpectralManager.cs b/src/Workleap.OpenApi.MSBuild/SpectralManager.cs index 8bc21b3..3aae4db 100644 --- a/src/Workleap.OpenApi.MSBuild/SpectralManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SpectralManager.cs @@ -1,6 +1,5 @@ using System.Runtime.InteropServices; using Microsoft.Build.Framework; -using Workleap.OpenApi.MSBuild.Exceptions; namespace Workleap.OpenApi.MSBuild; @@ -10,15 +9,13 @@ internal sealed class SpectralManager : ISpectralManager private readonly IProcessWrapper _processWrapper; private readonly ILoggerWrapper _loggerWrapper; - private readonly string _openApiToolsDirectoryPath; private readonly string _toolDirectory; public SpectralManager(IProcessWrapper processWrapper, ILoggerWrapper loggerWrapper, string openApiToolsDirectoryPath) { this._processWrapper = processWrapper; this._loggerWrapper = loggerWrapper; - this._openApiToolsDirectoryPath = openApiToolsDirectoryPath; - this._toolDirectory = Path.Combine(openApiToolsDirectoryPath, $"spectral/{SpectralVersion}"); + this._toolDirectory = Path.Combine(openApiToolsDirectoryPath, "spectral", SpectralVersion); } public async Task InstallSpectralAsync(CancellationToken cancellationToken) @@ -28,12 +25,11 @@ public async Task InstallSpectralAsync(CancellationToken cancellationToken) var url = $"https://github.com/stoplightio/spectral/releases/download/v{SpectralVersion}/{executableFileName}"; - await this.DownloadFileAsync(url, $"{this._toolDirectory}/{executableFileName}", cancellationToken); + await this.DownloadFileAsync(url, Path.Combine(this._toolDirectory, executableFileName), cancellationToken); } private void CreateRequiredDirectories() { - Directory.CreateDirectory(Path.Combine(this._openApiToolsDirectoryPath, "spectral")); Directory.CreateDirectory(this._toolDirectory); } @@ -120,7 +116,7 @@ private static string GetOperatingSystem() return "win"; } - return "unknown"; + throw new OpenApiTaskFailedException("Unknown operating system encountered"); } private static string GetArchitecture() @@ -135,6 +131,6 @@ private static string GetArchitecture() return "arm64"; } - return "unknown"; + throw new OpenApiTaskFailedException("Unknown processor architecture encountered"); } } \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs index 6fcf244..de26eb0 100644 --- a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -1,6 +1,4 @@ using System.Runtime.InteropServices; -using Microsoft.Build.Framework; -using Workleap.OpenApi.MSBuild.Exceptions; namespace Workleap.OpenApi.MSBuild; @@ -17,7 +15,7 @@ public SwaggerManager(IProcessWrapper processWrapper, ILoggerWrapper loggerWrapp this._processWrapper = processWrapper; this._loggerWrapper = loggerWrapper; this._openApiWebApiAssemblyPath = openApiWebApiAssemblyPath; - this._swaggerDirectory = Path.Combine(openApiToolsDirectoryPath, $"swagger/{SwaggerVersion}"); + this._swaggerDirectory = Path.Combine(openApiToolsDirectoryPath, "swagger", SwaggerVersion); } public async Task RunSwaggerAsync(string[] openApiSwaggerDocumentNames, CancellationToken cancellationToken) @@ -39,7 +37,7 @@ public async Task InstallSwaggerCliAsync(CancellationToken cancellationToken) var retryCount = 0; while (retryCount < 2) { - var exitCode = await this._processWrapper.RunProcessAsync("dotnet", new[] { "tool", "update", "Swashbuckle.AspNetCore.Cli", "--tool-path", this._swaggerDirectory, "--version", "6.5.0" }, cancellationToken); + var exitCode = await this._processWrapper.RunProcessAsync("dotnet", new[] { "tool", "update", "Swashbuckle.AspNetCore.Cli", "--tool-path", this._swaggerDirectory, "--version", SwaggerVersion }, cancellationToken); if (exitCode != 0 && retryCount != 1) { diff --git a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs index 6d37067..43d799e 100644 --- a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs +++ b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs @@ -1,8 +1,4 @@ -using System.Runtime.InteropServices; -using CliWrap; -using CliWrap.Buffered; -using Microsoft.Build.Framework; -using Workleap.OpenApi.MSBuild.Exceptions; +using Microsoft.Build.Framework; namespace Workleap.OpenApi.MSBuild; @@ -43,7 +39,7 @@ protected override async Task ExecuteAsync(CancellationToken cancellationT if (this.OpenApiSpecificationFiles.Length != this.OpenApiSwaggerDocumentNames.Length) { - this.Log.LogWarning("OpenApiSpecificationFiles and OpenApiSwaggerDocumentNames should have the same lenght", this.OpenApiWebApiAssemblyPath); + this.Log.LogWarning("{0} and {1} should have the same length", nameof(this.OpenApiSpecificationFiles), nameof(this.OpenApiSwaggerDocumentNames)); return false; } @@ -64,7 +60,7 @@ protected override async Task ExecuteAsync(CancellationToken cancellationT } catch (OpenApiTaskFailedException e) { - this.Log.LogWarning("OpenApi validation could not be done. {0}", e.Message); + this.Log.LogWarning("An error occured while validating the OpenAPI specification: {0}", e.Message); } return true; From b9850f2ce83ffd3e0fa118aa623fed5683bf1010 Mon Sep 17 00:00:00 2001 From: Xavier Date: Thu, 2 Nov 2023 10:22:38 -0400 Subject: [PATCH 10/12] PR corrections --- .../ILoggerWrapper.cs | 4 +- src/Workleap.OpenApi.MSBuild/LoggerWrapper.cs | 10 +++ .../OpenApiTaskFailedException.cs | 0 .../ProcessWrapper.cs | 2 + .../SpectralManager.cs | 66 +++---------------- .../SwaggerManager.cs | 6 +- .../ValidateOpenApiTask.cs | 15 ++--- 7 files changed, 32 insertions(+), 71 deletions(-) rename src/Workleap.OpenApi.MSBuild/{Exceptions => }/OpenApiTaskFailedException.cs (100%) diff --git a/src/Workleap.OpenApi.MSBuild/ILoggerWrapper.cs b/src/Workleap.OpenApi.MSBuild/ILoggerWrapper.cs index 2a5cf05..7fcdad0 100644 --- a/src/Workleap.OpenApi.MSBuild/ILoggerWrapper.cs +++ b/src/Workleap.OpenApi.MSBuild/ILoggerWrapper.cs @@ -4,5 +4,7 @@ namespace Workleap.OpenApi.MSBuild; public interface ILoggerWrapper { - TaskLoggingHelper Helper { get; set; } + void LogWarning(string message, params object[] messageArgs); + + void LogMessage(string message, params object[] messageArgs); } \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/LoggerWrapper.cs b/src/Workleap.OpenApi.MSBuild/LoggerWrapper.cs index f7587d1..95b59b3 100644 --- a/src/Workleap.OpenApi.MSBuild/LoggerWrapper.cs +++ b/src/Workleap.OpenApi.MSBuild/LoggerWrapper.cs @@ -10,4 +10,14 @@ public LoggerWrapper(TaskLoggingHelper helper) } public TaskLoggingHelper Helper { get; set; } + + public void LogWarning(string message, params object[] messageArgs) + { + this.Helper.LogWarning(message, messageArgs); + } + + public void LogMessage(string message, params object[] messageArgs) + { + this.Helper.LogMessage(message, messageArgs); + } } \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/Exceptions/OpenApiTaskFailedException.cs b/src/Workleap.OpenApi.MSBuild/OpenApiTaskFailedException.cs similarity index 100% rename from src/Workleap.OpenApi.MSBuild/Exceptions/OpenApiTaskFailedException.cs rename to src/Workleap.OpenApi.MSBuild/OpenApiTaskFailedException.cs diff --git a/src/Workleap.OpenApi.MSBuild/ProcessWrapper.cs b/src/Workleap.OpenApi.MSBuild/ProcessWrapper.cs index bbeb9f6..1ae50d6 100644 --- a/src/Workleap.OpenApi.MSBuild/ProcessWrapper.cs +++ b/src/Workleap.OpenApi.MSBuild/ProcessWrapper.cs @@ -18,6 +18,8 @@ public async Task RunProcessAsync(string filename, string[] arguments, Canc .WithWorkingDirectory(this._workingDirectory) .WithValidation(CommandResultValidation.None) .WithArguments(arguments) + .WithStandardOutputPipe(PipeTarget.ToStream(Console.OpenStandardOutput())) + .WithStandardErrorPipe(PipeTarget.ToStream(Console.OpenStandardError())) .ExecuteBufferedAsync(cancellationToken); return result.ExitCode; diff --git a/src/Workleap.OpenApi.MSBuild/SpectralManager.cs b/src/Workleap.OpenApi.MSBuild/SpectralManager.cs index 8bc21b3..c9f6fcb 100644 --- a/src/Workleap.OpenApi.MSBuild/SpectralManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SpectralManager.cs @@ -8,36 +8,28 @@ internal sealed class SpectralManager : ISpectralManager { private const string SpectralVersion = "6.11.0"; - private readonly IProcessWrapper _processWrapper; private readonly ILoggerWrapper _loggerWrapper; - private readonly string _openApiToolsDirectoryPath; + private readonly IHttpClientWrapper _httpClientWrapper; private readonly string _toolDirectory; - public SpectralManager(IProcessWrapper processWrapper, ILoggerWrapper loggerWrapper, string openApiToolsDirectoryPath) + public SpectralManager(LoggerWrapper loggerWrapper, string openApiToolsDirectoryPath, IHttpClientWrapper httpClientWrapper) { - this._processWrapper = processWrapper; this._loggerWrapper = loggerWrapper; - this._openApiToolsDirectoryPath = openApiToolsDirectoryPath; - this._toolDirectory = Path.Combine(openApiToolsDirectoryPath, $"spectral/{SpectralVersion}"); + this._httpClientWrapper = httpClientWrapper; + this._toolDirectory = Path.Combine(openApiToolsDirectoryPath, "spectral", SpectralVersion); } public async Task InstallSpectralAsync(CancellationToken cancellationToken) { - this.CreateRequiredDirectories(); - var executableFileName = this.GetSpectralFileName(); + Directory.CreateDirectory(this._toolDirectory); + var executableFileName = GetSpectralFileName(); var url = $"https://github.com/stoplightio/spectral/releases/download/v{SpectralVersion}/{executableFileName}"; - await this.DownloadFileAsync(url, $"{this._toolDirectory}/{executableFileName}", cancellationToken); + await this._httpClientWrapper.DownloadFileToDestinationAsync(url, Path.Combine(this._toolDirectory, executableFileName), cancellationToken); } - private void CreateRequiredDirectories() - { - Directory.CreateDirectory(Path.Combine(this._openApiToolsDirectoryPath, "spectral")); - Directory.CreateDirectory(this._toolDirectory); - } - - private string GetSpectralFileName() + private static string GetSpectralFileName() { var osType = GetOperatingSystem(); var architecture = GetArchitecture(); @@ -48,7 +40,6 @@ private string GetSpectralFileName() if (distro.Contains("Alpine Linux")) { osType = "alpine"; - this._loggerWrapper.Helper.LogMessage(MessageImportance.Low, "Installing on Alpine Linux."); } } @@ -57,52 +48,11 @@ private string GetSpectralFileName() if (osType == "win") { fileName = "spectral.exe"; - this._loggerWrapper.Helper.LogMessage(MessageImportance.Low, "Installing on Windows."); } return fileName; } - private async Task DownloadFileAsync(string url, string destination, CancellationToken cancellationToken) - { - if (File.Exists(destination)) - { - this._loggerWrapper.Helper.LogMessage(MessageImportance.Low, "File already exist"); - - return; - } - - using var httpClient = new HttpClient(); - using var response = await httpClient.GetAsync(url, cancellationToken); - response.EnsureSuccessStatusCode(); - - if (!response.IsSuccessStatusCode) - { - using var retryResponse = await httpClient.GetAsync(url, cancellationToken); - - if (retryResponse.IsSuccessStatusCode) - { - await SaveFileFromResponseAsync(destination, retryResponse, cancellationToken); - } - else - { - throw new OpenApiTaskFailedException("Spectral could not be installed."); - } - } - else - { - await SaveFileFromResponseAsync(destination, response, cancellationToken); - } - } - - private static async Task SaveFileFromResponseAsync(string destination, HttpResponseMessage response, CancellationToken cancellationToken) - { - using var fileTarget = new FileStream(destination, FileMode.Create, FileAccess.Write, FileShare.None); - using var fileStream = await response.Content.ReadAsStreamAsync(); - - await fileStream.CopyToAsync(fileTarget, 1024, cancellationToken); - } - private static string GetOperatingSystem() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) diff --git a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs index 6fcf244..65a6342 100644 --- a/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs +++ b/src/Workleap.OpenApi.MSBuild/SwaggerManager.cs @@ -17,7 +17,7 @@ public SwaggerManager(IProcessWrapper processWrapper, ILoggerWrapper loggerWrapp this._processWrapper = processWrapper; this._loggerWrapper = loggerWrapper; this._openApiWebApiAssemblyPath = openApiWebApiAssemblyPath; - this._swaggerDirectory = Path.Combine(openApiToolsDirectoryPath, $"swagger/{SwaggerVersion}"); + this._swaggerDirectory = Path.Combine(openApiToolsDirectoryPath, "swagger", SwaggerVersion); } public async Task RunSwaggerAsync(string[] openApiSwaggerDocumentNames, CancellationToken cancellationToken) @@ -39,11 +39,11 @@ public async Task InstallSwaggerCliAsync(CancellationToken cancellationToken) var retryCount = 0; while (retryCount < 2) { - var exitCode = await this._processWrapper.RunProcessAsync("dotnet", new[] { "tool", "update", "Swashbuckle.AspNetCore.Cli", "--tool-path", this._swaggerDirectory, "--version", "6.5.0" }, cancellationToken); + var exitCode = await this._processWrapper.RunProcessAsync("dotnet", new[] { "tool", "update", "Swashbuckle.AspNetCore.Cli", "--tool-path", this._swaggerDirectory, "--version", SwaggerVersion }, cancellationToken); if (exitCode != 0 && retryCount != 1) { - this._loggerWrapper.Helper.LogWarning("Swashbuckle download failed. Retrying once more..."); + this._loggerWrapper.LogWarning("Swashbuckle download failed. Retrying once more..."); retryCount++; continue; } diff --git a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs index 6d37067..8a5c882 100644 --- a/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs +++ b/src/Workleap.OpenApi.MSBuild/ValidateOpenApiTask.cs @@ -33,7 +33,9 @@ protected override async Task 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 spectralManager = new SpectralManager(processWrapper, loggerWrapper, this.OpenApiToolsDirectoryPath); + using var httpClientWrapper = new HttpClientWrapper(); + + var spectralManager = new SpectralManager(loggerWrapper, this.OpenApiToolsDirectoryPath, httpClientWrapper); this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiWebApiAssemblyPath), this.OpenApiWebApiAssemblyPath); this.Log.LogMessage(MessageImportance.Low, "{0} = '{1}'", nameof(this.OpenApiToolsDirectoryPath), this.OpenApiToolsDirectoryPath); @@ -43,7 +45,7 @@ protected override async Task ExecuteAsync(CancellationToken cancellationT if (this.OpenApiSpecificationFiles.Length != this.OpenApiSwaggerDocumentNames.Length) { - this.Log.LogWarning("OpenApiSpecificationFiles and OpenApiSwaggerDocumentNames should have the same lenght", this.OpenApiWebApiAssemblyPath); + this.Log.LogWarning("You must provide the same amount of open api specification file names and swagger document file names"); return false; } @@ -52,19 +54,14 @@ protected override async Task ExecuteAsync(CancellationToken cancellationT { await this.GeneratePublicNugetSource(); - // Install Swagger CLI await swaggerManager.InstallSwaggerCliAsync(cancellationToken); - - // await swaggerManager.RunSwaggerAsync(this.OpenApiSwaggerDocumentNames, cancellationToken); - - // Install spectral await spectralManager.InstallSpectralAsync(cancellationToken); - // Install oasdiff + // await swaggerManager.RunSwaggerAsync(this.OpenApiSwaggerDocumentNames, cancellationToken); } catch (OpenApiTaskFailedException e) { - this.Log.LogWarning("OpenApi validation could not be done. {0}", e.Message); + this.Log.LogWarning("OpenAPI validation could not be done. {0}", e.Message); } return true; From 3df41f061572d7b9bea4b5eb4b225b9a68612eb1 Mon Sep 17 00:00:00 2001 From: Xavier Date: Thu, 2 Nov 2023 10:37:46 -0400 Subject: [PATCH 11/12] Add missing files --- .../HttpClientWrapper.cs | 47 +++++++++++++++++++ .../IHttpClientWrapper.cs | 6 +++ 2 files changed, 53 insertions(+) create mode 100644 src/Workleap.OpenApi.MSBuild/HttpClientWrapper.cs create mode 100644 src/Workleap.OpenApi.MSBuild/IHttpClientWrapper.cs diff --git a/src/Workleap.OpenApi.MSBuild/HttpClientWrapper.cs b/src/Workleap.OpenApi.MSBuild/HttpClientWrapper.cs new file mode 100644 index 0000000..0caad97 --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/HttpClientWrapper.cs @@ -0,0 +1,47 @@ +using System.Net; + +namespace Workleap.OpenApi.MSBuild; + +internal sealed class HttpClientWrapper : IHttpClientWrapper, IDisposable +{ + private readonly HttpClient _httpClient; + + public HttpClientWrapper() + { + this._httpClient = new HttpClient(); + } + + public async Task DownloadFileToDestinationAsync(string url, string destination, CancellationToken cancellationToken) + { + using var responseStream = await this._httpClient.GetStreamAsync(url); + + if (responseStream == null) + { + 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 + { + await SaveFileFromResponseAsync(destination, responseStream, cancellationToken); + } + } + + private static async Task SaveFileFromResponseAsync(string destination, Stream responseStream, CancellationToken cancellationToken) + { + using var fileTarget = new FileStream(destination, FileMode.Create, FileAccess.Write, FileShare.None); + + await responseStream.CopyToAsync(fileTarget, 81920, cancellationToken); + } + + public void Dispose() + { + this._httpClient.Dispose(); + } +} \ No newline at end of file diff --git a/src/Workleap.OpenApi.MSBuild/IHttpClientWrapper.cs b/src/Workleap.OpenApi.MSBuild/IHttpClientWrapper.cs new file mode 100644 index 0000000..0ecd3e1 --- /dev/null +++ b/src/Workleap.OpenApi.MSBuild/IHttpClientWrapper.cs @@ -0,0 +1,6 @@ +namespace Workleap.OpenApi.MSBuild; + +internal interface IHttpClientWrapper +{ + Task DownloadFileToDestinationAsync(string url, string destination, CancellationToken cancellationToken); +} \ No newline at end of file From abe63a6a4b859e20a66c83656804f214c5218acb Mon Sep 17 00:00:00 2001 From: Xavier Date: Thu, 2 Nov 2023 10:59:57 -0400 Subject: [PATCH 12/12] Removed http client wrapper constructor --- src/Workleap.OpenApi.MSBuild/HttpClientWrapper.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Workleap.OpenApi.MSBuild/HttpClientWrapper.cs b/src/Workleap.OpenApi.MSBuild/HttpClientWrapper.cs index 0caad97..1146a1e 100644 --- a/src/Workleap.OpenApi.MSBuild/HttpClientWrapper.cs +++ b/src/Workleap.OpenApi.MSBuild/HttpClientWrapper.cs @@ -4,12 +4,7 @@ namespace Workleap.OpenApi.MSBuild; internal sealed class HttpClientWrapper : IHttpClientWrapper, IDisposable { - private readonly HttpClient _httpClient; - - public HttpClientWrapper() - { - this._httpClient = new HttpClient(); - } + private readonly HttpClient _httpClient = new(); public async Task DownloadFileToDestinationAsync(string url, string destination, CancellationToken cancellationToken) {