Skip to content

Commit

Permalink
Implement SBOM CLI ToolTask (#607)
Browse files Browse the repository at this point in the history
* implement ToolTask

* addressing feedback

* addressing feedback pt. 2

---------

Co-authored-by: vpatakottu <[email protected]>
  • Loading branch information
vpatakottu and vpatakottu authored Jul 10, 2024
1 parent b04a2f5 commit 84441e4
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 105 deletions.
94 changes: 0 additions & 94 deletions src/Microsoft.Sbom.Targets/GenerateSbomTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,78 +111,6 @@ public override bool Execute()
}
}

private string Remove_Spaces_Tabs_Newlines(string value)
{
return value.Replace("\n", string.Empty).Replace("\t", string.Empty).Replace(" ", string.Empty);
}

/// <summary>
/// Ensure all required arguments are non-null/empty,
/// and do not contain whitespaces, tabs, or newline characters.
/// </summary>
/// <returns>True if the required parameters are valid. False otherwise.</returns>
private bool ValidateAndSanitizeRequiredParams()
{
if (string.IsNullOrWhiteSpace(this.BuildDropPath))
{
Log.LogError($"SBOM generation failed: Empty argument detected for {nameof(this.BuildDropPath)}. Please provide a valid path.");
return false;
}

if (string.IsNullOrWhiteSpace(this.PackageSupplier))
{
Log.LogError($"SBOM generation failed: Empty argument detected for {nameof(this.PackageSupplier)}. Please provide a valid supplier name.");
return false;
}

if (string.IsNullOrWhiteSpace(this.PackageName))
{
Log.LogError($"SBOM generation failed: Empty argument detected for {nameof(this.PackageName)}. Please provide a valid name.");
return false;
}

if (string.IsNullOrWhiteSpace(this.PackageVersion))
{
Log.LogError($"SBOM generation failed: Empty argument detected for {nameof(this.PackageVersion)}. Please provide a valid version number.");
return false;
}

if (string.IsNullOrWhiteSpace(this.NamespaceBaseUri))
{
Log.LogError($"SBOM generation failed: Empty argument detected for {nameof(this.NamespaceBaseUri)}. Please provide a valid URI.");
return false;
}

this.PackageSupplier = Remove_Spaces_Tabs_Newlines(this.PackageSupplier);
this.PackageName = Remove_Spaces_Tabs_Newlines(this.PackageName);
this.PackageVersion = Remove_Spaces_Tabs_Newlines(this.PackageVersion);
this.NamespaceBaseUri = this.NamespaceBaseUri.Trim();
this.BuildDropPath = this.BuildDropPath.Trim();

return true;
}

/// <summary>
/// Checks the user's input for Verbosity and assigns the
/// associated EventLevel value for logging.
/// </summary>
private EventLevel ValidateAndAssignVerbosity()
{
if (string.IsNullOrWhiteSpace(this.Verbosity))
{
Log.LogMessage($"No verbosity level specified. Setting verbosity level at \"{EventLevel.LogAlways}\"");
return EventLevel.LogAlways;
}

if (Enum.TryParse(this.Verbosity, true, out EventLevel eventLevel))
{
return eventLevel;
}

Log.LogMessage($"Unrecognized verbosity level specified. Setting verbosity level at \"{EventLevel.LogAlways}\"");
return EventLevel.LogAlways;
}

/// <summary>
/// Check for ManifestInfo and create an SbomSpecification accordingly.
/// </summary>
Expand All @@ -196,26 +124,4 @@ private IList<SbomSpecification> ValidateAndAssignSpecifications()

return null;
}

