Skip to content

Commit

Permalink
[idp-1511] Support updating spectral without ejecting (#59)
Browse files Browse the repository at this point in the history
* Working stades

* Refactor

* Fixing bugs, remaining cleanup

* Cleanup

* Missing document

* Cleanup in system test

* Add debugging logs

* Put back incremental build

* Try pinpoint problem source

* Try addig a nuget.config

* Cleanup

* Revert blind fix-try

* Fix targets

* Cleanup

* CR fixes

* Add unit test for RulesetManager

* Fix sealed

---------

Co-authored-by: Mathieu Gamache <[email protected]>
  • Loading branch information
PrincessMadMath and Mathieu Gamache authored Jun 12, 2024
1 parent f90150d commit 8cd45bb
Show file tree
Hide file tree
Showing 28 changed files with 594 additions and 176 deletions.
41 changes: 36 additions & 5 deletions Run-SystemTest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Process {

Exec { & dotnet add package Workleap.OpenApi.MSBuild --prerelease --source $openApiMsBuildSource }

Write-Information "dotnet build -c Release $extraArgs"
$buildProcess = Start-Process -FilePath "dotnet" -ArgumentList "build -c Release $extraArgs" -NoNewWindow -PassThru -Wait

Exec { & dotnet remove package Workleap.OpenApi.MSBuild }
Expand All @@ -27,6 +28,8 @@ Process {
Write-Error "The build for project $projectPath was expected to fail, but it succeeded."
} elseif (!$isFailureExpected -and $buildProcess.ExitCode -ne 0) {
Write-Error "The build for project $projectPath was expected to succeed, but it failed."
} else {
Write-Information "The validation succeeded."
}
}
finally {
Expand Down Expand Up @@ -54,16 +57,44 @@ Process {
# Build the OpenApi.MSBuild package to be used in the system tests
Exec { & dotnet pack -c Release -o "$outputDir" }

### Testing Generate Contract Mode ###

# Then Should Successfully Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $genericSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiDevelopmentMode=GenerateContract"
# When using legacy name / Then Should Successfully Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $genericSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiDevelopmentMode=CodeFirst"
# When Comparing Spec and No Diff Error / Then Should Successfully Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $genericSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiDevelopmentMode=GenerateContract;OpenApiCompareCodeAgainstSpecFile=true"
# When Comparing Spec and Have Diff / Then Should Fail Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $oasDiffErrorSysTestDir -isFailureExpected $true -extraArgs "/p:OpenApiDevelopmentMode=GenerateContract;OpenApiCompareCodeAgainstSpecFile=true"

### Testing Compare Contract Mode ###

# Given no diff / Then Should Successfully Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $genericSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiDevelopmentMode=ValidateContract"
# Given no diff / When using legacy name / Then Should Successfully Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $genericSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiDevelopmentMode=ContractFirst"
BuildProject -openApiMsBuildSource $outputDir -projectPath $genericSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiDevelopmentMode=ValidateContract;OpenApiCompareCodeAgainstSpecFile=true"
BuildProject -openApiMsBuildSource $outputDir -projectPath $genericSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiDevelopmentMode=GenerateContract"
# Given diff / Then Should Fail Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $oasDiffErrorSysTestDir -isFailureExpected $true -extraArgs "/p:OpenApiDevelopmentMode=ValidateContract"
# Given diff / When OpenApiTreatWarningsAsErrors=false / Then Should Successfully Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $oasDiffErrorSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiTreatWarningsAsErrors=false"

### Testing Spectral Validation ###

# Given no spectral violation / When using frontend profile / Then Should Successfully Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $genericSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiDevelopmentMode=GenerateContract;OpenApiServiceProfile=frontend"
# Given no spectral violation / When using invalid profile / Then Should Fail Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $genericSysTestDir -isFailureExpected $true -extraArgs "/p:OpenApiDevelopmentMode=GenerateContract;OpenApiServiceProfile=scrap"
BuildProject -openApiMsBuildSource $outputDir -projectPath $oasDiffErrorSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiTreatWarningsAsErrors=false"
BuildProject -openApiMsBuildSource $outputDir -projectPath $spectralErrorSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiTreatWarningsAsErrors=false"
BuildProject -openApiMsBuildSource $outputDir -projectPath $oasDiffErrorSysTestDir -isFailureExpected $true
# Given spectral violations / When using default ruleset / Then Should Fail Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $spectralErrorSysTestDir -isFailureExpected $true
# Given spectral violations / When using default ruleset And OpenApiTreatWarningsAsErrors=false / Then Should Successfully Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $spectralErrorSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiTreatWarningsAsErrors=false"
# Given workleap spectral violations / When ejecting ruleset / Then Should Successfully Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $spectralErrorSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiSpectralRulesetUrl=./eject.spectral.yaml"
# Given spectral violations / When overriding ruleset without disabling problematic ruleset / Then Should Fail Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $spectralErrorSysTestDir -isFailureExpected $true -extraArgs "/p:OpenApiSpectralRulesetUrl=./override.spectral.yaml"
# Given spectral violations / When overriding ruleset while disabling problematic ruleset / Then Should Successfully Build
BuildProject -openApiMsBuildSource $outputDir -projectPath $spectralErrorSysTestDir -isFailureExpected $false -extraArgs "/p:OpenApiSpectralRulesetUrl=./override.fixed.spectral.yaml"
}
finally {
Pop-Location
Expand Down
14 changes: 10 additions & 4 deletions src/WebApiDebugger/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,31 @@
"ValidateContractWithoutCompare": {
"commandName": "Executable",
"executablePath": "dotnet",
"commandLineArgs": "msbuild /t:ValidateOpenApi /p:OpenApiDevelopmentMode=ValidateContract /p:OpenApiCompareCodeAgainstSpecFile=false",
"commandLineArgs": "msbuild /t:ValidateOpenApi /p:OpenApiEnabled=true /p:OpenApiDevelopmentMode=ValidateContract /p:OpenApiCompareCodeAgainstSpecFile=false",
"workingDirectory": "$(ProjectDir)"
},
"ValidateContractWithCompare": {
"commandName": "Executable",
"executablePath": "dotnet",
"commandLineArgs": "msbuild /t:ValidateOpenApi /p:OpenApiDevelopmentMode=ValidateContract /p:OpenApiCompareCodeAgainstSpecFile=true",
"commandLineArgs": "msbuild /t:ValidateOpenApi /p:OpenApiEnabled=true /p:OpenApiDevelopmentMode=ValidateContract /p:OpenApiCompareCodeAgainstSpecFile=true",
"workingDirectory": "$(ProjectDir)"
},
"GenerateContract": {
"commandName": "Executable",
"executablePath": "dotnet",
"commandLineArgs": "msbuild /t:ValidateOpenApi /p:OpenApiDevelopmentMode=GenerateContract",
"commandLineArgs": "msbuild /t:ValidateOpenApi /p:OpenApiEnabled=true /p:OpenApiEnabled=true /p:OpenApiDevelopmentMode=GenerateContract",
"workingDirectory": "$(ProjectDir)"
},
"CustomSpectral": {
"commandName": "Executable",
"executablePath": "dotnet",
"commandLineArgs": "msbuild /t:ValidateOpenApi /p:OpenApiEnabled=true /p:OpenApiDevelopmentMode=GenerateContract /p:OpenApiSpectralRulesetUrl=./custom.spectral.yaml",
"workingDirectory": "$(ProjectDir)"
},
"GenerateContractOnCI": {
"commandName": "Executable",
"executablePath": "dotnet",
"commandLineArgs": "msbuild /t:ValidateOpenApi /p:OpenApiDevelopmentMode=GenerateContract /p:OpenApiCompareCodeAgainstSpecFile=true",
"commandLineArgs": "msbuild /t:ValidateOpenApi /p:OpenApiEnabled=true /p:OpenApiDevelopmentMode=GenerateContract /p:OpenApiCompareCodeAgainstSpecFile=true",
"workingDirectory": "$(ProjectDir)"
},
"WebApiDebugger": {
Expand Down
8 changes: 7 additions & 1 deletion src/WebApiDebugger/WebApiDebugger.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>

<PropertyGroup>
<OpenApiEnabled>false</OpenApiEnabled>
<OpenApiDebuggingEnabled>true</OpenApiDebuggingEnabled>
<OpenApiDevelopmentMode>GenerateContract</OpenApiDevelopmentMode>
</PropertyGroup>
Expand All @@ -21,5 +21,11 @@
<ProjectReference Include="..\Workleap.OpenApi.MSBuild\Workleap.OpenApi.MSBuild.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
<None Update="custom.spectral.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<Import Project="..\Workleap.OpenApi.MSBuild\msbuild\build\Workleap.OpenApi.MSBuild.targets" />
</Project>
1 change: 1 addition & 0 deletions src/WebApiDebugger/custom.spectral.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extends: [https://raw.githubusercontent.com/gsoft-inc/wl-api-guidelines/0.1.0/.spectral.yaml]
12 changes: 6 additions & 6 deletions src/Workleap.OpenApi.MSBuild.sln
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.MsBuild.SystemTest.S
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.MsBuild.SystemTest.GenericTest", "tests\WebApi.MsBuild.SystemTest.GenericTest\WebApi.MsBuild.SystemTest.GenericTest.csproj", "{502CEF5F-350E-4498-BA3C-3C09C9620737}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workleap.OpenApi.MSBuild.Tests", "tests\Workleap.OpenApi.MSBuild.Tests\Workleap.OpenApi.MSBuild.Tests.csproj", "{7CD01912-BA30-4F96-A397-E79A7E670E27}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workleap.OpenApi.MSBuild.Tests", "tests\Workleap.OpenApi.MSBuild.Tests\Workleap.OpenApi.MSBuild.Tests.csproj", "{E5EC1EFE-2271-4AFF-BED6-FEFD2B301423}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -51,10 +51,10 @@ Global
{502CEF5F-350E-4498-BA3C-3C09C9620737}.Debug|Any CPU.Build.0 = Debug|Any CPU
{502CEF5F-350E-4498-BA3C-3C09C9620737}.Release|Any CPU.ActiveCfg = Release|Any CPU
{502CEF5F-350E-4498-BA3C-3C09C9620737}.Release|Any CPU.Build.0 = Release|Any CPU
{7CD01912-BA30-4F96-A397-E79A7E670E27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CD01912-BA30-4F96-A397-E79A7E670E27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CD01912-BA30-4F96-A397-E79A7E670E27}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CD01912-BA30-4F96-A397-E79A7E670E27}.Release|Any CPU.Build.0 = Release|Any CPU
{E5EC1EFE-2271-4AFF-BED6-FEFD2B301423}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5EC1EFE-2271-4AFF-BED6-FEFD2B301423}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5EC1EFE-2271-4AFF-BED6-FEFD2B301423}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5EC1EFE-2271-4AFF-BED6-FEFD2B301423}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -64,7 +64,7 @@ Global
{3909D38E-7584-4206-9CDC-3E56203BE6DB} = {4DDE83BF-D190-4CC9-AD36-E9250DABB27D}
{C42C2836-4997-49D3-9BC6-E6A0E1B8C472} = {4DDE83BF-D190-4CC9-AD36-E9250DABB27D}
{502CEF5F-350E-4498-BA3C-3C09C9620737} = {4DDE83BF-D190-4CC9-AD36-E9250DABB27D}
{7CD01912-BA30-4F96-A397-E79A7E670E27} = {4DDE83BF-D190-4CC9-AD36-E9250DABB27D}
{E5EC1EFE-2271-4AFF-BED6-FEFD2B301423} = {4DDE83BF-D190-4CC9-AD36-E9250DABB27D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BCD029BE-FB3E-45A2-8DD4-FDE33D87544D}
Expand Down
50 changes: 43 additions & 7 deletions src/Workleap.OpenApi.MSBuild/GenerateContractProcess.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Build.Framework;
using Workleap.OpenApi.MSBuild.Spectral;

namespace Workleap.OpenApi.MSBuild;

Expand All @@ -11,15 +12,26 @@ namespace Workleap.OpenApi.MSBuild;
internal class GenerateContractProcess
{
private readonly ILoggerWrapper _loggerWrapper;
private readonly SpectralManager _spectralManager;
private readonly SpectralInstaller _spectralInstaller;
private readonly SpectralRulesetManager _spectralRulesetManager;
private readonly SpectralRunner _spectralRunner;
private readonly SwaggerManager _swaggerManager;
private readonly SpecGeneratorManager _specGeneratorManager;
private readonly OasdiffManager _oasdiffManager;

internal GenerateContractProcess(ILoggerWrapper loggerWrapper, SpectralManager spectralManager, SwaggerManager swaggerManager, SpecGeneratorManager specGeneratorManager, OasdiffManager oasdiffManager)
internal GenerateContractProcess(
ILoggerWrapper loggerWrapper,
SpectralInstaller spectralInstaller,
SpectralRulesetManager spectralRulesetManager,
SpectralRunner spectralRunner,
SwaggerManager swaggerManager,
SpecGeneratorManager specGeneratorManager,
OasdiffManager oasdiffManager)
{
this._loggerWrapper = loggerWrapper;
this._spectralManager = spectralManager;
this._spectralInstaller = spectralInstaller;
this._spectralRulesetManager = spectralRulesetManager;
this._spectralRunner = spectralRunner;
this._swaggerManager = swaggerManager;
this._specGeneratorManager = specGeneratorManager;
this._oasdiffManager = oasdiffManager;
Expand All @@ -38,7 +50,7 @@ internal async Task Execute(
CancellationToken cancellationToken)
{
this._loggerWrapper.LogMessage("Installing dependencies...");
await this.InstallDependencies(mode, cancellationToken);
var dependenciesResult = await this.InstallDependencies(mode, cancellationToken);

this._loggerWrapper.LogMessage("Running Swagger...");
var generateOpenApiDocsPath = (await this._swaggerManager.RunSwaggerAsync(openApiSwaggerDocumentNames, cancellationToken)).ToList();
Expand All @@ -55,15 +67,21 @@ internal async Task Execute(
}

this._loggerWrapper.LogMessage("Running Spectral...");
await this._spectralManager.RunSpectralAsync(generateOpenApiDocsPath, cancellationToken);
await this._spectralRunner.RunSpectralAsync(generateOpenApiDocsPath, dependenciesResult.SpectralExecutablePath, dependenciesResult.SpectralRulesetPath, cancellationToken);
}

private async Task InstallDependencies(
private async Task<DependenciesResult> InstallDependencies(
GenerateContractMode mode,
CancellationToken cancellationToken)
{
var installationTasks = new List<Task>();
installationTasks.Add(this._spectralManager.InstallSpectralAsync(cancellationToken));

var spectralRulesetTask = this._spectralRulesetManager.GetLocalSpectralRulesetFile(cancellationToken);
installationTasks.Add(spectralRulesetTask);

var spectralInstallerTask = this._spectralInstaller.InstallSpectralAsync(cancellationToken);
installationTasks.Add(spectralInstallerTask);

installationTasks.Add(this._swaggerManager.InstallSwaggerCliAsync(cancellationToken));

if (mode == GenerateContractMode.SpecComparison)
Expand All @@ -73,5 +91,23 @@ private async Task InstallDependencies(

await Task.WhenAll(installationTasks);
this._loggerWrapper.LogMessage("Finished installing OpenAPI dependencies.", MessageImportance.High);

var spectralRulesetPath = await spectralRulesetTask;
var spectralExecutablePath = await spectralInstallerTask;

return new DependenciesResult(spectralRulesetPath, spectralExecutablePath);
}

private class DependenciesResult
{
public DependenciesResult(string spectralRulesetPath, string spectralExecutablePath)
{
this.SpectralRulesetPath = spectralRulesetPath;
this.SpectralExecutablePath = spectralExecutablePath;
}

public string SpectralRulesetPath { get; }

public string SpectralExecutablePath { get; }
}
}
8 changes: 0 additions & 8 deletions src/Workleap.OpenApi.MSBuild/ISpectralManager.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
using System.Security.Cryptography;

namespace Workleap.OpenApi.MSBuild;
namespace Workleap.OpenApi.MSBuild.Spectral;

internal sealed class SpectralDiffCalculator
/// <summary>
/// Class that contains methods to check if some elements has changed since the last execution.
/// </summary>
internal sealed class DiffCalculator
{
private const string ChecksumExtension = "spectral-checksum";
private const string SpectralRulesetChecksumItemName = "spectral-ruleset-checksum";

private readonly string _spectralOutputDirectoryPath;

public SpectralDiffCalculator(string spectralChecksumOutputDirectoryPath)
public DiffCalculator(string spectralChecksumOutputDirectoryPath)
{
this._spectralOutputDirectoryPath = spectralChecksumOutputDirectoryPath;
}

public bool HasRulesetChangedSinceLastExecution(string spectralRulset)
public bool HasRulesetChangedSinceLastExecution(string spectralRulsetPath)
{
var preciousRulesetChecksum = this.GetItemChecksum(SpectralRulesetChecksumItemName);
var currentRulesetChecksum = GetFileChecksum(spectralRulset);
var currentRulesetChecksum = GetFileChecksum(spectralRulsetPath);

var hasRulesetChanged = !string.Equals(preciousRulesetChecksum, currentRulesetChecksum, StringComparison.OrdinalIgnoreCase);

Expand Down
66 changes: 66 additions & 0 deletions src/Workleap.OpenApi.MSBuild/Spectral/SpectralInstaller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace Workleap.OpenApi.MSBuild.Spectral;

internal class SpectralInstaller
{
private const string SpectralVersion = "6.11.0";
private const string SpectralDownloadUrlFormat = "https://github.com/stoplightio/spectral/releases/download/v{0}/{1}";

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

public SpectralInstaller(
ILoggerWrapper loggerWrapper,
string openApiToolsDirectoryPath,
IHttpClientWrapper httpClientWrapper)
{
this._loggerWrapper = loggerWrapper;
this._httpClientWrapper = httpClientWrapper;
this._spectralDirectory = Path.Combine(openApiToolsDirectoryPath, "spectral", SpectralVersion);
}

/// <summary>
/// Install spectral tool
/// </summary>
/// <returns>Return executable path</returns>
public async Task<string> InstallSpectralAsync(CancellationToken cancellationToken)
{
this._loggerWrapper.LogMessage("Starting Spectral installation.");

Directory.CreateDirectory(this._spectralDirectory);

var executablePath = GetSpectralFileName();
var url = string.Format(SpectralDownloadUrlFormat, SpectralVersion, executablePath);
var destination = Path.Combine(this._spectralDirectory, executablePath);

await this._httpClientWrapper.DownloadFileToDestinationAsync(url, destination, cancellationToken);

this._loggerWrapper.LogMessage("Spectral installation completed.");

return executablePath;
}

private static string GetSpectralFileName()
{
var osType = RuntimeInformationHelper.GetOperatingSystem();
var architecture = RuntimeInformationHelper.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";
}
}

var fileName = $"spectral-{osType}-{architecture}";

if (osType == "windows")
{
fileName = "spectral.exe";
}

return fileName;
}
}
Loading

0 comments on commit 8cd45bb

Please sign in to comment.