/// <summary>
/// Ensure a valid NamespaceUriUniquePart is provided.
/// </summary>
/// <returns>True if the Namespace URI unique part is valid. False otherwise.</returns>
private bool ValidateAndSanitizeNamespaceUriUniquePart()
{
// Ensure the NamespaceUriUniquePart is valid if provided.
if (!string.IsNullOrWhiteSpace(this.NamespaceUriUniquePart)
&& (!Guid.TryParse(this.NamespaceUriUniquePart, out _)
|| this.NamespaceUriUniquePart.Equals(Guid.Empty.ToString())))
{
Log.LogError($"SBOM generation failed: NamespaceUriUniquePart '{this.NamespaceUriUniquePart}' must be a valid unique GUID.");
return false;
}
else if (!string.IsNullOrWhiteSpace(this.NamespaceUriUniquePart))
{
this.NamespaceUriUniquePart = this.NamespaceUriUniquePart.Trim();
}

return true;
}
}
6 changes: 2 additions & 4 deletions src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<Target Name="AddSbomToolToPackage" AfterTargets="Build">
<MSBuild Projects="..\Microsoft.Sbom.Tool\Microsoft.Sbom.Tool.csproj" Properties="TargetFramework=$(SbomCLIToolTargetFramework)" Targets="Publish" />
<ItemGroup>
<Content Include="..\Microsoft.Sbom.Tool\bin\$(Configuration)\$(SbomCLIToolTargetFramework)\publish\*" Pack="true">
<Content Include="..\Microsoft.Sbom.Tool\bin\$(Configuration)\$(SbomCLIToolTargetFramework)\publish\**" Pack="true">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<PackageCopyToOutput>true</PackageCopyToOutput>
<PackagePath>\tasks\net472\sbom-tool\</PackagePath>
Expand All @@ -73,9 +73,7 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
<Compile Remove="**/*.*" />
<Compile Include="GenerateSbom.cs" />
<Compile Include="SbomCLIToolTask.cs" />
<Compile Remove="GenerateSbomTask.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'net472'">
Expand Down
13 changes: 12 additions & 1 deletion src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<!--Set the SBOM CLI Tool path. This variable is only used in SbomCLIToolTask.cs-->
<SbomToolPath Condition=" '$(MSBuildRuntimeType)' == 'Full'">$(MSBuildThisFileDirectory)\..\tasks\$(GenerateSbom_TFM)\sbom-tool</SbomToolPath>
<ManifestFolderName>_manifest</ManifestFolderName>
</PropertyGroup>

<!--Based on the MSBuild runtime, GenerateSbom will either pull the GenerateSbomTask or SbomCLIToolTask logic-->
Expand Down Expand Up @@ -45,7 +46,17 @@

<!-- Include the generated SBOM contents within the consumer's nuget package -->
<ItemGroup >
<Content Include="$(SbomPathResult)\**">
<Content Condition=" '$(MSBuildRuntimeType)' == 'Core'" Include="$(SbomPathResult)\**">
<Pack>true</Pack>
<PackagePath>_manifest</PackagePath>
</Content>

<Content Condition=" '$(MSBuildRuntimeType)' == 'Full' And '$(SbomGenerationManifestDirPath)' == '' " Include="$(SbomGenerationBuildDropPath)\$(ManifestFolderName)\**">
<Pack>true</Pack>
<PackagePath>_manifest</PackagePath>
</Content>

<Content Condition=" '$(MSBuildRuntimeType)' == 'Full' And '$(SbomGenerationManifestDirPath)' != '' " Include="$(SbomGenerationManifestDirPath)\$(ManifestFolderName)\**">
<Pack>true</Pack>
<PackagePath>_manifest</PackagePath>
</Content>
Expand Down
62 changes: 60 additions & 2 deletions src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

namespace Microsoft.Sbom.Targets;

using System.Diagnostics.Tracing;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

/// <summary>
Expand All @@ -29,7 +29,42 @@ protected override string GenerateFullPathToTool()
/// <returns>string list of args</returns>
protected override string GenerateCommandLineCommands()
{
return "Command";
var builder = new CommandLineBuilder();

builder.AppendSwitch("generate");
builder.AppendSwitchIfNotNull("-BuildDropPath ", this.BuildDropPath);
builder.AppendSwitchIfNotNull("-BuildComponentPath ", this.BuildComponentPath);
builder.AppendSwitchIfNotNull("-PackageName ", this.PackageName);
builder.AppendSwitchIfNotNull("-PackageVersion ", this.PackageVersion);
builder.AppendSwitchIfNotNull("-PackageSupplier ", this.PackageSupplier);
builder.AppendSwitchIfNotNull("-NamespaceUriBase ", this.NamespaceBaseUri);
builder.AppendSwitchIfNotNull("-DeleteManifestDirIfPresent ", $"{this.DeleteManifestDirIfPresent}");
builder.AppendSwitchIfNotNull("-FetchLicenseInformation ", $"{this.FetchLicenseInformation}");
builder.AppendSwitchIfNotNull("-EnablePackageMetadataParsing ", $"{this.EnablePackageMetadataParsing}");
builder.AppendSwitchIfNotNull("-Verbosity ", this.Verbosity);

// For optional arguments, append them only if they are specified by the user
if (!string.IsNullOrWhiteSpace(this.ManifestDirPath))
{
builder.AppendSwitchIfNotNull("-ManifestDirPath ", this.ManifestDirPath);
}

if (!string.IsNullOrWhiteSpace(this.ExternalDocumentListFile))
{
builder.AppendSwitchIfNotNull("-ExternalDocumentListFile ", this.ExternalDocumentListFile);
}

if (!string.IsNullOrWhiteSpace(this.NamespaceUriUniquePart))
{
builder.AppendSwitchIfNotNull("-NamespaceUriUniquePart ", this.NamespaceUriUniquePart);
}

if (!string.IsNullOrWhiteSpace(this.ManifestInfo))
{
builder.AppendSwitchIfNotNull("-ManifestInfo ", this.ManifestInfo);
}

return builder.ToString();
}

/// <summary>
Expand All @@ -38,6 +73,29 @@ protected override string GenerateCommandLineCommands()
/// <returns></returns>
protected override bool ValidateParameters()
{
// Validate required args and args that take paths as input.
if (!ValidateAndSanitizeRequiredParams() || !ValidateAndSanitizeNamespaceUriUniquePart())
{
return false;
}

ValidateAndAssignVerbosity();
SetOutputImportance();
return true;
}

/// <summary>
/// This method sets the standard output importance. Setting
/// it to "High" ensures all output from the SBOM CLI is printed to
/// Visual Studio's output console; otherwise, it is hidden.
/// </summary>
private void SetOutputImportance()
{
this.StandardOutputImportance = "High";

if (this.Verbosity.ToLower().Equals("Fatal"))
{
this.StandardOutputImportance = "Low";
}
}
}
124 changes: 124 additions & 0 deletions src/Microsoft.Sbom.Targets/SbomInputValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.Sbom.Targets;

using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;

/// <summary>
/// Validation class used to sanitize and validate arguments passed into
/// the GenerateSbomTask and SbomCLIToolTask
/// </summary>
public partial class GenerateSbom
{
/// <summary>
/// Ensure all required arguments are non-null/empty,
/// and do not contain whitespaces, tabs, or newline characters.
/// </summary>
/// <returns>True if the required parameters are valid. False otherwise.</returns>
public bool ValidateAndSanitizeRequiredParams()
{
var requiredProperties = new Dictionary<string, string>
{
{ nameof(this.BuildDropPath), this.BuildDropPath },
{ nameof(this.PackageSupplier), this.PackageSupplier },
{ nameof(this.PackageName), this.PackageName },
{ nameof(this.PackageVersion), this.PackageVersion },
{ nameof(this.NamespaceBaseUri), this.NamespaceBaseUri }
};

foreach (var property in requiredProperties)
{
if (string.IsNullOrWhiteSpace(property.Value))
{
Log.LogError($"SBOM generation failed: Empty argument detected for {property.Key}. Please provide a valid value.");
return false;
}
}

this.PackageSupplier = Remove_Spaces_Tabs_Newlines(this.PackageSupplier);
this.PackageName = Remove_Spaces_Tabs_Newlines(this.PackageName);
this.PackageVersion = Remove_Spaces_Tabs_Newlines(this.PackageVersion);
this.NamespaceBaseUri = this.NamespaceBaseUri.Trim();
this.BuildDropPath = this.BuildDropPath.Trim();

return true;
}

public string Remove_Spaces_Tabs_Newlines(string value)
{
return value.Replace("\n", string.Empty).Replace("\t", string.Empty).Replace(" ", string.Empty);
}

/// <summary>
/// Checks the user's input for Verbosity and assigns the
/// associated EventLevel value for logging. The SBOM API accepts
/// an EventLevel for verbosity while the CLI accepts LogEventLevel.
/// </summary>
public EventLevel ValidateAndAssignVerbosity()
{
// The following shows the accepted verbosity inputs for the SBOM CLI and API respectively
// *********************************
// The SBOM CLI | The SBOM API |
// *********************************
// Verbose | EventLevel.Verbose
// Debug | EventLevel.LogAlways
// Information | EventLevel.Informational
// Warning | EventLevel.Warning
// Error | EventLevel.Error
// Fatal | EventLevel.Critical

// We should standardize on the SBOM CLI verbosity inputs and convert them to the associated
// EventLevel value for the API.
if (string.IsNullOrWhiteSpace(this.Verbosity))
{
Log.LogWarning($"No verbosity level specified. Setting verbosity level at Verbose");
this.Verbosity = "Verbose";
return EventLevel.Verbose;
}

switch (this.Verbosity.ToLower().Trim())
{
case "verbose":
return EventLevel.Verbose;
case "debug":
return EventLevel.Verbose;
case "information":
return EventLevel.Informational;
case "warning":
return EventLevel.Warning;
case "error":
return EventLevel.Error;
case "fatal":
return EventLevel.Critical;
default:
Log.LogWarning($"Unrecognized verbosity level specified. Setting verbosity level at Verbose");
this.Verbosity = "Verbose";
return EventLevel.Verbose;
}
}

/// <summary>
/// Ensure a valid NamespaceUriUniquePart is provided.
/// </summary>
/// <returns>True if the Namespace URI unique part is valid. False otherwise.</returns>
public bool ValidateAndSanitizeNamespaceUriUniquePart()
{
// Ensure the NamespaceUriUniquePart is valid if provided.
if (!string.IsNullOrWhiteSpace(this.NamespaceUriUniquePart)
&& (!Guid.TryParse(this.NamespaceUriUniquePart, out _)
|| this.NamespaceUriUniquePart.Equals(Guid.Empty.ToString())))
{
Log.LogError($"SBOM generation failed: NamespaceUriUniquePart '{this.NamespaceUriUniquePart}' must be a valid unique GUID.");
return false;
}
else if (!string.IsNullOrWhiteSpace(this.NamespaceUriUniquePart))
{
this.NamespaceUriUniquePart = this.NamespaceUriUniquePart.Trim();
}

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,12 @@ public void Sbom_Generation_Succeeds_For_Invalid_Verbosity()
/// values to the SBOM API.
/// </summary>
[TestMethod]
[DataRow("CRITICAL", "Fatal")]
[DataRow("informational", "Information")]
[DataRow("LoGAlwAys", "Verbose")]
[DataRow("FATAL", "Fatal")]
[DataRow("information", "Information")]
[DataRow("vErBose", "Verbose")]
[DataRow("Warning", "Warning")]
[DataRow("eRRor", "Error")]
[DataRow("verBOSE", "Verbose")]
[DataRow("Debug", "Verbose")]
public void Sbom_Generation_Assigns_Correct_Verbosity_IgnoreCase(string inputVerbosity, string mappedVerbosity)
{
// Arrange
Expand Down

0 comments on commit 84441e4

Please sign in to comment.