From a4009b1f952ebe9fbb8633eb7f820445e6517e0b Mon Sep 17 00:00:00 2001 From: Sarah Oslund Date: Fri, 22 Mar 2024 10:20:41 -0700 Subject: [PATCH 01/36] Experimenting with dotnet --- Directory.Packages.props | 113 +++++++++--------- Microsoft.Sbom.sln | 6 + .../GenerateSbomTask.cs | 68 +++++++++++ .../Microsoft.Sbom.Targets.csproj | 24 ++++ .../Microsoft.Sbom.Targets.dll | Bin 0 -> 6656 bytes .../SbomExecution.props | 9 ++ .../SbomExecution.targets | 17 +++ 7 files changed, 181 insertions(+), 56 deletions(-) create mode 100644 src/Microsoft.Sbom.Targets/GenerateSbomTask.cs create mode 100644 src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj create mode 100644 src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.dll create mode 100644 src/Microsoft.Sbom.Targets/SbomExecution.props create mode 100644 src/Microsoft.Sbom.Targets/SbomExecution.targets diff --git a/Directory.Packages.props b/Directory.Packages.props index 85533223..56c98e5b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,58 +1,59 @@ - - - - Compile - - - - 4.0.11 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Compile + + + + 4.0.11 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Sbom.sln b/Microsoft.Sbom.sln index 5b7e5e3c..b8cd3865 100644 --- a/Microsoft.Sbom.sln +++ b/Microsoft.Sbom.sln @@ -47,6 +47,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.DotNetTool", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Extensions.DependencyInjection", "src\Microsoft.Sbom.Extensions.DependencyInjection\Microsoft.Sbom.Extensions.DependencyInjection.csproj", "{2EB7C6CC-5E40-4DAF-AF8B-D69736B601D9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Targets", "src\Microsoft.Sbom.Targets\Microsoft.Sbom.Targets.csproj", "{E6C3C851-EEA0-466E-BA36-73ED85F13EEA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -101,6 +103,10 @@ Global {2EB7C6CC-5E40-4DAF-AF8B-D69736B601D9}.Debug|Any CPU.Build.0 = Debug|Any CPU {2EB7C6CC-5E40-4DAF-AF8B-D69736B601D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {2EB7C6CC-5E40-4DAF-AF8B-D69736B601D9}.Release|Any CPU.Build.0 = Release|Any CPU + {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs new file mode 100644 index 00000000..dfaa50c2 --- /dev/null +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.Build.Framework; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Sbom.Api; +using Microsoft.Sbom.Extensions.DependencyInjection; +using Microsoft.Sbom.Tool; +using PowerArgs; +using Task = Microsoft.Build.Utilities.Task; + +namespace Microsoft.Sbom.Targets; + +public class GenerateSbomTask : Task +{ + [Required] + public string BuildDropPath { get; set; } + + [Required] + public string BuildComponentPath { get; set; } + + [Required] + public string PackageSupplier { get; set; } + + [Required] + public string PackageName { get; set; } + + [Required] + public string PackageVersion { get; set; } + + [Output] + public string SbomPath { get; set; } + + public override bool Execute() + { + if (string.IsNullOrEmpty(BuildDropPath) || + string.IsNullOrEmpty(BuildComponentPath) || + string.IsNullOrEmpty(PackageSupplier) || + string.IsNullOrEmpty(PackageName) || + string.IsNullOrEmpty(PackageVersion)) + { + Log.LogError("Required argument not provided."); + return false; + } + + try + { + // TODO setup suggested by docs + // await Host.CreateDefaultBuilder(args) + // .ConfigureServices((host, services) => + // { + // services + // .AddHostedService() + // .AddSbomTool(); + // }) + // .RunConsoleAsync(x => x.SuppressStatusMessages = true); + SbomPath = "path/to/sbom"; + return true; + } + catch (Exception e) + { + Log.LogError($"SBOM generation failed: {e.Message}"); + return false; + } + } +} diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj new file mode 100644 index 00000000..612395b4 --- /dev/null +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj @@ -0,0 +1,24 @@ + + + + Microsoft.Sbom.Targets + win-x64;osx-x64;linux-x64 + true + Tasks and targets for running the SBOM tool. + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.dll b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.dll new file mode 100644 index 0000000000000000000000000000000000000000..9569560198084339ca2afa597695965b63c1cd8e GIT binary patch literal 6656 zcmeHLYiu0V6+Sb&-Z<-INkU!_9urIzLz1zRxDliT{8)#$jURZO@UpOHcdyrjXJ;le zvrY_xng)<4RHCJST2U1GP#dYJM5@$KQK__Qs}>=IRKy>OQdN;E^+z9nXsZhPowGCR zbv8ltMK7k(%mLSeu|I-}(c=a3G z&uK^A*giRDhlU?`mB6YRCChca$S5i!sJVvi8u_t;QT58oT)Q@VV{CeSi0FuxqAz`a z=GA1j^K^sJq4f~01E;;teV`jp1HBKOB?h2g_nR5)zqBPl@VTXE<_R&vcG#Fa8xLmE z8rU6Uq>spN7-2@-W}pMqFo`A;-p6?w4cA4x9rWR*ab~1WMxe`A06-?b#eRd5W)RJo zK^T-kiEn*CGUyx7n_3^dHv{E(P-I_e2AyqfMQ>_-L<3EvpnV)yH1}+FcA1ULl7|95{4jPVA*vIL5Q~Xr0834 z4|A+1y$g>_*Of%s-MxAjtj5F+y$c3n;%dEXRfAZocdbr{o!XXcX4f{D*$4{(Z24?( zr?(j-ZSL54D`FG$GaHe~(3Xz(cC1OX?+3fx+XBrlggd)p;Cr;{X7*9?u0-o2+JPCg z8}zQL*fPro3ZEa);%M{UE%cf_=DwbNdoU+qKODek5S7kHiT;VFf%SA2qQG`5A!}^S z5iYm`nR@^utuoR)pePq zR}MI*8JwhD=q%^XJob@s#8{q0@1~e$X{bM+&d}ckKB7I9zJgwb#~J#ic1GxAP8rRMO{zgBG zS@x&iPi1K?&HAaf_rb{mvVV0iIo2AY^m^(-x?gaO`sr?9GXlRWFe~tJfja=l4Yu%P z`*bP~&3j_%cxoLeE2d6Crjs30--Jx3Ag21#c|`1?n3_zl19d8<-l9CJ*V8feNBEYe zugBB}u-Qh>$JBa^YoqVQ)DUDDdMTzRFkgnw3H58rrS79u6m2K8C%2wLERgx|bu%LI zQE)Y#N^3Y!JmMLyJ`5?3HRxe_Q`;WHQ}lan2Zi(p z?IwB}e3Lq;NG4rL^MKpv%YdEq9N<88c`b&_|1ErdQ#&sl z(s-qYz&?R90+$4CM@DfL*HRAvz98_PG~*YD;U>Y4fzNs=bRCd;8}KwBSNK)HIUjWN z)xg^Ub@UG4SwJ0qE$}sf=_RQcu${CEIMG}>k~T_z7xW5@NZM$8*jDn+BKJ^KNeUa$ zZ6jW}=BV50wsEU8Z&lQ&RaLjoPfrhkM0Go4E2m7i6^dSU+=}K}B?C3vDdz*v{|ND* zSM@ztxzVL{;+c=BAhbQVb&6PCsQJEQtDr@pqN3>$6^8Jh7;jn$g%{C<#V}G;bE4)( zc2&`cT?)L=n~h9ahj}!z9XqmBXbyUTqP!~BDiswB1m20zDmtnVS&^dtFjUo|vpAw6 zt87J9e}p$du?ES6I$pB_RbJ-s+HFS#cPdu}7~CYPTW7P&>Mb)ai;`Iyo(E#Q&Y4uRm?azI1^NV@k_29@hb964u~XNgWzmjaX>`8 zlGD&xaY~jISF$fo*v7t7l3+afq!rFLU2fH6NCEbVl*`+XH#ao60 zSwBbx6)e~#6~>1X7E!`LUL2XmQErkn2ZE5)B`cz_;)7Tg^#_5qNWfzpPi zwWkcy+JLNI&t$I6(6t;O7{i%WDP7-iSDG|Emo{jFm073JwbxN9t6h^}y=$+*=p5cj zxm>ROrgS5I|OuAN^^+^*v~YS3GyvED(A zwwj|ulUzXieScS+Mh6#eF?(Pjx3Q73c{}tSYmwhL?L5MOMoy#Ejh)2TxkhbL5{dR{ zWd7oe(GVJ#!1wAU$u#;M$B?2B8i5LtBC3q&@6l)%r(S4SZrO;WQicX99^Zvsq>d5I zDWfnjHey7c=U~JdEMu>^*TnZf(Pn&v39;J`)Pk)};8L(3mmYk(HQX>)}al5G)rE!yPCXpxPR*Be)yMorb0Lmp$%Yv;+MDWEYxbEWe1Pi9)_G()sApKZf2I{7GTMcRGIe zqi1;P!2?qzoT#aWZ&SGYPF;GzQ(p1GDcPVYS4G&r%jlVYxk!b>{d+&VpW-5zAv5zBU|8kUfJN=h9|Kl}`A zT)g~NgP#}<-;CsMgQb-&@d+gAA!^~X-eL56`iM5-+c0Sx)4f2C;VXL@aEK;=4WsIe zf*uAuRR7n7ewP002b@7Ven`V)INcFr%wb7$%Y)EhmFwZ5xKa(Ha?PTCal29MOiERA zQTLpt>Lv>GaypWmPytmhfCU$ou+`GI==5OS^}zxcHY;M0 zcf?0rctoEQ})M%x>i+68%?{*_P zylVccP;^4}(j>fv=oZ=ke#C<#QbndXh`ND&UYQAOtEbV~gHPKhE&@jVKfRfx;C|v9 L{M4cUX9WHW&SGmT literal 0 HcmV?d00001 diff --git a/src/Microsoft.Sbom.Targets/SbomExecution.props b/src/Microsoft.Sbom.Targets/SbomExecution.props new file mode 100644 index 00000000..6cca406a --- /dev/null +++ b/src/Microsoft.Sbom.Targets/SbomExecution.props @@ -0,0 +1,9 @@ + + + C:\code\tmp\droppath + C:\code\tmp\droppath + test-supplier + test-name + test-ver + + diff --git a/src/Microsoft.Sbom.Targets/SbomExecution.targets b/src/Microsoft.Sbom.Targets/SbomExecution.targets new file mode 100644 index 00000000..8e061540 --- /dev/null +++ b/src/Microsoft.Sbom.Targets/SbomExecution.targets @@ -0,0 +1,17 @@ + + + + + + + + + + + + From fb128c854443ac8417fabe6b6f0fd83840223b5c Mon Sep 17 00:00:00 2001 From: Sarah Oslund Date: Wed, 3 Apr 2024 11:01:40 -0700 Subject: [PATCH 02/36] Add comments --- .../GenerateSbomTask.cs | 12 +++------ .../Microsoft.Sbom.Targets.csproj | 23 ++++++++++++++++-- .../Microsoft.Sbom.Targets.dll | Bin 6656 -> 0 bytes .../SbomExecution.props | 2 ++ .../SbomExecution.targets | 11 +++++++-- 5 files changed, 35 insertions(+), 13 deletions(-) delete mode 100644 src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.dll diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index dfaa50c2..5eacabbc 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -15,6 +15,8 @@ namespace Microsoft.Sbom.Targets; public class GenerateSbomTask : Task { + // TODO it is possible we will want to expose additional arguments, either as required or optional. + // Will need to get SDK team/ windows team input on which arguments are necessary. [Required] public string BuildDropPath { get; set; } @@ -47,15 +49,7 @@ public override bool Execute() try { - // TODO setup suggested by docs - // await Host.CreateDefaultBuilder(args) - // .ConfigureServices((host, services) => - // { - // services - // .AddHostedService() - // .AddSbomTool(); - // }) - // .RunConsoleAsync(x => x.SuppressStatusMessages = true); + // TODO replace this with a call to SBOM API to generate SBOM SbomPath = "path/to/sbom"; return true; } diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj index 612395b4..6dd5cf57 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj @@ -13,12 +13,31 @@ - - + diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.dll b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.dll deleted file mode 100644 index 9569560198084339ca2afa597695965b63c1cd8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6656 zcmeHLYiu0V6+Sb&-Z<-INkU!_9urIzLz1zRxDliT{8)#$jURZO@UpOHcdyrjXJ;le zvrY_xng)<4RHCJST2U1GP#dYJM5@$KQK__Qs}>=IRKy>OQdN;E^+z9nXsZhPowGCR zbv8ltMK7k(%mLSeu|I-}(c=a3G z&uK^A*giRDhlU?`mB6YRCChca$S5i!sJVvi8u_t;QT58oT)Q@VV{CeSi0FuxqAz`a z=GA1j^K^sJq4f~01E;;teV`jp1HBKOB?h2g_nR5)zqBPl@VTXE<_R&vcG#Fa8xLmE z8rU6Uq>spN7-2@-W}pMqFo`A;-p6?w4cA4x9rWR*ab~1WMxe`A06-?b#eRd5W)RJo zK^T-kiEn*CGUyx7n_3^dHv{E(P-I_e2AyqfMQ>_-L<3EvpnV)yH1}+FcA1ULl7|95{4jPVA*vIL5Q~Xr0834 z4|A+1y$g>_*Of%s-MxAjtj5F+y$c3n;%dEXRfAZocdbr{o!XXcX4f{D*$4{(Z24?( zr?(j-ZSL54D`FG$GaHe~(3Xz(cC1OX?+3fx+XBrlggd)p;Cr;{X7*9?u0-o2+JPCg z8}zQL*fPro3ZEa);%M{UE%cf_=DwbNdoU+qKODek5S7kHiT;VFf%SA2qQG`5A!}^S z5iYm`nR@^utuoR)pePq zR}MI*8JwhD=q%^XJob@s#8{q0@1~e$X{bM+&d}ckKB7I9zJgwb#~J#ic1GxAP8rRMO{zgBG zS@x&iPi1K?&HAaf_rb{mvVV0iIo2AY^m^(-x?gaO`sr?9GXlRWFe~tJfja=l4Yu%P z`*bP~&3j_%cxoLeE2d6Crjs30--Jx3Ag21#c|`1?n3_zl19d8<-l9CJ*V8feNBEYe zugBB}u-Qh>$JBa^YoqVQ)DUDDdMTzRFkgnw3H58rrS79u6m2K8C%2wLERgx|bu%LI zQE)Y#N^3Y!JmMLyJ`5?3HRxe_Q`;WHQ}lan2Zi(p z?IwB}e3Lq;NG4rL^MKpv%YdEq9N<88c`b&_|1ErdQ#&sl z(s-qYz&?R90+$4CM@DfL*HRAvz98_PG~*YD;U>Y4fzNs=bRCd;8}KwBSNK)HIUjWN z)xg^Ub@UG4SwJ0qE$}sf=_RQcu${CEIMG}>k~T_z7xW5@NZM$8*jDn+BKJ^KNeUa$ zZ6jW}=BV50wsEU8Z&lQ&RaLjoPfrhkM0Go4E2m7i6^dSU+=}K}B?C3vDdz*v{|ND* zSM@ztxzVL{;+c=BAhbQVb&6PCsQJEQtDr@pqN3>$6^8Jh7;jn$g%{C<#V}G;bE4)( zc2&`cT?)L=n~h9ahj}!z9XqmBXbyUTqP!~BDiswB1m20zDmtnVS&^dtFjUo|vpAw6 zt87J9e}p$du?ES6I$pB_RbJ-s+HFS#cPdu}7~CYPTW7P&>Mb)ai;`Iyo(E#Q&Y4uRm?azI1^NV@k_29@hb964u~XNgWzmjaX>`8 zlGD&xaY~jISF$fo*v7t7l3+afq!rFLU2fH6NCEbVl*`+XH#ao60 zSwBbx6)e~#6~>1X7E!`LUL2XmQErkn2ZE5)B`cz_;)7Tg^#_5qNWfzpPi zwWkcy+JLNI&t$I6(6t;O7{i%WDP7-iSDG|Emo{jFm073JwbxN9t6h^}y=$+*=p5cj zxm>ROrgS5I|OuAN^^+^*v~YS3GyvED(A zwwj|ulUzXieScS+Mh6#eF?(Pjx3Q73c{}tSYmwhL?L5MOMoy#Ejh)2TxkhbL5{dR{ zWd7oe(GVJ#!1wAU$u#;M$B?2B8i5LtBC3q&@6l)%r(S4SZrO;WQicX99^Zvsq>d5I zDWfnjHey7c=U~JdEMu>^*TnZf(Pn&v39;J`)Pk)};8L(3mmYk(HQX>)}al5G)rE!yPCXpxPR*Be)yMorb0Lmp$%Yv;+MDWEYxbEWe1Pi9)_G()sApKZf2I{7GTMcRGIe zqi1;P!2?qzoT#aWZ&SGYPF;GzQ(p1GDcPVYS4G&r%jlVYxk!b>{d+&VpW-5zAv5zBU|8kUfJN=h9|Kl}`A zT)g~NgP#}<-;CsMgQb-&@d+gAA!^~X-eL56`iM5-+c0Sx)4f2C;VXL@aEK;=4WsIe zf*uAuRR7n7ewP002b@7Ven`V)INcFr%wb7$%Y)EhmFwZ5xKa(Ha?PTCal29MOiERA zQTLpt>Lv>GaypWmPytmhfCU$ou+`GI==5OS^}zxcHY;M0 zcf?0rctoEQ})M%x>i+68%?{*_P zylVccP;^4}(j>fv=oZ=ke#C<#QbndXh`ND&UYQAOtEbV~gHPKhE&@jVKfRfx;C|v9 L{M4cUX9WHW&SGmT diff --git a/src/Microsoft.Sbom.Targets/SbomExecution.props b/src/Microsoft.Sbom.Targets/SbomExecution.props index 6cca406a..8df225bf 100644 --- a/src/Microsoft.Sbom.Targets/SbomExecution.props +++ b/src/Microsoft.Sbom.Targets/SbomExecution.props @@ -1,5 +1,7 @@ + C:\code\tmp\droppath C:\code\tmp\droppath test-supplier diff --git a/src/Microsoft.Sbom.Targets/SbomExecution.targets b/src/Microsoft.Sbom.Targets/SbomExecution.targets index 8e061540..2a003101 100644 --- a/src/Microsoft.Sbom.Targets/SbomExecution.targets +++ b/src/Microsoft.Sbom.Targets/SbomExecution.targets @@ -1,7 +1,13 @@ - - + + + + From 40ba5464bcb5e6d1a6c90c392648f8284183ac1c Mon Sep 17 00:00:00 2001 From: gustavoaca1997 Date: Thu, 23 May 2024 20:16:13 -0700 Subject: [PATCH 03/36] Make the Sbom.Targets project to build. --- Directory.Packages.props | 107 +++++++++--------- .../GenerateSbomTask.cs | 8 +- .../SbomExecution.targets | 8 +- 3 files changed, 62 insertions(+), 61 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 57ab5929..e8367c7b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,58 +1,59 @@ - - - - Compile - - - - 4.2.2 - - - - - - - - - - - - - - + + + + Compile + + + + 4.2.2 + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index 5eacabbc..67e9ed56 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -1,17 +1,17 @@ // 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 Microsoft.Build.Framework; +using Microsoft.Build.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Sbom.Api; using Microsoft.Sbom.Extensions.DependencyInjection; using Microsoft.Sbom.Tool; using PowerArgs; -using Task = Microsoft.Build.Utilities.Task; - -namespace Microsoft.Sbom.Targets; public class GenerateSbomTask : Task { @@ -49,7 +49,7 @@ public override bool Execute() try { - // TODO replace this with a call to SBOM API to generate SBOM + // TODO replace this with a call to SBOM API to generate SBOM SbomPath = "path/to/sbom"; return true; } diff --git a/src/Microsoft.Sbom.Targets/SbomExecution.targets b/src/Microsoft.Sbom.Targets/SbomExecution.targets index 2a003101..9a6829a8 100644 --- a/src/Microsoft.Sbom.Targets/SbomExecution.targets +++ b/src/Microsoft.Sbom.Targets/SbomExecution.targets @@ -8,7 +8,7 @@ good for testing purposes, but not the behavior we ultimately want. We will need to discuss with the .NET SDK team if they want this target to be automatically included after any target and if so, which one. They likely know of a nuget packaging target that it would make sense to hook this in with. --> - + + + TODO temporary logging for testing - + --> From c31fb95c90a63a8f8ccf4615e88487f333e08d92 Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Fri, 24 May 2024 14:30:18 -0700 Subject: [PATCH 04/36] Add all arguments to GenerateSBOMTask (#1) Co-authored-by: vpatakottu --- .../GenerateSbomTask.cs | 65 +++++++++++++++++++ .../SbomExecution.props | 15 ++++- .../SbomExecution.targets | 17 +++-- 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index 67e9ed56..13a2d3b6 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -17,21 +17,86 @@ public class GenerateSbomTask : Task { // TODO it is possible we will want to expose additional arguments, either as required or optional. // Will need to get SDK team/ windows team input on which arguments are necessary. + + /// + /// The path to the drop directory for which the SBOM will be generated + /// [Required] public string BuildDropPath { get; set; } + /// + /// The path to the directory containing build components and package information. + /// For example, path to a .csproj or packages.config file. + /// [Required] public string BuildComponentPath { get; set; } + /// + /// Supplier of the package the SBOM represents. + /// [Required] public string PackageSupplier { get; set; } + /// + /// Name of the package the SBOM represents. + /// [Required] public string PackageName { get; set; } + /// + /// Version of the package the SBOM represents. + /// [Required] public string PackageVersion { get; set; } + /// + /// The base path of the SBOM namespace uri. + /// + [Required] + public string NamespaceBaseUri { get; set; } + + /// + /// A unique URI part that will be appended to NamespaceBaseUri. + /// + public string NamespaceUriUniquePart { get; set; } + + /// + /// The path to a file containing a list of external SBOMs that will be appended to the + /// SBOM that is being generated. + /// + public string ExternalDocumentListFile { get; set; } + + /// + /// If true, it will fetch licensing information for detected packages. + /// + public bool FetchLicenseInformation { get; set; } + + /// + /// If true, it will parse licensing and supplier information from a packages metadata file. + /// + public bool EnablePackageMetadataParsing { get; set; } + + /// + /// Determines how detailed the outputed logging will be. + /// + public string Verbosity { get; set; } + + /// + /// A list of the name and version of the manifest format being used. + /// + public string ManifestInfo { get; set; } + + /// + /// If true, it will delete the previously generated SBOM manifest directory before + /// generating a new SBOM in ManifestDirPath. + /// + public bool DeleteManifestDirIfPresent { get; set; } + + /// + /// The path where the SBOM will be generated. + /// + public string ManifestDirPath { get; set; } + [Output] public string SbomPath { get; set; } diff --git a/src/Microsoft.Sbom.Targets/SbomExecution.props b/src/Microsoft.Sbom.Targets/SbomExecution.props index 8df225bf..806fd7a1 100644 --- a/src/Microsoft.Sbom.Targets/SbomExecution.props +++ b/src/Microsoft.Sbom.Targets/SbomExecution.props @@ -2,10 +2,19 @@ - C:\code\tmp\droppath - C:\code\tmp\droppath + $(OutputPath) + $(MSBuildProjectDirectory) test-supplier test-name - test-ver + 1.0.0 + http://spdx.org/spdxdocs/$(SbomGenerationPackageName) + + + false + false + LogAlways + + true + diff --git a/src/Microsoft.Sbom.Targets/SbomExecution.targets b/src/Microsoft.Sbom.Targets/SbomExecution.targets index 9a6829a8..eca2ffe3 100644 --- a/src/Microsoft.Sbom.Targets/SbomExecution.targets +++ b/src/Microsoft.Sbom.Targets/SbomExecution.targets @@ -8,17 +8,26 @@ good for testing purposes, but not the behavior we ultimately want. We will need to discuss with the .NET SDK team if they want this target to be automatically included after any target and if so, which one. They likely know of a nuget packaging target that it would make sense to hook this in with. --> - + From 8ffff9cd2770a4ed1fe35b78cfeb0ec0f54cfe2d Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Mon, 27 May 2024 17:54:47 -0700 Subject: [PATCH 05/36] Add call to the SBOM API from GenerateSbomTask#Execute (#2) * Start implementing Execute * Make SBOM Targets test target .NET 8 only. * Make the Component Path not required. * Build the current project in test, and check that the manifest was generated. * Ad a few comments for TODOs. --- Directory.Packages.props | 7 +- Microsoft.Sbom.sln | 14 ++-- .../GenerateSbomTask.cs | 61 ++++++++++++++---- .../SbomExecution.targets | 8 +-- .../GenerateSbomTaskTests.cs | 64 +++++++++++++++++++ .../Microsoft.Sbom.Targets.Tests.csproj | 18 ++++++ 6 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskTests.cs create mode 100644 test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj diff --git a/Directory.Packages.props b/Directory.Packages.props index e8367c7b..910bfa7e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,6 +14,7 @@ + @@ -23,14 +24,14 @@ - + - + @@ -56,4 +57,4 @@ - + \ No newline at end of file diff --git a/Microsoft.Sbom.sln b/Microsoft.Sbom.sln index 0cb58a71..5da6022a 100644 --- a/Microsoft.Sbom.sln +++ b/Microsoft.Sbom.sln @@ -51,6 +51,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Extensions.D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Targets", "src\Microsoft.Sbom.Targets\Microsoft.Sbom.Targets.csproj", "{E6C3C851-EEA0-466E-BA36-73ED85F13EEA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Sbom.Targets.Tests", "test\Microsoft.Sbom.Targets.Tests\Microsoft.Sbom.Targets.Tests.csproj", "{E31B914C-F24B-4DC8-ACC7-CAEA952563B8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -105,14 +107,18 @@ Global {2EB7C6CC-5E40-4DAF-AF8B-D69736B601D9}.Debug|Any CPU.Build.0 = Debug|Any CPU {2EB7C6CC-5E40-4DAF-AF8B-D69736B601D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {2EB7C6CC-5E40-4DAF-AF8B-D69736B601D9}.Release|Any CPU.Build.0 = Release|Any CPU - {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Release|Any CPU.Build.0 = Release|Any CPU {EE4E2E03-7B4C-46E5-B9D2-89E84A18D787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EE4E2E03-7B4C-46E5-B9D2-89E84A18D787}.Debug|Any CPU.Build.0 = Debug|Any CPU {EE4E2E03-7B4C-46E5-B9D2-89E84A18D787}.Release|Any CPU.ActiveCfg = Release|Any CPU {EE4E2E03-7B4C-46E5-B9D2-89E84A18D787}.Release|Any CPU.Build.0 = Release|Any CPU + {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6C3C851-EEA0-466E-BA36-73ED85F13EEA}.Release|Any CPU.Build.0 = Release|Any CPU + {E31B914C-F24B-4DC8-ACC7-CAEA952563B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E31B914C-F24B-4DC8-ACC7-CAEA952563B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E31B914C-F24B-4DC8-ACC7-CAEA952563B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E31B914C-F24B-4DC8-ACC7-CAEA952563B8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index 13a2d3b6..73bf1cab 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -4,11 +4,15 @@ namespace Microsoft.Sbom.Targets; using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Threading; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Sbom.Api; +using Microsoft.Sbom.Contracts; using Microsoft.Sbom.Extensions.DependencyInjection; using Microsoft.Sbom.Tool; using PowerArgs; @@ -24,13 +28,6 @@ public class GenerateSbomTask : Task [Required] public string BuildDropPath { get; set; } - /// - /// The path to the directory containing build components and package information. - /// For example, path to a .csproj or packages.config file. - /// - [Required] - public string BuildComponentPath { get; set; } - /// /// Supplier of the package the SBOM represents. /// @@ -55,6 +52,12 @@ public class GenerateSbomTask : Task [Required] public string NamespaceBaseUri { get; set; } + /// + /// The path to the directory containing build components and package information. + /// For example, path to a .csproj or packages.config file. + /// + public string BuildComponentPath { get; set; } + /// /// A unique URI part that will be appended to NamespaceBaseUri. /// @@ -90,7 +93,7 @@ public class GenerateSbomTask : Task /// If true, it will delete the previously generated SBOM manifest directory before /// generating a new SBOM in ManifestDirPath. /// - public bool DeleteManifestDirIfPresent { get; set; } + public bool DeleteManifestDirIfPresent { get; set; } = true; /// /// The path where the SBOM will be generated. @@ -100,10 +103,22 @@ public class GenerateSbomTask : Task [Output] public string SbomPath { get; set; } + private ISBOMGenerator Generator { get; set; } + + public GenerateSbomTask() + { + var host = Host.CreateDefaultBuilder() + .ConfigureServices((host, services) => + services + .AddSbomTool()) + .Build(); + this.Generator = host.Services.GetRequiredService(); + } + public override bool Execute() { + // TODO: Validate all arguments. if (string.IsNullOrEmpty(BuildDropPath) || - string.IsNullOrEmpty(BuildComponentPath) || string.IsNullOrEmpty(PackageSupplier) || string.IsNullOrEmpty(PackageName) || string.IsNullOrEmpty(PackageVersion)) @@ -114,12 +129,36 @@ public override bool Execute() try { - // TODO replace this with a call to SBOM API to generate SBOM + var sbomMetadata = new SBOMMetadata + { + PackageSupplier = this.PackageSupplier, + PackageName = this.PackageName, + PackageVersion = this.PackageVersion, + }; + var runtimeConfiguration = new RuntimeConfiguration + { + NamespaceUriBase = this.NamespaceBaseUri, + NamespaceUriUniquePart = this.NamespaceUriUniquePart, + DeleteManifestDirectoryIfPresent = this.DeleteManifestDirIfPresent, + Verbosity = Enum.TryParse(this.Verbosity, out EventLevel eventLevel) ? eventLevel : default, // TODO: validate this + }; +#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits + var result = System.Threading.Tasks.Task.Run(() => this.Generator.GenerateSbomAsync( + rootPath: this.BuildDropPath, + manifestDirPath: this.ManifestDirPath, + metadata: sbomMetadata, + componentPath: this.BuildComponentPath, + runtimeConfiguration: runtimeConfiguration, + specifications: !string.IsNullOrWhiteSpace(this.ManifestInfo) ? [SbomSpecification.Parse(this.ManifestInfo)] : null, + externalDocumentReferenceListFile: this.ExternalDocumentListFile)).GetAwaiter().GetResult(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + SbomPath = "path/to/sbom"; - return true; + return result.IsSuccessful; } catch (Exception e) { + // TODO: Add automated tests for the different exceptions. Log.LogError($"SBOM generation failed: {e.Message}"); return false; } diff --git a/src/Microsoft.Sbom.Targets/SbomExecution.targets b/src/Microsoft.Sbom.Targets/SbomExecution.targets index eca2ffe3..21f945a2 100644 --- a/src/Microsoft.Sbom.Targets/SbomExecution.targets +++ b/src/Microsoft.Sbom.Targets/SbomExecution.targets @@ -1,13 +1,13 @@ - + + target and if so, which one. They likely know of a nuget packaging target that it would make sense to hook this in with. - +--> diff --git a/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskTests.cs b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskTests.cs new file mode 100644 index 00000000..1d2096b5 --- /dev/null +++ b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Sbom.Targets; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Microsoft.Sbom.Targets.Tests; + +[TestClass] +public class GenerateSbomTaskTests +{ + private Mock buildEngine; + private List errors; + private static readonly string CurrentDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + private static readonly string ManifestDirectory = Path.Combine(CurrentDirectory, "_manifest"); + + [TestInitialize] + public void Startup() + { + // Setup the build engine + buildEngine = new Mock(); + errors = new List(); + buildEngine.Setup(x => x.LogErrorEvent(It.IsAny())).Callback(e => errors.Add(e)); + + // Clean up the manifest directory + if (Directory.Exists(ManifestDirectory)) + { + Directory.Delete(ManifestDirectory, true); + } + } + + [TestMethod] + public void Sbom_Is_Successfully_Generated() + { + // Let's generate a SBOM for the current assembly + var sourceDirectory = Path.Combine(CurrentDirectory, "..\\..\\.."); + + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + BuildComponentPath = sourceDirectory, + PackageSupplier = "Microsoft", + PackageName = "CoseSignTool", + PackageVersion = "1.0.0", + NamespaceBaseUri = "https://base.uri", + BuildEngine = this.buildEngine.Object + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsTrue(result); + + var manifestPath = Path.Combine(ManifestDirectory, "spdx_2.2", "manifest.spdx.json"); + Assert.IsTrue(Path.Exists(manifestPath)); + } +} diff --git a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj new file mode 100644 index 00000000..cd7d8162 --- /dev/null +++ b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + false + True + Microsoft.Sbom.Targets.Tests + $(StrongNameSigningKeyFilePath) + + + + TRACE + + + + + + From ce039ddbc2b47a4ce1c8683f1a29dd06ac07892b Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Tue, 28 May 2024 17:25:11 -0700 Subject: [PATCH 06/36] Validate and Sanitize Arguments (#3) * Validate arguments * ignore case for enum conversion * create method for manifestinfo --------- Co-authored-by: vpatakottu --- .../GenerateSbomTask.cs | 53 ++++++++++++++----- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index 73bf1cab..94f3cd3c 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -15,7 +15,9 @@ namespace Microsoft.Sbom.Targets; using Microsoft.Sbom.Contracts; using Microsoft.Sbom.Extensions.DependencyInjection; using Microsoft.Sbom.Tool; +using Microsoft.VisualBasic; using PowerArgs; +using Serilog.Events; public class GenerateSbomTask : Task { @@ -117,18 +119,11 @@ public GenerateSbomTask() public override bool Execute() { - // TODO: Validate all arguments. - if (string.IsNullOrEmpty(BuildDropPath) || - string.IsNullOrEmpty(PackageSupplier) || - string.IsNullOrEmpty(PackageName) || - string.IsNullOrEmpty(PackageVersion)) - { - Log.LogError("Required argument not provided."); - return false; - } - try { + // Set other configurations. The GenerateSBOMAsync() already sanitizes and checks for + // a valid namespace URI and generates a random guid for NamespaceUriUniquePart if + // one is not provided. var sbomMetadata = new SBOMMetadata { PackageSupplier = this.PackageSupplier, @@ -140,7 +135,7 @@ public override bool Execute() NamespaceUriBase = this.NamespaceBaseUri, NamespaceUriUniquePart = this.NamespaceUriUniquePart, DeleteManifestDirectoryIfPresent = this.DeleteManifestDirIfPresent, - Verbosity = Enum.TryParse(this.Verbosity, out EventLevel eventLevel) ? eventLevel : default, // TODO: validate this + Verbosity = ValidateAndAssignVerbosity() }; #pragma warning disable VSTHRD002 // Avoid problematic synchronous waits var result = System.Threading.Tasks.Task.Run(() => this.Generator.GenerateSbomAsync( @@ -149,7 +144,7 @@ public override bool Execute() metadata: sbomMetadata, componentPath: this.BuildComponentPath, runtimeConfiguration: runtimeConfiguration, - specifications: !string.IsNullOrWhiteSpace(this.ManifestInfo) ? [SbomSpecification.Parse(this.ManifestInfo)] : null, + specifications: ValidateAndAssignSpecifications(), externalDocumentReferenceListFile: this.ExternalDocumentListFile)).GetAwaiter().GetResult(); #pragma warning restore VSTHRD002 // Avoid problematic synchronous waits @@ -163,4 +158,38 @@ public override bool Execute() return false; } } + + /// + /// Checks the user's input for Verbosity and assigns the + /// associated EventLevel value for logging. + /// + private EventLevel ValidateAndAssignVerbosity() + { + if (string.IsNullOrEmpty(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; + } + + /// + /// Check for ManifestInfo and create an SbomSpecification accordingly + /// + /// + private IList ValidateAndAssignSpecifications() + { + if (!string.IsNullOrWhiteSpace(this.ManifestInfo)) + { + return new List { SbomSpecification.Parse(this.ManifestInfo) }; + } + + return null; + } } From 5df56c1aa448089cef351366a5185661d98584fe Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Fri, 31 May 2024 14:07:28 -0700 Subject: [PATCH 07/36] Add tests for Generate SBOM Task (#4) * Add more tests for GenerateSbomTask.Execute logic * Add SBOM Validation to the Generate SBOM Task tests. * Add a utility method that validates the SBOM being generated during tests * Make more tests use the new utility validator method * Pass sbom specification during tests * Refactor GenerateSbomTask tests to be parametrized through the SBOM Specification * Fix typo * Add an abstract method for the Sbom Specification of the AbstractGenerateSbomTaskTests * Address PR suggestions * Made fields internal instead of private in AbstractGenerateSbomTaskTests --- .../AbstractGenerateSbomTaskTests.cs | 264 ++++++++++++++++++ .../GenerateSbomTaskSPDX_2_2Tests.cs | 22 ++ .../GenerateSbomTaskTests.cs | 64 ----- .../Utility/GeneratedSbomValidator.cs | 138 +++++++++ 4 files changed, 424 insertions(+), 64 deletions(-) create mode 100644 test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs create mode 100644 test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2Tests.cs delete mode 100644 test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskTests.cs create mode 100644 test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs new file mode 100644 index 00000000..801a93be --- /dev/null +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs @@ -0,0 +1,264 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Sbom.Api.Utils; +using Microsoft.Sbom.Contracts; +using Microsoft.Sbom.Extensions.DependencyInjection; +using Microsoft.Sbom.Targets; +using Microsoft.Sbom.Targets.Tests.Utility; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Newtonsoft.Json; + +namespace Microsoft.Sbom.Targets.Tests; + +/// +/// Base class for testing SBOM generation through the GenerateSbomTask. +/// +[TestClass] +public abstract class AbstractGenerateSbomTaskTests +{ + internal abstract SbomSpecification SbomSpecification { get; } + + internal static readonly string CurrentDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + internal static readonly string DefaultManifestDirectory = Path.Combine(CurrentDirectory, "_manifest"); + internal static readonly string TemporaryDirectory = Path.Combine(CurrentDirectory, "_temp"); + internal const string PackageSupplier = "Test-Microsoft"; + internal const string PackageName = "CoseSignTool"; + internal const string PackageVersion = "0.0.1"; + internal const string NamespaceBaseUri = "https://base0.uri"; + + internal Mock BuildEngine; + internal List Errors; + internal string ManifestPath; + internal GeneratedSbomValidator GeneratedSbomValidator; + + internal string SbomSpecificationDirectoryName => $"{this.SbomSpecification.Name}_{this.SbomSpecification.Version}"; + + [TestInitialize] + public void Startup() + { + // Setup the build engine + this.BuildEngine = new Mock(); + this.Errors = new List(); + this.BuildEngine.Setup(x => x.LogErrorEvent(It.IsAny())).Callback(e => Errors.Add(e)); + + // Clean up the manifest directory + if (Directory.Exists(DefaultManifestDirectory)) + { + Directory.Delete(DefaultManifestDirectory, true); + } + + // Clean up the manifest directory + if (Directory.Exists(TemporaryDirectory)) + { + Directory.Delete(TemporaryDirectory, true); + } + + this.ManifestPath = Path.Combine(DefaultManifestDirectory, this.SbomSpecificationDirectoryName, "manifest.spdx.json"); + this.GeneratedSbomValidator = new(this.SbomSpecification); + } + + [TestMethod] + public void Sbom_Is_Successfully_Generated() + { + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification.ToString(), + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsTrue(result); + this.GeneratedSbomValidator.AssertSbomIsValid(this.ManifestPath, CurrentDirectory, PackageName, PackageVersion, PackageSupplier, NamespaceBaseUri); + } + + [TestMethod] + public void Sbom_Is_Successfully_Generated_In_Specified_Location() + { + var manifestDirPath = Path.Combine(TemporaryDirectory, "sub-directory"); + Directory.CreateDirectory(manifestDirPath); + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + ManifestDirPath = manifestDirPath, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification.ToString(), + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsTrue(result); + + this.ManifestPath = Path.Combine(manifestDirPath, "_manifest", this.SbomSpecificationDirectoryName, "manifest.spdx.json"); + this.GeneratedSbomValidator.AssertSbomIsValid(this.ManifestPath, CurrentDirectory, PackageName, PackageVersion, PackageSupplier, NamespaceBaseUri); + } + + [TestMethod] + public void Sbom_Generation_Fails_With_NotFound_BuildDropPath() + { + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = ".\\non-existent\\path", + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification.ToString(), + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void Sbom_Generation_Fails_With_NotFound_BuildComponentPath() + { + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + BuildComponentPath = ".\\non-existent\\path", + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification.ToString(), + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsFalse(result); + Assert.IsFalse(Directory.Exists(DefaultManifestDirectory)); + } + + [TestMethod] + public void Sbom_Generation_Fails_With_NotFound_ExternalDocumentListFile() + { + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + ExternalDocumentListFile = ".\\non-existent\\path", + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification.ToString(), + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsFalse(result); + Assert.IsFalse(Directory.Exists(DefaultManifestDirectory)); + } + + [TestMethod] + public void Sbom_Generation_Fails_With_NotFound_ManifestDirPath() + { + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + ManifestDirPath = ".\\non-existent\\path", + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification.ToString(), + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsFalse(result); + Assert.IsFalse(Directory.Exists(DefaultManifestDirectory)); + } + + [TestMethod] + public void Sbom_Is_Successfully_Generated_With_Component_Path() + { + // Let's generate a SBOM for the current assembly + var sourceDirectory = Path.Combine(CurrentDirectory, "..", "..", ".."); + + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + BuildComponentPath = sourceDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification.ToString(), + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsTrue(result); + this.GeneratedSbomValidator.AssertSbomIsValid(this.ManifestPath, CurrentDirectory, PackageName, PackageVersion, PackageSupplier, NamespaceBaseUri, buildComponentPath: sourceDirectory); + } + + [TestMethod] + public void Sbom_Is_Successfully_Generated_With_Unique_Namespace_Part_Defined() + { + var uniqueNamespacePart = Guid.NewGuid().ToString(); + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + NamespaceUriUniquePart = uniqueNamespacePart, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification.ToString(), + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsTrue(result); + this.GeneratedSbomValidator.AssertSbomIsValid(this.ManifestPath, CurrentDirectory, PackageName, PackageVersion, PackageSupplier, NamespaceBaseUri, expectedNamespaceUriUniquePart: uniqueNamespacePart); + } +} diff --git a/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2Tests.cs b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2Tests.cs new file mode 100644 index 00000000..a3acf920 --- /dev/null +++ b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2Tests.cs @@ -0,0 +1,22 @@ +// 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.Tests; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Sbom.Api.Utils; +using Microsoft.Sbom.Contracts; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Class to test the generation of SBOM using SPDX 2.2 specification. +/// +[TestClass] +public class GenerateSbomTaskSPDX_2_2Tests : AbstractGenerateSbomTaskTests +{ + internal override SbomSpecification SbomSpecification => Constants.SPDX22Specification; +} diff --git a/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskTests.cs b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskTests.cs deleted file mode 100644 index 1d2096b5..00000000 --- a/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Build.Framework; -using Microsoft.Sbom.Targets; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -namespace Microsoft.Sbom.Targets.Tests; - -[TestClass] -public class GenerateSbomTaskTests -{ - private Mock buildEngine; - private List errors; - private static readonly string CurrentDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); - private static readonly string ManifestDirectory = Path.Combine(CurrentDirectory, "_manifest"); - - [TestInitialize] - public void Startup() - { - // Setup the build engine - buildEngine = new Mock(); - errors = new List(); - buildEngine.Setup(x => x.LogErrorEvent(It.IsAny())).Callback(e => errors.Add(e)); - - // Clean up the manifest directory - if (Directory.Exists(ManifestDirectory)) - { - Directory.Delete(ManifestDirectory, true); - } - } - - [TestMethod] - public void Sbom_Is_Successfully_Generated() - { - // Let's generate a SBOM for the current assembly - var sourceDirectory = Path.Combine(CurrentDirectory, "..\\..\\.."); - - // Arrange - var task = new GenerateSbomTask - { - BuildDropPath = CurrentDirectory, - BuildComponentPath = sourceDirectory, - PackageSupplier = "Microsoft", - PackageName = "CoseSignTool", - PackageVersion = "1.0.0", - NamespaceBaseUri = "https://base.uri", - BuildEngine = this.buildEngine.Object - }; - - // Act - var result = task.Execute(); - - // Assert - Assert.IsTrue(result); - - var manifestPath = Path.Combine(ManifestDirectory, "spdx_2.2", "manifest.spdx.json"); - Assert.IsTrue(Path.Exists(manifestPath)); - } -} diff --git a/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs b/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs new file mode 100644 index 00000000..3a7dc466 --- /dev/null +++ b/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs @@ -0,0 +1,138 @@ +// 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.Tests.Utility; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Sbom.Api.Utils; +using Microsoft.Sbom.Contracts; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; + +/// +/// This class is used to validate that the generated SBOM has valid fields and data. +/// +#pragma warning disable CA5350 // Suppress Do Not Use Weak Cryptographic Algorithms as we use SHA1 intentionally +internal class GeneratedSbomValidator +{ + private readonly SbomSpecification sbomSpecification; + + public GeneratedSbomValidator(SbomSpecification sbomSpecification) + { + this.sbomSpecification = sbomSpecification; + } + + internal void AssertSbomIsValid(string manifestPath, string buildDropPath, string expectedPackageName, string expectedPackageVersion, string expectedPackageSupplier, string expectedNamespaceUriBase, string expectedNamespaceUriUniquePart = null, string buildComponentPath = null) + { + Assert.IsTrue(File.Exists(manifestPath)); + + // Read and parse the manifest + var manifestContent = File.ReadAllText(manifestPath); + var manifest = JsonConvert.DeserializeObject(manifestContent); + + if (this.sbomSpecification.Equals(Constants.SPDX22Specification)) + { + // Check the manifest has expected file data + var filesValue = manifest["files"]; + Assert.IsNotNull(filesValue); + + var expectedFilesHashes = this.GetBuildDropFileHashes(buildDropPath); + Assert.AreEqual(expectedFilesHashes.Count, filesValue.Count); + foreach (var file in filesValue) + { + var filePath = Path.GetFullPath(Path.Combine(buildDropPath, (string)file["fileName"])); + var fileChecksums = file["checksums"]; + Assert.IsNotNull(fileChecksums); + + foreach (var checksum in fileChecksums) + { + var algorithm = (string)checksum["algorithm"]; + var hash = (string)checksum["checksumValue"]; + Assert.IsNotNull(algorithm); + Assert.IsNotNull(hash); + + Assert.IsTrue(expectedFilesHashes.ContainsKey(filePath)); + Assert.IsTrue(expectedFilesHashes[filePath].ContainsKey(algorithm)); + Assert.IsTrue(expectedFilesHashes[filePath][algorithm].Equals(hash, StringComparison.InvariantCultureIgnoreCase)); + } + } + + var packagesValue = manifest["packages"]; + Assert.IsNotNull(packagesValue); + if (string.IsNullOrEmpty(buildComponentPath)) + { + Assert.IsTrue(packagesValue.Count == 1); + } + else + { + Assert.IsTrue(packagesValue.Count > 1); + } + + var nameValue = manifest["name"]; + Assert.IsNotNull(nameValue); + Assert.AreEqual($"{expectedPackageName} {expectedPackageVersion}", (string)nameValue); + + var creatorsValue = manifest["creationInfo"]["creators"]; + Assert.IsNotNull(creatorsValue); + Assert.IsTrue(creatorsValue.Count > 0); + Assert.IsTrue(((string)creatorsValue[0]).Contains(expectedPackageSupplier)); + + string namespaceValue = manifest["documentNamespace"]; + Assert.IsNotNull(namespaceValue); + + if (expectedNamespaceUriUniquePart != null) + { + Assert.IsTrue(namespaceValue.Equals($"{expectedNamespaceUriBase}/{expectedPackageName}/{expectedPackageVersion}/{expectedNamespaceUriUniquePart}", StringComparison.InvariantCultureIgnoreCase)); + } + else + { + Assert.IsTrue(namespaceValue.Contains($"{expectedNamespaceUriBase}/{expectedPackageName}/{expectedPackageVersion}", StringComparison.InvariantCultureIgnoreCase)); + } + } + } + + private IDictionary> GetBuildDropFileHashes(string buildDropPath) + { + var filesHashes = new Dictionary>(); + + // Get all files in the buildDropPath and its subfolders + var files = Directory.GetFiles(buildDropPath, "*", SearchOption.AllDirectories) + .Where(f => !f.Contains("manifest.spdx.json")) + .Select(Path.GetFullPath); + + // Compute hashes for each file. + foreach (var filePath in files) + { + var fileHashes = new Dictionary(); + // Compute hashes for the file. + foreach (var hashAlgorithmPair in this.GetListOfHashAlgorithmCreators()) + { + using var stream = File.OpenRead(filePath); + using var hashAlgorithmInstance = hashAlgorithmPair.Item2(); + var hash = hashAlgorithmInstance.ComputeHash(stream); + var hashString = BitConverter.ToString(hash).Replace("-", string.Empty); + fileHashes.Add(hashAlgorithmPair.Item1, hashString); + } + + filesHashes.Add(filePath, fileHashes); + } + + return filesHashes; + } + + private IList<(string, Func)> GetListOfHashAlgorithmCreators() + { + if (this.sbomSpecification.Equals(Constants.SPDX22Specification)) + { + return [("SHA1", SHA1.Create), ("SHA256", SHA256.Create)]; + } + + return []; + } +} From 4ece5d9a6c4db64c70b3f4c03f15e9084fe2c698 Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Fri, 7 Jun 2024 06:56:40 -0700 Subject: [PATCH 08/36] Add unit tests for GenerateSbomTask inputs (#6) * add unit tests for GenerateSbomTask inputs * remove console print * Addressing feedback * addressing feedback and adding more tests' --------- Co-authored-by: vpatakottu --- .../GenerateSbomTask.cs | 92 ++++- .../AbstractGenerateSBomTaskInputTests.cs | 333 ++++++++++++++++++ .../GenerateSbomTaskSPDX_2_2InputTests.cs | 17 + 3 files changed, 441 insertions(+), 1 deletion(-) create mode 100644 test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs create mode 100644 test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2InputTests.cs diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index 94f3cd3c..dc5da3d4 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -6,6 +6,7 @@ namespace Microsoft.Sbom.Targets; using System; using System.Collections.Generic; using System.Diagnostics.Tracing; +using System.IO; using System.Threading; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -121,6 +122,18 @@ public override bool Execute() { try { + // Validate required args and args that take paths as input. + if (!ValidateRequiredParams() || !ValidateRootedPaths()) + { + return false; + } + + if (!string.IsNullOrWhiteSpace(this.NamespaceUriUniquePart) && !Guid.TryParse(this.NamespaceUriUniquePart, out var guidResult)) + { + Log.LogError($"SBOM generation failed: NamespaceUriUniquePart '{this.NamespaceUriUniquePart}' must be a valid GUID."); + return false; + } + // Set other configurations. The GenerateSBOMAsync() already sanitizes and checks for // a valid namespace URI and generates a random guid for NamespaceUriUniquePart if // one is not provided. @@ -159,13 +172,90 @@ public override bool Execute() } } + /// + /// Ensure all required arguments are non-null/empty, + /// and do not contain whitespaces, tabs, or newline characters. + /// + /// + private bool ValidateRequiredParams() + { + 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; + } + + return true; + } + + /// + /// Ensure all arguments that accept paths are rooted. + /// + /// + private bool ValidateRootedPaths() + { + if (!Path.IsPathRooted(this.BuildDropPath)) + { + Log.LogError($"SBOM generation failed: Unrooted path detected. Please specify a full path for {nameof(this.BuildDropPath)}. " + + $"Current value is {this.BuildDropPath}"); + return false; + } + + if (!string.IsNullOrWhiteSpace(this.BuildComponentPath) && !Path.IsPathRooted(this.BuildComponentPath)) + { + Log.LogError($"SBOM generation failed: Unrooted path detected. Please specify a full path for {nameof(this.BuildComponentPath)}. " + + $"Current value is {this.BuildComponentPath}"); + return false; + } + + if (!string.IsNullOrWhiteSpace(this.ManifestDirPath) && !Path.IsPathRooted(this.ManifestDirPath)) + { + Log.LogError($"SBOM generation failed: Unrooted path detected. Please specify a full path for {nameof(this.ManifestDirPath)}. " + + $"Current value is {this.ManifestDirPath}"); + return false; + } + + if (!string.IsNullOrWhiteSpace(this.ExternalDocumentListFile) && !Path.IsPathRooted(this.ExternalDocumentListFile)) + { + Log.LogError($"SBOM generation failed: Unrooted path detected. Please specify a full path for {nameof(this.ExternalDocumentListFile)}. " + + $"Current value is {this.ExternalDocumentListFile}"); + return false; + } + + return true; + } + /// /// Checks the user's input for Verbosity and assigns the /// associated EventLevel value for logging. /// private EventLevel ValidateAndAssignVerbosity() { - if (string.IsNullOrEmpty(this.Verbosity)) + if (string.IsNullOrWhiteSpace(this.Verbosity)) { Log.LogMessage($"No verbosity level specified. Setting verbosity level at \"{EventLevel.LogAlways}\""); return EventLevel.LogAlways; diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs new file mode 100644 index 00000000..1c3c1f39 --- /dev/null +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs @@ -0,0 +1,333 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using Microsoft.Build.Framework; +using Microsoft.Sbom.Contracts; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Microsoft.Sbom.Targets.Tests; + +[TestClass] +public abstract class AbstractGenerateSBomTaskInputTests +{ + internal abstract SbomSpecification SbomSpecification { get; } + + internal static readonly string CurrentDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + internal static readonly string DefaultManifestDirectory = Path.Combine(CurrentDirectory, "_manifest"); + internal static readonly string TemporaryDirectory = Path.Combine(CurrentDirectory, "_temporary"); + internal static readonly string BuildComponentPath = Path.Combine(CurrentDirectory, "..", "..", ".."); + internal static readonly string ExternalDocumentListFile = Path.GetRandomFileName(); + internal const string PackageSupplier = "Test-Microsoft"; + internal const string PackageName = "CoseSignTool"; + internal const string PackageVersion = "0.0.1"; + internal const string NamespaceBaseUri = "https://base0.uri"; + + private Mock buildEngine; + private List errors; + + [TestInitialize] + public void Startup() + { + // Setup the build engine + this.buildEngine = new Mock(); + this.errors = new List(); + this.buildEngine.Setup(x => x.LogErrorEvent(It.IsAny())).Callback(e => errors.Add(e)); + } + + [TestCleanup] + public void Cleanup() { + // Clean up the manifest directory + if (Directory.Exists(DefaultManifestDirectory)) + { + Directory.Delete(DefaultManifestDirectory, true); + } + + // Clean up the manifest directory + if (Directory.Exists(TemporaryDirectory)) + { + Directory.Delete(TemporaryDirectory, true); + } + } + + /// + /// Test for ensuring the GenerateSbomTask fails for null or empty inputs for + /// required params, which includes BuildDropPath, PackageSupplier, PackageName, + /// PackageVersion, and NamespaceBaseUri. + /// + [TestMethod] + [DynamicData(nameof(GetNullRequiredParamsData), DynamicDataSourceType.Method)] + [DynamicData(nameof(GetEmptyRequiredParamsData), DynamicDataSourceType.Method)] + [DynamicData(nameof(GetWhiteSpace_Tabs_NewLineParamsData), DynamicDataSourceType.Method)] + public void Sbom_Fails_With_Null_Empty_And_WhiteSpace_Required_Params( + string buildDropPath, + string packageSupplier, + string packageName, + string packageVersion, + string namespaceBaseUri) + { + // Arrange. + var task = new GenerateSbomTask + { + BuildDropPath = buildDropPath, + PackageSupplier = packageSupplier, + PackageName = packageName, + PackageVersion = packageVersion, + NamespaceBaseUri = namespaceBaseUri, + ManifestInfo = this.SbomSpecification.ToString(), + BuildEngine = this.buildEngine.Object + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsFalse(result); + } + + private static IEnumerable GetNullRequiredParamsData() + { + yield return new object[] { null, PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, null, PackageName, PackageVersion, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, PackageSupplier, null, PackageVersion, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, null, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, null }; + } + + private static IEnumerable GetEmptyRequiredParamsData() + { + yield return new object[] { string.Empty, PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, string.Empty, PackageName, PackageVersion, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, PackageSupplier, string.Empty, PackageVersion, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, string.Empty, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, string.Empty }; + } + + private static IEnumerable GetWhiteSpace_Tabs_NewLineParamsData() + { + yield return new object[] { " ", PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, "\n", PackageName, PackageVersion, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, PackageSupplier, "\t", PackageVersion, NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, " \n \t \n \t \n ", NamespaceBaseUri }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, "\t \t \t " }; + } + + /// + /// Test for ensuring the GenerateSbomTask fails when user provides an + /// invalid URI format. + /// + [TestMethod] + [DataRow("incorrectly_formatted_uri.com")] // Missing protocol + [DataRow("http://invalid.com:70000")] // Invalid port + [DataRow("http://inv\nalid.com")] // Contains new line character + [DataRow("http://invalid.com/path with spaces")] // Contains spaces + [DataRow("http:invalid.com")] // Missing // after protocol + [DataRow("http://")] // Missing domain + public void Sbom_Fails_With_Invalid_NamespaceBaseUri(string namespaceBaseUri) + { + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = namespaceBaseUri, + ManifestInfo = this.SbomSpecification.ToString(), + BuildEngine = this.buildEngine.Object + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Test for ensuring the GenerateSbomTask fails when user provides + /// an invalid GUID for NamespaceUriUniquePart. + /// + [TestMethod] + [DataRow("-1")] // starts with hyphen + [DataRow("1234567890")] // Too less digits + [DataRow("12345678-1234-1234-1234-123456789abcd")] // Too many digits + [DataRow("12345678-1234-1234-1234-123456789abg")] // invalid character g + [DataRow("12345678-1234-1234-1234-123456789ab!")] // invalid character ! + [DataRow("12345678-1234-1234-1234-123456789ab")] // Too less digits + [DataRow("12345678-1234-1234-1234-123456789ac-")] // Ends with a hyphen + public void Sbom_Generation_Fails_For_Invalid_NamespaceUriUniquePart(string namespaceUriUniquePart) + { + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + NamespaceUriUniquePart = namespaceUriUniquePart, + ManifestInfo = this.SbomSpecification.ToString(), + BuildEngine = this.buildEngine.Object + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Test for ensuring the GenerateSbomTask fails when relative paths are + /// provided for all path arguments, which includes BuildDroppath, BuildComponentPath, + /// ManifestDirPath, and ExternalDocumentListFile + /// + [TestMethod] + [DynamicData(nameof(GetUnrootedPathTestData), DynamicDataSourceType.Method)] + public void Sbom_Fails_With_Unrooted_Paths( + string buildDropPath, + string buildComponentPath, + string manifestDirPath, + string externalDocumentListFile) + { + // Arrange. + var task = new GenerateSbomTask + { + BuildDropPath = buildDropPath, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildComponentPath = buildComponentPath, + ManifestDirPath = manifestDirPath, + ExternalDocumentListFile = externalDocumentListFile, + ManifestInfo = this.SbomSpecification.ToString(), + BuildEngine = this.buildEngine.Object + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsFalse(result); + } + + private static IEnumerable GetUnrootedPathTestData() + { + yield return new object[] { Path.Combine("..", ".."), BuildComponentPath, DefaultManifestDirectory, ExternalDocumentListFile }; + yield return new object[] { CurrentDirectory, Path.Combine("..", ".."), DefaultManifestDirectory, ExternalDocumentListFile }; + yield return new object[] { CurrentDirectory, BuildComponentPath, Path.Combine("..", ".."), ExternalDocumentListFile }; + yield return new object[] { CurrentDirectory, BuildComponentPath, DefaultManifestDirectory, Path.Combine("..", "..") }; + } + + /// + /// Test for ensuring GenerateSbomTask assigns a defualt Verbosity + /// level when null input is provided. + /// + [TestMethod] + public void Sbom_Generation_Succeeds_For_Null_Verbosity() + { + // Arrange + // If Verbosity is null, the default value should be Verbose and is printed in the + // tool's standard output. + var pattern = new Regex("Verbosity=.*Value=Verbose"); + var stringWriter = new StringWriter(); + Console.SetOut(stringWriter); + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + ManifestInfo = this.SbomSpecification.ToString(), + Verbosity = null, + BuildEngine = this.buildEngine.Object + }; + + // Act + var result = task.Execute(); + var output = stringWriter.ToString(); + + // Assert + Assert.IsTrue(result); + Assert.IsTrue(pattern.IsMatch(output)); + } + + /// + /// Test for ensuring GenerateSbomTask assigns a default Verbosity for + /// unrecognized input. + /// + [TestMethod] + public void Sbom_Generation_Succeeds_For_Invalid_Verbosity() + { + // Arrange + // If an invalid Verbosity is specified, the default value should be Verbose and is printed in the + // tool's standard output. + var pattern = new Regex("Verbosity=.*Value=Verbose"); + var stringWriter = new StringWriter(); + Console.SetOut(stringWriter); + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + Verbosity = "Invalid Verbosity", + ManifestInfo = this.SbomSpecification.ToString(), + BuildEngine = this.buildEngine.Object + }; + + // Act + var result = task.Execute(); + var output = stringWriter.ToString(); + + // Assert + Assert.IsTrue(result); + Assert.IsTrue(pattern.IsMatch(output)); + } + + /// + /// Test to ensure GenerateSbomTask correctly parses and provides each EventLevel verbosity + /// values to the SBOM API. + /// + [TestMethod] + [DataRow("CRITICAL", "Fatal")] + [DataRow("informational", "Information")] + [DataRow("LoGAlwAys", "Verbose")] + [DataRow("Warning", "Warning")] + [DataRow("eRRor", "Error")] + [DataRow("verBOSE", "Verbose")] + public void Sbom_Generation_Assigns_Correct_Verbosity_IgnoreCase(string inputVerbosity, string mappedVerbosity) + { + // Arrange + var pattern = new Regex($"Verbosity=.*Value={mappedVerbosity}"); + var stringWriter = new StringWriter(); + Console.SetOut(stringWriter); + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + Verbosity = inputVerbosity, + ManifestInfo = this.SbomSpecification.ToString(), + BuildEngine = this.buildEngine.Object + }; + + // Act + var result = task.Execute(); + var output = stringWriter.ToString(); + + // Assert + Assert.IsTrue(result); + Assert.IsTrue(pattern.IsMatch(output)); + } +} diff --git a/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2InputTests.cs b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2InputTests.cs new file mode 100644 index 00000000..92431f09 --- /dev/null +++ b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2InputTests.cs @@ -0,0 +1,17 @@ +// 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.Tests; + +using Microsoft.Sbom.Api.Utils; +using Microsoft.Sbom.Contracts; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Class to test the generation of SBOM using SPDX 2.2 specification. +/// +[TestClass] +public class GenerateSbomTaskSPDX_2_2InputTests : AbstractGenerateSBomTaskInputTests +{ + internal override SbomSpecification SbomSpecification => Constants.SPDX22Specification; +} From f7e0383c92e84137c4da4977ceeef6b4693233fe Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:11:18 -0700 Subject: [PATCH 09/36] Add additional unit tests for valid cases (#7) * add additional unit tests for valid cases * address feedback and add few more cases --------- Co-authored-by: vpatakottu --- .../GenerateSbomTask.cs | 49 +++++++--- .../AbstractGenerateSBomTaskInputTests.cs | 2 + .../AbstractGenerateSbomTaskTests.cs | 97 ++++++++++++++++--- .../Utility/GeneratedSbomValidator.cs | 4 +- 4 files changed, 124 insertions(+), 28 deletions(-) diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index dc5da3d4..6919677b 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -7,18 +7,12 @@ namespace Microsoft.Sbom.Targets; using System.Collections.Generic; using System.Diagnostics.Tracing; using System.IO; -using System.Threading; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Sbom.Api; using Microsoft.Sbom.Contracts; using Microsoft.Sbom.Extensions.DependencyInjection; -using Microsoft.Sbom.Tool; -using Microsoft.VisualBasic; -using PowerArgs; -using Serilog.Events; public class GenerateSbomTask : Task { @@ -123,17 +117,11 @@ public override bool Execute() try { // Validate required args and args that take paths as input. - if (!ValidateRequiredParams() || !ValidateRootedPaths()) + if (!ValidateAndSanitizeRequiredParams() || !ValidateRootedPaths() || !ValidateAndSanitizeNamespaceUriUniquePart()) { return false; } - if (!string.IsNullOrWhiteSpace(this.NamespaceUriUniquePart) && !Guid.TryParse(this.NamespaceUriUniquePart, out var guidResult)) - { - Log.LogError($"SBOM generation failed: NamespaceUriUniquePart '{this.NamespaceUriUniquePart}' must be a valid GUID."); - return false; - } - // Set other configurations. The GenerateSBOMAsync() already sanitizes and checks for // a valid namespace URI and generates a random guid for NamespaceUriUniquePart if // one is not provided. @@ -172,12 +160,17 @@ 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); + } + /// /// Ensure all required arguments are non-null/empty, /// and do not contain whitespaces, tabs, or newline characters. /// /// - private bool ValidateRequiredParams() + private bool ValidateAndSanitizeRequiredParams() { if (string.IsNullOrWhiteSpace(this.BuildDropPath)) { @@ -209,6 +202,12 @@ private bool ValidateRequiredParams() 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; } @@ -282,4 +281,26 @@ private IList ValidateAndAssignSpecifications() return null; } + + /// + /// Ensure a valid NamespaceUriUniquePart is provided + /// + /// + private bool ValidateAndSanitizeNamespaceUriUniquePart() + { + // Ensure the NamespaceUriUniquePart is valid if provided. + if (!string.IsNullOrWhiteSpace(this.NamespaceUriUniquePart) + && (!Guid.TryParse(this.NamespaceUriUniquePart, out var guidResult) + || 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; + } } diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs index 1c3c1f39..da7af181 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs @@ -160,6 +160,8 @@ public void Sbom_Fails_With_Invalid_NamespaceBaseUri(string namespaceBaseUri) [DataRow("12345678-1234-1234-1234-123456789ab!")] // invalid character ! [DataRow("12345678-1234-1234-1234-123456789ab")] // Too less digits [DataRow("12345678-1234-1234-1234-123456789ac-")] // Ends with a hyphen + [DataRow("12345678-1234-1234-1234-12345\n6789ac")] // Contains newline + [DataRow("00000000-0000-0000-0000-000000000000")] // Empty guid public void Sbom_Generation_Fails_For_Invalid_NamespaceUriUniquePart(string namespaceUriUniquePart) { // Arrange diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs index 801a93be..e331decc 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs @@ -1,20 +1,10 @@ -using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.Framework; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Sbom.Api.Utils; using Microsoft.Sbom.Contracts; -using Microsoft.Sbom.Extensions.DependencyInjection; -using Microsoft.Sbom.Targets; using Microsoft.Sbom.Targets.Tests.Utility; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Newtonsoft.Json; namespace Microsoft.Sbom.Targets.Tests; @@ -88,6 +78,86 @@ public void Sbom_Is_Successfully_Generated() this.GeneratedSbomValidator.AssertSbomIsValid(this.ManifestPath, CurrentDirectory, PackageName, PackageVersion, PackageSupplier, NamespaceBaseUri); } + [TestMethod] + [DataRow("http://example.com/hello/world")] // Regular valid URI + [DataRow("http://example.com/hello%20world")] // Valid URI with space encoded + [DataRow("http://ExAmplE.com")] // Mix of cases + [DataRow(" http://example.com ")] // Trailing spaces + [DataRow("http://www.example.com/path/to/resource?param1=value1¶m2=value2¶m3=value3¶m4=value4¶m5=" + + "value5¶m6=value6¶m7=value7¶m8=value8¶m9=value9¶m10=value10¶m11=value11¶m12=value12" + + "¶m13=value13¶m14=value14¶m15=value15¶m16=value16¶m17=value17¶m18=value18¶m19=value19¶m20=value20#section1")] // Super long URI + public void Sbom_Is_Successfully_Generated_Valid_URI(string namespaceBaseUri) + { + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = namespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification.ToString(), + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsTrue(result); + this.GeneratedSbomValidator.AssertSbomIsValid(this.ManifestPath, CurrentDirectory, PackageName, PackageVersion, PackageSupplier, namespaceBaseUri); + } + + [TestMethod] + [DynamicData(nameof(GetPackageSupplierCases), DynamicDataSourceType.Method)] + [DynamicData(nameof(GetPackageNameCases), DynamicDataSourceType.Method)] + [DynamicData(nameof(GetPackageVersionCases), DynamicDataSourceType.Method)] + public void Sbom_Is_Successfully_Generated_Valid_RequiredParams(string packageSupplier, string packageName, string packageVersion) + { + // Arrange + var task = new GenerateSbomTask + { + BuildDropPath = CurrentDirectory, + PackageSupplier = packageSupplier, + PackageName = packageName, + PackageVersion = packageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification.ToString(), + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsTrue(result); + this.GeneratedSbomValidator.AssertSbomIsValid(this.ManifestPath, CurrentDirectory, PackageName, PackageVersion, PackageSupplier, NamespaceBaseUri); + } + + private static IEnumerable GetPackageSupplierCases() + { + yield return new object[] { "Test-\nMicrosoft", PackageName, PackageVersion }; + yield return new object[] { "Test\t-Microsoft", PackageName, PackageVersion }; + yield return new object[] { "Test - Microsoft ", PackageName, PackageVersion }; + yield return new object[] { "Test - Mic\tro\nsoft", PackageName, PackageVersion }; + } + + private static IEnumerable GetPackageNameCases() + { + yield return new object[] { PackageSupplier, "CoseSign\nTool", PackageVersion }; + yield return new object[] { PackageSupplier, "Cose\tSign\tTool", PackageVersion }; + yield return new object[] { PackageSupplier, "Cose Sign Tool ", PackageVersion }; + yield return new object[] { PackageSupplier, "Cose S\ti\ngn \n Too\tl", PackageVersion }; + } + + private static IEnumerable GetPackageVersionCases() + { + yield return new object[] { PackageSupplier, PackageName, "0.0\n.1" }; + yield return new object[] { PackageSupplier, PackageName, "0.0\t.1" }; + yield return new object[] { PackageSupplier, PackageName, "0. 0. 1" }; + yield return new object[] { PackageSupplier, PackageName, "0 . \t 0 \n .1" }; + } + [TestMethod] public void Sbom_Is_Successfully_Generated_In_Specified_Location() { @@ -238,9 +308,12 @@ public void Sbom_Is_Successfully_Generated_With_Component_Path() } [TestMethod] - public void Sbom_Is_Successfully_Generated_With_Unique_Namespace_Part_Defined() + [DataRow("550e8400-e29b-41d4-a716-446655440000")] // Standard random GUID + [DataRow("3F2504E0-4f89-11D3-9A0C-0305E82c3301")] // Mixed cases + [DataRow("3F2504E04F8911D39A0C0305E82C3301")] // Guids without hyphens + [DataRow(" 3F2504E0-4F89-11D3-9A0C-0305E82C3301 ")] // Guids with trailing spaces + public void Sbom_Is_Successfully_Generated_With_Unique_Namespace_Part_Defined(string uniqueNamespacePart) { - var uniqueNamespacePart = Guid.NewGuid().ToString(); // Arrange var task = new GenerateSbomTask { diff --git a/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs b/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs index 3a7dc466..5e775811 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs +++ b/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs @@ -88,11 +88,11 @@ internal void AssertSbomIsValid(string manifestPath, string buildDropPath, strin if (expectedNamespaceUriUniquePart != null) { - Assert.IsTrue(namespaceValue.Equals($"{expectedNamespaceUriBase}/{expectedPackageName}/{expectedPackageVersion}/{expectedNamespaceUriUniquePart}", StringComparison.InvariantCultureIgnoreCase)); + Assert.IsTrue(namespaceValue.Equals($"{expectedNamespaceUriBase.Trim()}/{expectedPackageName}/{expectedPackageVersion}/{expectedNamespaceUriUniquePart.Trim()}", StringComparison.InvariantCultureIgnoreCase)); } else { - Assert.IsTrue(namespaceValue.Contains($"{expectedNamespaceUriBase}/{expectedPackageName}/{expectedPackageVersion}", StringComparison.InvariantCultureIgnoreCase)); + Assert.IsTrue(namespaceValue.Contains($"{expectedNamespaceUriBase.Trim()}/{expectedPackageName}/{expectedPackageVersion}", StringComparison.InvariantCultureIgnoreCase)); } } } From 245de38fe5ad4e46ce708994a4b7f547beb8e8e5 Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Wed, 19 Jun 2024 15:21:30 -0700 Subject: [PATCH 10/36] Merging Varshita's branch into our feature branch (#12) * setting up imports * Add reference to local Nuget package, for testing purposes * rename targets and props, export them to the build folder * Fix test project * Fix Targets file to include props * Manually adding the Sources Providers that support ProviderType.Packages * Manually add the missing classes for SBOM generation * Add MSBuild properties to our Props file (#8) * Use MSBuild/.NET props for default values of the Generate SBOM task. * Remove hardcoded path from the Targets * Add default value to props file for SbomGenerationManifestDirPath * Add final ManifestDirPath to SbomGenerationResult. * Fix typo * Change Summary comment for ManifestDirPath * include sbom files in user's nuget packages (#11) Co-authored-by: vpatakottu * Make the task target .net 8 and .net 6 (#13) * Remove unrooted checks (#14) * Downgrade Microsoft.Extensions.Hosting back to 7.0.1 * Remove LocalNuget configuration * Stop tracking nuspec file * Remove unnecessary comments. * Remove reference to Microsoft.Sbom.Targets Nuget * Apply suggestions from the linter and PR comments. --------- Co-authored-by: vpatakottu Co-authored-by: vpatakottu <47004464+vpatakottu@users.noreply.github.com> --- .gitignore | 1 + Directory.Packages.props | 2 +- Microsoft.Sbom.sln | 2 +- nuget.config | 4 +- src/GenerateSBOMTest/GenerateSBOMTest.sln | 25 ++++ .../GenerateSBOMTest/GenerateSBOMTest.csproj | 19 ++++ .../GenerateSBOMTest/Program.cs | 3 + src/Microsoft.Sbom.Api/SBOMGenerator.cs | 4 +- .../Contracts/SBOMGenerationResult.cs | 8 +- .../GenerateSbomTask.cs | 107 +++++++++--------- .../Microsoft.Sbom.Targets.csproj | 68 ++++++----- .../Microsoft.Sbom.Targets.props | 21 ++++ .../Microsoft.Sbom.Targets.targets | 46 ++++++++ .../SbomExecution.props | 20 ---- .../SbomExecution.targets | 33 ------ .../AbstractGenerateSBomTaskInputTests.cs | 43 ------- .../Microsoft.Sbom.Targets.Tests.csproj | 4 + 17 files changed, 226 insertions(+), 184 deletions(-) create mode 100644 src/GenerateSBOMTest/GenerateSBOMTest.sln create mode 100644 src/GenerateSBOMTest/GenerateSBOMTest/GenerateSBOMTest.csproj create mode 100644 src/GenerateSBOMTest/GenerateSBOMTest/Program.cs create mode 100644 src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.props create mode 100644 src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets delete mode 100644 src/Microsoft.Sbom.Targets/SbomExecution.props delete mode 100644 src/Microsoft.Sbom.Targets/SbomExecution.targets diff --git a/.gitignore b/.gitignore index 94f3f90d..7f5dec04 100644 --- a/.gitignore +++ b/.gitignore @@ -189,6 +189,7 @@ PublishScripts/ # NuGet Packages *.nupkg +*.nuspec # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore diff --git a/Directory.Packages.props b/Directory.Packages.props index 910bfa7e..20cd09e7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -57,4 +57,4 @@ - \ No newline at end of file + diff --git a/Microsoft.Sbom.sln b/Microsoft.Sbom.sln index 5da6022a..0de060d1 100644 --- a/Microsoft.Sbom.sln +++ b/Microsoft.Sbom.sln @@ -51,7 +51,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Extensions.D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Targets", "src\Microsoft.Sbom.Targets\Microsoft.Sbom.Targets.csproj", "{E6C3C851-EEA0-466E-BA36-73ED85F13EEA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Sbom.Targets.Tests", "test\Microsoft.Sbom.Targets.Tests\Microsoft.Sbom.Targets.Tests.csproj", "{E31B914C-F24B-4DC8-ACC7-CAEA952563B8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Targets.Tests", "test\Microsoft.Sbom.Targets.Tests\Microsoft.Sbom.Targets.Tests.csproj", "{E31B914C-F24B-4DC8-ACC7-CAEA952563B8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/nuget.config b/nuget.config index 248a5bb5..765346e5 100644 --- a/nuget.config +++ b/nuget.config @@ -1,7 +1,7 @@ - + - \ No newline at end of file + diff --git a/src/GenerateSBOMTest/GenerateSBOMTest.sln b/src/GenerateSBOMTest/GenerateSBOMTest.sln new file mode 100644 index 00000000..0c4039a6 --- /dev/null +++ b/src/GenerateSBOMTest/GenerateSBOMTest.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.34929.205 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenerateSBOMTest", "GenerateSBOMTest\GenerateSBOMTest.csproj", "{42BFEE5C-290D-4E99-9247-6AE26FA57227}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {42BFEE5C-290D-4E99-9247-6AE26FA57227}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42BFEE5C-290D-4E99-9247-6AE26FA57227}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42BFEE5C-290D-4E99-9247-6AE26FA57227}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42BFEE5C-290D-4E99-9247-6AE26FA57227}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {424A9B69-FF02-48BE-8D4C-B7D85BCFF76C} + EndGlobalSection +EndGlobal diff --git a/src/GenerateSBOMTest/GenerateSBOMTest/GenerateSBOMTest.csproj b/src/GenerateSBOMTest/GenerateSBOMTest/GenerateSBOMTest.csproj new file mode 100644 index 00000000..038641f4 --- /dev/null +++ b/src/GenerateSBOMTest/GenerateSBOMTest/GenerateSBOMTest.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + diff --git a/src/GenerateSBOMTest/GenerateSBOMTest/Program.cs b/src/GenerateSBOMTest/GenerateSBOMTest/Program.cs new file mode 100644 index 00000000..2bb61757 --- /dev/null +++ b/src/GenerateSBOMTest/GenerateSBOMTest/Program.cs @@ -0,0 +1,3 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +Console.WriteLine("Hello, World!"); diff --git a/src/Microsoft.Sbom.Api/SBOMGenerator.cs b/src/Microsoft.Sbom.Api/SBOMGenerator.cs index 06c9d259..a226c81b 100644 --- a/src/Microsoft.Sbom.Api/SBOMGenerator.cs +++ b/src/Microsoft.Sbom.Api/SBOMGenerator.cs @@ -80,7 +80,7 @@ public async Task GenerateSbomAsync( var entityErrors = recorder.Errors.Select(error => error.ToEntityError()).ToList(); - return new SbomGenerationResult(isSuccess, entityErrors); + return new SbomGenerationResult(isSuccess, entityErrors, isSuccess ? inputConfiguration.ManifestDirPath.ToString() : null); } /// @@ -120,7 +120,7 @@ public async Task GenerateSbomAsync( // This is the generate workflow var result = await generationWorkflow.RunAsync(); - return new SbomGenerationResult(result, new List()); + return new SbomGenerationResult(result, new List(), result ? inputConfiguration.ManifestDirPath.ToString() : null); } /// diff --git a/src/Microsoft.Sbom.Contracts/Contracts/SBOMGenerationResult.cs b/src/Microsoft.Sbom.Contracts/Contracts/SBOMGenerationResult.cs index b57bde7d..f6f2010e 100644 --- a/src/Microsoft.Sbom.Contracts/Contracts/SBOMGenerationResult.cs +++ b/src/Microsoft.Sbom.Contracts/Contracts/SBOMGenerationResult.cs @@ -21,9 +21,15 @@ public class SbomGenerationResult /// public IList Errors { get; private set; } - public SbomGenerationResult(bool isSuccessful, IList errors) + /// + /// Gets the path where the SBOM was generated, if the generation was successful. + /// + public string? ManifestDirPath { get; private set; } + + public SbomGenerationResult(bool isSuccessful, IList errors, string manifestDirPath = null) { IsSuccessful = isSuccessful; Errors = errors ?? new List(); + this.ManifestDirPath = manifestDirPath; } } diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index 6919677b..6225a037 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -11,16 +11,26 @@ namespace Microsoft.Sbom.Targets; using Microsoft.Build.Utilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Sbom.Api.Manifest.ManifestConfigHandlers; +using Microsoft.Sbom.Api.Metadata; +using Microsoft.Sbom.Api.Providers; +using Microsoft.Sbom.Api.Providers.ExternalDocumentReferenceProviders; +using Microsoft.Sbom.Api.Providers.FilesProviders; +using Microsoft.Sbom.Api.Providers.PackagesProviders; using Microsoft.Sbom.Contracts; +using Microsoft.Sbom.Contracts.Entities; +using Microsoft.Sbom.Contracts.Interfaces; +using Microsoft.Sbom.Extensions; using Microsoft.Sbom.Extensions.DependencyInjection; +using Microsoft.Sbom.Parsers.Spdx22SbomParser; +/// +/// MSBuild task for generating SBOMs from build output. +/// public class GenerateSbomTask : Task { - // TODO it is possible we will want to expose additional arguments, either as required or optional. - // Will need to get SDK team/ windows team input on which arguments are necessary. - /// - /// The path to the drop directory for which the SBOM will be generated + /// The path to the drop directory for which the SBOM will be generated. /// [Required] public string BuildDropPath { get; set; } @@ -49,7 +59,7 @@ public class GenerateSbomTask : Task [Required] public string NamespaceBaseUri { get; set; } - /// + /// /// The path to the directory containing build components and package information. /// For example, path to a .csproj or packages.config file. /// @@ -97,27 +107,52 @@ public class GenerateSbomTask : Task /// public string ManifestDirPath { get; set; } + /// + /// The path to the generated SBOM directory. + /// [Output] public string SbomPath { get; set; } private ISBOMGenerator Generator { get; set; } + /// + /// Constructor for the GenerateSbomTask. + /// public GenerateSbomTask() { var host = Host.CreateDefaultBuilder() .ConfigureServices((host, services) => services - .AddSbomTool()) + .AddSbomTool() + /* Manually adding some dependencies since `AddSbomTool()` does not add them when + * running the MSBuild Task from another project. + */ + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton()) .Build(); this.Generator = host.Services.GetRequiredService(); } + /// public override bool Execute() { try { // Validate required args and args that take paths as input. - if (!ValidateAndSanitizeRequiredParams() || !ValidateRootedPaths() || !ValidateAndSanitizeNamespaceUriUniquePart()) + if (!ValidateAndSanitizeRequiredParams() || !ValidateAndSanitizeNamespaceUriUniquePart()) { return false; } @@ -136,7 +171,7 @@ public override bool Execute() NamespaceUriBase = this.NamespaceBaseUri, NamespaceUriUniquePart = this.NamespaceUriUniquePart, DeleteManifestDirectoryIfPresent = this.DeleteManifestDirIfPresent, - Verbosity = ValidateAndAssignVerbosity() + Verbosity = ValidateAndAssignVerbosity(), }; #pragma warning disable VSTHRD002 // Avoid problematic synchronous waits var result = System.Threading.Tasks.Task.Run(() => this.Generator.GenerateSbomAsync( @@ -149,7 +184,7 @@ public override bool Execute() externalDocumentReferenceListFile: this.ExternalDocumentListFile)).GetAwaiter().GetResult(); #pragma warning restore VSTHRD002 // Avoid problematic synchronous waits - SbomPath = "path/to/sbom"; + SbomPath = !string.IsNullOrWhiteSpace(result.ManifestDirPath) ? Path.GetFullPath(result.ManifestDirPath) : null; return result.IsSuccessful; } catch (Exception e) @@ -169,7 +204,7 @@ private string Remove_Spaces_Tabs_Newlines(string value) /// Ensure all required arguments are non-null/empty, /// and do not contain whitespaces, tabs, or newline characters. /// - /// + /// True if the required parameters are valid. False otherwise. private bool ValidateAndSanitizeRequiredParams() { if (string.IsNullOrWhiteSpace(this.BuildDropPath)) @@ -211,43 +246,6 @@ private bool ValidateAndSanitizeRequiredParams() return true; } - /// - /// Ensure all arguments that accept paths are rooted. - /// - /// - private bool ValidateRootedPaths() - { - if (!Path.IsPathRooted(this.BuildDropPath)) - { - Log.LogError($"SBOM generation failed: Unrooted path detected. Please specify a full path for {nameof(this.BuildDropPath)}. " + - $"Current value is {this.BuildDropPath}"); - return false; - } - - if (!string.IsNullOrWhiteSpace(this.BuildComponentPath) && !Path.IsPathRooted(this.BuildComponentPath)) - { - Log.LogError($"SBOM generation failed: Unrooted path detected. Please specify a full path for {nameof(this.BuildComponentPath)}. " + - $"Current value is {this.BuildComponentPath}"); - return false; - } - - if (!string.IsNullOrWhiteSpace(this.ManifestDirPath) && !Path.IsPathRooted(this.ManifestDirPath)) - { - Log.LogError($"SBOM generation failed: Unrooted path detected. Please specify a full path for {nameof(this.ManifestDirPath)}. " + - $"Current value is {this.ManifestDirPath}"); - return false; - } - - if (!string.IsNullOrWhiteSpace(this.ExternalDocumentListFile) && !Path.IsPathRooted(this.ExternalDocumentListFile)) - { - Log.LogError($"SBOM generation failed: Unrooted path detected. Please specify a full path for {nameof(this.ExternalDocumentListFile)}. " + - $"Current value is {this.ExternalDocumentListFile}"); - return false; - } - - return true; - } - /// /// Checks the user's input for Verbosity and assigns the /// associated EventLevel value for logging. @@ -260,7 +258,8 @@ private EventLevel ValidateAndAssignVerbosity() return EventLevel.LogAlways; } - if (Enum.TryParse(this.Verbosity, true, out EventLevel eventLevel)) { + if (Enum.TryParse(this.Verbosity, true, out EventLevel eventLevel)) + { return eventLevel; } @@ -269,28 +268,28 @@ private EventLevel ValidateAndAssignVerbosity() } /// - /// Check for ManifestInfo and create an SbomSpecification accordingly + /// Check for ManifestInfo and create an SbomSpecification accordingly. /// - /// + /// A list of the parsed manifest info. Null ig the manifest info is null or empty. private IList ValidateAndAssignSpecifications() { if (!string.IsNullOrWhiteSpace(this.ManifestInfo)) { - return new List { SbomSpecification.Parse(this.ManifestInfo) }; + return [SbomSpecification.Parse(this.ManifestInfo)]; } return null; } /// - /// Ensure a valid NamespaceUriUniquePart is provided + /// Ensure a valid NamespaceUriUniquePart is provided. /// - /// + /// True if the Namespace URI unique part is valid. False otherwise. private bool ValidateAndSanitizeNamespaceUriUniquePart() { // Ensure the NamespaceUriUniquePart is valid if provided. if (!string.IsNullOrWhiteSpace(this.NamespaceUriUniquePart) - && (!Guid.TryParse(this.NamespaceUriUniquePart, out var guidResult) + && (!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."); diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj index 6dd5cf57..e2f4729b 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj @@ -4,40 +4,54 @@ Microsoft.Sbom.Targets win-x64;osx-x64;linux-x64 true + true + true + 1.0.0 + GenerateSbomTask Tasks and targets for running the SBOM tool. + true + + + + $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage + + + tasks + + NU5100 + + true + true + + + + + + + + + + + + + + + - - + + + - - + - - - + + + diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.props b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.props new file mode 100644 index 00000000..4593f8db --- /dev/null +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.props @@ -0,0 +1,21 @@ + + + false + + $(OutDir) + $(MSBuildProjectDirectory) + $(Authors) + $(AssemblyName) + $(PackageId) + $(AssemblyName) + $(Version) + 1.0.0 + http://spdx.org/spdxdocs/$(SbomGenerationPackageName)" + false + false + LogAlways + SPDX:2.2 + true + + diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets new file mode 100644 index 00000000..25825534 --- /dev/null +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -0,0 +1,46 @@ + + + + + + + net6.0 + net8.0 + + + + + + + + + + + + + + true + _manifest + + + + diff --git a/src/Microsoft.Sbom.Targets/SbomExecution.props b/src/Microsoft.Sbom.Targets/SbomExecution.props deleted file mode 100644 index 806fd7a1..00000000 --- a/src/Microsoft.Sbom.Targets/SbomExecution.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - $(OutputPath) - $(MSBuildProjectDirectory) - test-supplier - test-name - 1.0.0 - http://spdx.org/spdxdocs/$(SbomGenerationPackageName) - - - false - false - LogAlways - - true - - - diff --git a/src/Microsoft.Sbom.Targets/SbomExecution.targets b/src/Microsoft.Sbom.Targets/SbomExecution.targets deleted file mode 100644 index 21f945a2..00000000 --- a/src/Microsoft.Sbom.Targets/SbomExecution.targets +++ /dev/null @@ -1,33 +0,0 @@ - - - diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs index da7af181..a91dbaa9 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs @@ -184,49 +184,6 @@ public void Sbom_Generation_Fails_For_Invalid_NamespaceUriUniquePart(string name Assert.IsFalse(result); } - /// - /// Test for ensuring the GenerateSbomTask fails when relative paths are - /// provided for all path arguments, which includes BuildDroppath, BuildComponentPath, - /// ManifestDirPath, and ExternalDocumentListFile - /// - [TestMethod] - [DynamicData(nameof(GetUnrootedPathTestData), DynamicDataSourceType.Method)] - public void Sbom_Fails_With_Unrooted_Paths( - string buildDropPath, - string buildComponentPath, - string manifestDirPath, - string externalDocumentListFile) - { - // Arrange. - var task = new GenerateSbomTask - { - BuildDropPath = buildDropPath, - PackageSupplier = PackageSupplier, - PackageName = PackageName, - PackageVersion = PackageVersion, - NamespaceBaseUri = NamespaceBaseUri, - BuildComponentPath = buildComponentPath, - ManifestDirPath = manifestDirPath, - ExternalDocumentListFile = externalDocumentListFile, - ManifestInfo = this.SbomSpecification.ToString(), - BuildEngine = this.buildEngine.Object - }; - - // Act - var result = task.Execute(); - - // Assert - Assert.IsFalse(result); - } - - private static IEnumerable GetUnrootedPathTestData() - { - yield return new object[] { Path.Combine("..", ".."), BuildComponentPath, DefaultManifestDirectory, ExternalDocumentListFile }; - yield return new object[] { CurrentDirectory, Path.Combine("..", ".."), DefaultManifestDirectory, ExternalDocumentListFile }; - yield return new object[] { CurrentDirectory, BuildComponentPath, Path.Combine("..", ".."), ExternalDocumentListFile }; - yield return new object[] { CurrentDirectory, BuildComponentPath, DefaultManifestDirectory, Path.Combine("..", "..") }; - } - /// /// Test for ensuring GenerateSbomTask assigns a defualt Verbosity /// level when null input is provided. diff --git a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj index cd7d8162..09913734 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj +++ b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj @@ -11,6 +11,10 @@ TRACE + + + + From 288626c09193873794873d1ef3364978ecf88401 Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Thu, 20 Jun 2024 11:32:13 -0700 Subject: [PATCH 11/36] Fix ubuntu tests (#16) * add users/gustavoca/net-sdk-sbom-tool branch to PR pipelines * Fix Ubuntu tests for Targets project --- .github/workflows/build.yml | 4 ++++ .../AbstractGenerateSbomTaskTests.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d7efb25..6399289a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,9 +4,13 @@ on: push: branches: - main + # Remove before merging into main + - users/gustavoca/net-sdk-sbom-tool pull_request: branches: - main + # Remove before merging into main + - users/gustavoca/net-sdk-sbom-tool permissions: contents: read diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs index e331decc..f09ba5dc 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs @@ -29,7 +29,7 @@ public abstract class AbstractGenerateSbomTaskTests internal string ManifestPath; internal GeneratedSbomValidator GeneratedSbomValidator; - internal string SbomSpecificationDirectoryName => $"{this.SbomSpecification.Name}_{this.SbomSpecification.Version}"; + internal string SbomSpecificationDirectoryName => $"{this.SbomSpecification.Name}_{this.SbomSpecification.Version}".ToLowerInvariant(); [TestInitialize] public void Startup() From a0288610acd9d96f58357767b94c9c0a45e53850 Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Mon, 24 Jun 2024 10:33:03 -0700 Subject: [PATCH 12/36] Update feature branch (#17) * build(deps): bump actions/checkout from 4.1.1 to 4.1.6 (#574) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/b4ffde65f46336ab88eb53be808477a3936bae11...a5ac7e51b41094c92402da3b24376905380afc29) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump github/codeql-action from 3.24.3 to 3.25.8 (#591) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.3 to 3.25.8. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/379614612a29c9e28f31f39a59013eb8012a51f0...2e230e8fe0ad3a14a340ad0815ddb96d599d2aff) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 8 ++++---- .github/workflows/gen-docs.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6399289a..5e36b6e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: MINVERBUILDMETADATA: build.${{github.run_number}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 0 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d41b757b..74276c3d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -23,15 +23,15 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Initialize CodeQL - uses: github/codeql-action/init@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3 + uses: github/codeql-action/init@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 with: languages: csharp - name: Autobuild - uses: github/codeql-action/autobuild@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3 + uses: github/codeql-action/autobuild@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3 + uses: github/codeql-action/analyze@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 diff --git a/.github/workflows/gen-docs.yml b/.github/workflows/gen-docs.yml index e2162198..87123b41 100644 --- a/.github/workflows/gen-docs.yml +++ b/.github/workflows/gen-docs.yml @@ -16,7 +16,7 @@ jobs: gen-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Setup .NET uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 From 22ee3b5b436f8980784ec9f561118932bd067439 Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Mon, 24 Jun 2024 18:53:31 -0700 Subject: [PATCH 13/36] Add missing header to AbstractGenerateSbomTaskTests (#598) --- .../AbstractGenerateSbomTaskTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs index f09ba5dc..aa054b48 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.Collections.Generic; using System.IO; using Microsoft.Build.Framework; From abdb4a0f151d515e36701cd1d4bda03cf9f1d5c3 Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:52:11 -0700 Subject: [PATCH 14/36] Create template tool task (#600) * create template tool task * Make the tests successfully run * Include the SBOM CLI Tool to the .NET Framework package folder. * refactor code a little and address feedback @microsoft-github-policy-service agree company="Microsoft" --------- Co-authored-by: vpatakottu Co-authored-by: gustavoaca1997 --- src/Microsoft.Sbom.Targets/GenerateSbom.cs | 106 ++++++++++++++++++ .../GenerateSbomTask.cs | 88 +-------------- .../Microsoft.Sbom.Targets.csproj | 30 ++++- .../Microsoft.Sbom.Targets.targets | 28 +++-- src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs | 43 +++++++ .../AbstractGenerateSBomTaskInputTests.cs | 24 ++-- .../AbstractGenerateSbomTaskTests.cs | 22 ++-- .../Microsoft.Sbom.Targets.Tests.csproj | 2 + 8 files changed, 223 insertions(+), 120 deletions(-) create mode 100644 src/Microsoft.Sbom.Targets/GenerateSbom.cs create mode 100644 src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs diff --git a/src/Microsoft.Sbom.Targets/GenerateSbom.cs b/src/Microsoft.Sbom.Targets/GenerateSbom.cs new file mode 100644 index 00000000..18e6fa62 --- /dev/null +++ b/src/Microsoft.Sbom.Targets/GenerateSbom.cs @@ -0,0 +1,106 @@ +// 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; +using Microsoft.Build.Framework; + +/// +/// This partial class defines and sanitizes the arguments that will be passed +/// into the SBOM API and CLI tool for generation. +/// +public partial class GenerateSbom +{ + /// + /// Gets or sets the path to the drop directory for which the SBOM will be generated. + /// + [Required] + public string BuildDropPath { get; set; } + + /// + /// Gets or sets the supplier of the package the SBOM represents. + /// + [Required] + public string PackageSupplier { get; set; } + + /// + /// Gets or sets the name of the package the SBOM represents. + /// + [Required] + public string PackageName { get; set; } + + /// + /// Gets or sets the version of the package the SBOM represents. + /// + [Required] + public string PackageVersion { get; set; } + + /// + /// Gets or sets the base path of the SBOM namespace uri. + /// + [Required] + public string NamespaceBaseUri { get; set; } + + /// + /// Gets or sets the path to the directory containing build components and package information. + /// For example, path to a .csproj or packages.config file. + /// + public string BuildComponentPath { get; set; } + + /// + /// Gets or sets a unique URI part that will be appended to NamespaceBaseUri. + /// + public string NamespaceUriUniquePart { get; set; } + + /// + /// Gets or sets the path to a file containing a list of external SBOMs that will be appended to the + /// SBOM that is being generated. + /// + public string ExternalDocumentListFile { get; set; } + + /// + /// Indicates whether licensing information will be fetched for detected packages. + /// + public bool FetchLicenseInformation { get; set; } + + /// + /// Indicates whether to parse licensing and supplier information from a packages metadata file. + /// + public bool EnablePackageMetadataParsing { get; set; } + + /// + /// Gets or sets the verbosity level for logging output. + /// + public string Verbosity { get; set; } + + /// + /// Gets or sets a list of names and versions of the manifest format being used. + /// + public string ManifestInfo { get; set; } + + /// + /// Indicates whether the previously generated SBOM manifest directory should be deleted + /// before generating a new SBOM in the directory specified by ManifestDirPath. + /// Defaults to true. + /// + public bool DeleteManifestDirIfPresent { get; set; } = true; + + /// + /// Gets or sets the path where the SBOM will be generated. + /// + public string ManifestDirPath { get; set; } + + /// + /// Gets or sets the path to the SBOM CLI tool + /// + public string SbomToolPath { get; set; } + + /// + /// Gets or sets the path to the generated SBOM directory. + /// + [Output] + public string SbomPath { get; set; } +} diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index 6225a037..09bf332d 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -27,98 +27,14 @@ namespace Microsoft.Sbom.Targets; /// /// MSBuild task for generating SBOMs from build output. /// -public class GenerateSbomTask : Task +public partial class GenerateSbom : Task { - /// - /// The path to the drop directory for which the SBOM will be generated. - /// - [Required] - public string BuildDropPath { get; set; } - - /// - /// Supplier of the package the SBOM represents. - /// - [Required] - public string PackageSupplier { get; set; } - - /// - /// Name of the package the SBOM represents. - /// - [Required] - public string PackageName { get; set; } - - /// - /// Version of the package the SBOM represents. - /// - [Required] - public string PackageVersion { get; set; } - - /// - /// The base path of the SBOM namespace uri. - /// - [Required] - public string NamespaceBaseUri { get; set; } - - /// - /// The path to the directory containing build components and package information. - /// For example, path to a .csproj or packages.config file. - /// - public string BuildComponentPath { get; set; } - - /// - /// A unique URI part that will be appended to NamespaceBaseUri. - /// - public string NamespaceUriUniquePart { get; set; } - - /// - /// The path to a file containing a list of external SBOMs that will be appended to the - /// SBOM that is being generated. - /// - public string ExternalDocumentListFile { get; set; } - - /// - /// If true, it will fetch licensing information for detected packages. - /// - public bool FetchLicenseInformation { get; set; } - - /// - /// If true, it will parse licensing and supplier information from a packages metadata file. - /// - public bool EnablePackageMetadataParsing { get; set; } - - /// - /// Determines how detailed the outputed logging will be. - /// - public string Verbosity { get; set; } - - /// - /// A list of the name and version of the manifest format being used. - /// - public string ManifestInfo { get; set; } - - /// - /// If true, it will delete the previously generated SBOM manifest directory before - /// generating a new SBOM in ManifestDirPath. - /// - public bool DeleteManifestDirIfPresent { get; set; } = true; - - /// - /// The path where the SBOM will be generated. - /// - public string ManifestDirPath { get; set; } - - /// - /// The path to the generated SBOM directory. - /// - [Output] - public string SbomPath { get; set; } - private ISBOMGenerator Generator { get; set; } /// /// Constructor for the GenerateSbomTask. /// - public GenerateSbomTask() + public GenerateSbom() { var host = Host.CreateDefaultBuilder() .ConfigureServices((host, services) => diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj index e2f4729b..a2c94d9e 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj @@ -2,6 +2,7 @@ Microsoft.Sbom.Targets + net8.0;net472 win-x64;osx-x64;linux-x64 true true @@ -10,6 +11,7 @@ GenerateSbomTask Tasks and targets for running the SBOM tool. true + net8.0 @@ -17,6 +19,7 @@ tasks + true NU5100 @@ -39,6 +42,19 @@ + + + + + + Always + true + \tasks\net472\sbom-tool\ + true + + + + + - - net6.0 - net8.0 + net472 + net8.0 + + + $(MSBuildThisFileDirectory)\..\tasks\$(GenerateSbom_TFM)\sbom-tool - + + + + - - + + - - + ManifestDirPath="$(SbomGenerationManifestDirPath)" + SbomToolPath="$(SbomToolPath)"> + + diff --git a/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs b/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs new file mode 100644 index 00000000..94e8f475 --- /dev/null +++ b/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs @@ -0,0 +1,43 @@ +// 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.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +/// +/// MSBuild ToolTask for generating an SBOM using the SBOM CLI tool +/// +public partial class GenerateSbom : ToolTask +{ + protected override string ToolName => "Microsoft.Sbom.Tool"; + + /// + /// Get full path to SBOM CLI tool. + /// + /// + protected override string GenerateFullPathToTool() + { + return Path.Combine(this.SbomToolPath, $"{this.ToolName}.exe"); + } + + /// + /// Return a formatted list of arguments for the SBOM CLI tool. + /// + /// string list of args + protected override string GenerateCommandLineCommands() + { + return "Command"; + } + + /// + /// Validates the SBOM CLI tool parameters + /// + /// + protected override bool ValidateParameters() + { + return true; + } +} diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs index a91dbaa9..84ae0688 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs @@ -55,7 +55,7 @@ public void Cleanup() { } /// - /// Test for ensuring the GenerateSbomTask fails for null or empty inputs for + /// Test for ensuring the GenerateSbom fails for null or empty inputs for /// required params, which includes BuildDropPath, PackageSupplier, PackageName, /// PackageVersion, and NamespaceBaseUri. /// @@ -71,7 +71,7 @@ public void Sbom_Fails_With_Null_Empty_And_WhiteSpace_Required_Params( string namespaceBaseUri) { // Arrange. - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = buildDropPath, PackageSupplier = packageSupplier, @@ -117,7 +117,7 @@ private static IEnumerable GetWhiteSpace_Tabs_NewLineParamsData() } /// - /// Test for ensuring the GenerateSbomTask fails when user provides an + /// Test for ensuring the GenerateSbom fails when user provides an /// invalid URI format. /// [TestMethod] @@ -130,7 +130,7 @@ private static IEnumerable GetWhiteSpace_Tabs_NewLineParamsData() public void Sbom_Fails_With_Invalid_NamespaceBaseUri(string namespaceBaseUri) { // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, PackageSupplier = PackageSupplier, @@ -149,7 +149,7 @@ public void Sbom_Fails_With_Invalid_NamespaceBaseUri(string namespaceBaseUri) } /// - /// Test for ensuring the GenerateSbomTask fails when user provides + /// Test for ensuring the GenerateSbom fails when user provides /// an invalid GUID for NamespaceUriUniquePart. /// [TestMethod] @@ -165,7 +165,7 @@ public void Sbom_Fails_With_Invalid_NamespaceBaseUri(string namespaceBaseUri) public void Sbom_Generation_Fails_For_Invalid_NamespaceUriUniquePart(string namespaceUriUniquePart) { // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, PackageSupplier = PackageSupplier, @@ -185,7 +185,7 @@ public void Sbom_Generation_Fails_For_Invalid_NamespaceUriUniquePart(string name } /// - /// Test for ensuring GenerateSbomTask assigns a defualt Verbosity + /// Test for ensuring GenerateSbom assigns a defualt Verbosity /// level when null input is provided. /// [TestMethod] @@ -197,7 +197,7 @@ public void Sbom_Generation_Succeeds_For_Null_Verbosity() var pattern = new Regex("Verbosity=.*Value=Verbose"); var stringWriter = new StringWriter(); Console.SetOut(stringWriter); - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, PackageSupplier = PackageSupplier, @@ -219,7 +219,7 @@ public void Sbom_Generation_Succeeds_For_Null_Verbosity() } /// - /// Test for ensuring GenerateSbomTask assigns a default Verbosity for + /// Test for ensuring GenerateSbom assigns a default Verbosity for /// unrecognized input. /// [TestMethod] @@ -231,7 +231,7 @@ public void Sbom_Generation_Succeeds_For_Invalid_Verbosity() var pattern = new Regex("Verbosity=.*Value=Verbose"); var stringWriter = new StringWriter(); Console.SetOut(stringWriter); - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, PackageSupplier = PackageSupplier, @@ -253,7 +253,7 @@ public void Sbom_Generation_Succeeds_For_Invalid_Verbosity() } /// - /// Test to ensure GenerateSbomTask correctly parses and provides each EventLevel verbosity + /// Test to ensure GenerateSbom correctly parses and provides each EventLevel verbosity /// values to the SBOM API. /// [TestMethod] @@ -269,7 +269,7 @@ public void Sbom_Generation_Assigns_Correct_Verbosity_IgnoreCase(string inputVer var pattern = new Regex($"Verbosity=.*Value={mappedVerbosity}"); var stringWriter = new StringWriter(); Console.SetOut(stringWriter); - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, PackageSupplier = PackageSupplier, diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs index aa054b48..c43d072d 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Sbom.Targets.Tests; /// -/// Base class for testing SBOM generation through the GenerateSbomTask. +/// Base class for testing SBOM generation through the GenerateSbom. /// [TestClass] public abstract class AbstractGenerateSbomTaskTests @@ -62,7 +62,7 @@ public void Startup() public void Sbom_Is_Successfully_Generated() { // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, PackageSupplier = PackageSupplier, @@ -92,7 +92,7 @@ public void Sbom_Is_Successfully_Generated() public void Sbom_Is_Successfully_Generated_Valid_URI(string namespaceBaseUri) { // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, PackageSupplier = PackageSupplier, @@ -118,7 +118,7 @@ public void Sbom_Is_Successfully_Generated_Valid_URI(string namespaceBaseUri) public void Sbom_Is_Successfully_Generated_Valid_RequiredParams(string packageSupplier, string packageName, string packageVersion) { // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, PackageSupplier = packageSupplier, @@ -167,7 +167,7 @@ public void Sbom_Is_Successfully_Generated_In_Specified_Location() var manifestDirPath = Path.Combine(TemporaryDirectory, "sub-directory"); Directory.CreateDirectory(manifestDirPath); // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, ManifestDirPath = manifestDirPath, @@ -193,7 +193,7 @@ public void Sbom_Is_Successfully_Generated_In_Specified_Location() public void Sbom_Generation_Fails_With_NotFound_BuildDropPath() { // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = ".\\non-existent\\path", PackageSupplier = PackageSupplier, @@ -215,7 +215,7 @@ public void Sbom_Generation_Fails_With_NotFound_BuildDropPath() public void Sbom_Generation_Fails_With_NotFound_BuildComponentPath() { // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, BuildComponentPath = ".\\non-existent\\path", @@ -239,7 +239,7 @@ public void Sbom_Generation_Fails_With_NotFound_BuildComponentPath() public void Sbom_Generation_Fails_With_NotFound_ExternalDocumentListFile() { // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, ExternalDocumentListFile = ".\\non-existent\\path", @@ -263,7 +263,7 @@ public void Sbom_Generation_Fails_With_NotFound_ExternalDocumentListFile() public void Sbom_Generation_Fails_With_NotFound_ManifestDirPath() { // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, ManifestDirPath = ".\\non-existent\\path", @@ -290,7 +290,7 @@ public void Sbom_Is_Successfully_Generated_With_Component_Path() var sourceDirectory = Path.Combine(CurrentDirectory, "..", "..", ".."); // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, BuildComponentPath = sourceDirectory, @@ -318,7 +318,7 @@ public void Sbom_Is_Successfully_Generated_With_Component_Path() public void Sbom_Is_Successfully_Generated_With_Unique_Namespace_Part_Defined(string uniqueNamespacePart) { // Arrange - var task = new GenerateSbomTask + var task = new GenerateSbom { BuildDropPath = CurrentDirectory, PackageSupplier = PackageSupplier, diff --git a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj index 09913734..4278eba1 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj +++ b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj @@ -17,6 +17,8 @@ + + From b04a2f54d2a95e171ce829dd51ef7ca3efc055af Mon Sep 17 00:00:00 2001 From: gustavoaca1997 Date: Fri, 28 Jun 2024 19:07:33 -0700 Subject: [PATCH 15/36] Run the Github build also for feature branches. --- .github/workflows/build.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e36b6e3..c587f8b2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,13 +4,11 @@ on: push: branches: - main - # Remove before merging into main - - users/gustavoca/net-sdk-sbom-tool + - 'feature/**' pull_request: branches: - main - # Remove before merging into main - - users/gustavoca/net-sdk-sbom-tool + - 'feature/**' permissions: contents: read From 84441e44445a8c3971dd39c6bef0ef4d94339c22 Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Tue, 9 Jul 2024 18:46:15 -0700 Subject: [PATCH 16/36] Implement SBOM CLI ToolTask (#607) * implement ToolTask * addressing feedback * addressing feedback pt. 2 --------- Co-authored-by: vpatakottu --- .../GenerateSbomTask.cs | 94 ------------- .../Microsoft.Sbom.Targets.csproj | 6 +- .../Microsoft.Sbom.Targets.targets | 13 +- src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs | 62 ++++++++- .../SbomInputValidator.cs | 124 ++++++++++++++++++ .../AbstractGenerateSBomTaskInputTests.cs | 8 +- 6 files changed, 202 insertions(+), 105 deletions(-) create mode 100644 src/Microsoft.Sbom.Targets/SbomInputValidator.cs diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index 09bf332d..82b6ce26 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -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); - } - - /// - /// Ensure all required arguments are non-null/empty, - /// and do not contain whitespaces, tabs, or newline characters. - /// - /// True if the required parameters are valid. False otherwise. - 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; - } - - /// - /// Checks the user's input for Verbosity and assigns the - /// associated EventLevel value for logging. - /// - 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; - } - /// /// Check for ManifestInfo and create an SbomSpecification accordingly. /// @@ -196,26 +124,4 @@ private IList ValidateAndAssignSpecifications() return null; } - - /// - /// Ensure a valid NamespaceUriUniquePart is provided. - /// - /// True if the Namespace URI unique part is valid. False otherwise. - 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; - } } diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj index a2c94d9e..f51b5928 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj @@ -46,7 +46,7 @@ - + Always true \tasks\net472\sbom-tool\ @@ -73,9 +73,7 @@ - - - + diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets index f7cad1b6..5ae9c17c 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -14,6 +14,7 @@ $(MSBuildThisFileDirectory)\..\tasks\$(GenerateSbom_TFM)\sbom-tool + _manifest @@ -45,7 +46,17 @@ - + + true + _manifest + + + + true + _manifest + + + true _manifest diff --git a/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs b/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs index 94e8f475..a050412a 100644 --- a/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs +++ b/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs @@ -3,8 +3,8 @@ namespace Microsoft.Sbom.Targets; +using System.Diagnostics.Tracing; using System.IO; -using Microsoft.Build.Framework; using Microsoft.Build.Utilities; /// @@ -29,7 +29,42 @@ protected override string GenerateFullPathToTool() /// string list of args 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(); } /// @@ -38,6 +73,29 @@ protected override string GenerateCommandLineCommands() /// protected override bool ValidateParameters() { + // Validate required args and args that take paths as input. + if (!ValidateAndSanitizeRequiredParams() || !ValidateAndSanitizeNamespaceUriUniquePart()) + { + return false; + } + + ValidateAndAssignVerbosity(); + SetOutputImportance(); return true; } + + /// + /// 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. + /// + private void SetOutputImportance() + { + this.StandardOutputImportance = "High"; + + if (this.Verbosity.ToLower().Equals("Fatal")) + { + this.StandardOutputImportance = "Low"; + } + } } diff --git a/src/Microsoft.Sbom.Targets/SbomInputValidator.cs b/src/Microsoft.Sbom.Targets/SbomInputValidator.cs new file mode 100644 index 00000000..6076c34c --- /dev/null +++ b/src/Microsoft.Sbom.Targets/SbomInputValidator.cs @@ -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; + +/// +/// Validation class used to sanitize and validate arguments passed into +/// the GenerateSbomTask and SbomCLIToolTask +/// +public partial class GenerateSbom +{ + /// + /// Ensure all required arguments are non-null/empty, + /// and do not contain whitespaces, tabs, or newline characters. + /// + /// True if the required parameters are valid. False otherwise. + public bool ValidateAndSanitizeRequiredParams() + { + var requiredProperties = new Dictionary + { + { 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); + } + + /// + /// 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. + /// + 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; + } + } + + /// + /// Ensure a valid NamespaceUriUniquePart is provided. + /// + /// True if the Namespace URI unique part is valid. False otherwise. + 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; + } +} diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs index 84ae0688..7afe52e5 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs @@ -257,12 +257,12 @@ public void Sbom_Generation_Succeeds_For_Invalid_Verbosity() /// values to the SBOM API. /// [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 From 69b86423efc70f94497f45a55e8ba7d880669a2b Mon Sep 17 00:00:00 2001 From: gustavoaca1997 Date: Thu, 11 Jul 2024 17:06:54 -0700 Subject: [PATCH 17/36] Update System.Text.Json --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 20cd09e7..939606e8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -52,7 +52,7 @@ - + From a3e094d3d7ae514a536629de35961477edaca3f3 Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Thu, 11 Jul 2024 17:17:16 -0700 Subject: [PATCH 18/36] Stop importing the props twice when referencing the Nuget package (#612) --- .../Microsoft.Sbom.Targets.csproj | 1 - .../Microsoft.Sbom.Targets.props | 21 ------------------- .../Microsoft.Sbom.Targets.targets | 19 +++++++++++++++-- 3 files changed, 17 insertions(+), 24 deletions(-) delete mode 100644 src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.props diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj index f51b5928..be7eabbc 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj @@ -59,7 +59,6 @@ - diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.props b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.props deleted file mode 100644 index 4593f8db..00000000 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.props +++ /dev/null @@ -1,21 +0,0 @@ - - - false - - $(OutDir) - $(MSBuildProjectDirectory) - $(Authors) - $(AssemblyName) - $(PackageId) - $(AssemblyName) - $(Version) - 1.0.0 - http://spdx.org/spdxdocs/$(SbomGenerationPackageName)" - false - false - LogAlways - SPDX:2.2 - true - - diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets index 5ae9c17c..84012489 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -20,10 +20,25 @@ - + + false + $(OutDir) + $(MSBuildProjectDirectory) + $(Authors) + $(AssemblyName) + $(PackageId) + $(AssemblyName) + $(Version) + 1.0.0 + http://spdx.org/spdxdocs/$(SbomGenerationPackageName)" + false + false + LogAlways + SPDX:2.2 + true + - Date: Wed, 24 Jul 2024 11:11:48 -0700 Subject: [PATCH 19/36] Add tests for the MSBuild Full version of the Generate SBOM task (#613) * Bring changes from feautre branch * Make the Targets.Tests project also target .NET Framework * Remove props file * Implement tests for MSBuild Full version of the task * Simplify how the CLI tool is called from the tests. * Add test for file being in use. * Test the output of the ToolTask. * Change the name of AbstractGenerateSBomTaskInputTests to AbstractGenerateSbomTaskInputTests * Include .NET Framework output in Sbom_Generation_Succeeds_For_Null_Verbosity * Update src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs Co-authored-by: Dave Tryon <45672944+DaveTryon@users.noreply.github.com> * Skip tests that are failing due to known issues * Add debug messages for the test pipeline * Fix .net core tests * Target .NET Framework only on Windows * Remove unnecessary comment. * Update default Verbosity. * Address comments. * Change name of AbstractGenerateSbomTaskInputTests * Address PR Comments --------- Co-authored-by: Dave Tryon <45672944+DaveTryon@users.noreply.github.com> --- Directory.Packages.props | 3 +- .../Microsoft.Sbom.Targets.targets | 4 +- src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs | 2 +- .../SbomInputValidator.cs | 15 +- ... => AbstractGenerateSbomTaskInputTests.cs} | 177 +++++++++++++----- .../AbstractGenerateSbomTaskTests.cs | 159 +++++++++++++--- .../GenerateSbomTaskSPDX_2_2InputTests.cs | 6 +- .../GenerateSbomTaskSPDX_2_2Tests.cs | 6 +- .../Microsoft.Sbom.Targets.Tests.csproj | 28 ++- .../Utility/GeneratedSbomValidator.cs | 15 +- 10 files changed, 316 insertions(+), 99 deletions(-) rename test/Microsoft.Sbom.Targets.Tests/{AbstractGenerateSBomTaskInputTests.cs => AbstractGenerateSbomTaskInputTests.cs} (61%) diff --git a/Directory.Packages.props b/Directory.Packages.props index 939606e8..d4f4fded 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,6 +16,7 @@ + @@ -57,4 +58,4 @@ - + \ No newline at end of file diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets index 84012489..914941eb 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -30,10 +30,10 @@ $(AssemblyName) $(Version) 1.0.0 - http://spdx.org/spdxdocs/$(SbomGenerationPackageName)" + http://spdx.org/spdxdocs/$(SbomGenerationPackageName) false false - LogAlways + information SPDX:2.2 true diff --git a/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs b/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs index a050412a..99f3692e 100644 --- a/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs +++ b/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs @@ -93,7 +93,7 @@ private void SetOutputImportance() { this.StandardOutputImportance = "High"; - if (this.Verbosity.ToLower().Equals("Fatal")) + if (this.Verbosity.ToLower().Equals("fatal")) { this.StandardOutputImportance = "Low"; } diff --git a/src/Microsoft.Sbom.Targets/SbomInputValidator.cs b/src/Microsoft.Sbom.Targets/SbomInputValidator.cs index 6076c34c..747595ee 100644 --- a/src/Microsoft.Sbom.Targets/SbomInputValidator.cs +++ b/src/Microsoft.Sbom.Targets/SbomInputValidator.cs @@ -13,6 +13,9 @@ namespace Microsoft.Sbom.Targets; /// public partial class GenerateSbom { + private const string DefaultVerbosity = "Information"; + private const EventLevel DefaultEventLevel = EventLevel.Informational; + /// /// Ensure all required arguments are non-null/empty, /// and do not contain whitespaces, tabs, or newline characters. @@ -74,9 +77,9 @@ public EventLevel ValidateAndAssignVerbosity() // 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; + Log.LogWarning($"No verbosity level specified. Setting verbosity level at {DefaultVerbosity}."); + this.Verbosity = DefaultVerbosity; + return DefaultEventLevel; } switch (this.Verbosity.ToLower().Trim()) @@ -94,9 +97,9 @@ public EventLevel ValidateAndAssignVerbosity() case "fatal": return EventLevel.Critical; default: - Log.LogWarning($"Unrecognized verbosity level specified. Setting verbosity level at Verbose"); - this.Verbosity = "Verbose"; - return EventLevel.Verbose; + Log.LogWarning($"Unrecognized verbosity level specified. Setting verbosity level at {DefaultVerbosity}."); + this.Verbosity = DefaultVerbosity; + return DefaultEventLevel; } } diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskInputTests.cs similarity index 61% rename from test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs rename to test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskInputTests.cs index 7afe52e5..5274dbf5 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSBomTaskInputTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskInputTests.cs @@ -4,31 +4,33 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Reflection; using System.Text.RegularExpressions; using Microsoft.Build.Framework; -using Microsoft.Sbom.Contracts; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace Microsoft.Sbom.Targets.Tests; [TestClass] -public abstract class AbstractGenerateSBomTaskInputTests +public abstract class AbstractGenerateSbomTaskInputTests { - internal abstract SbomSpecification SbomSpecification { get; } + internal abstract string SbomSpecification { get; } - internal static readonly string CurrentDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + internal static readonly string CurrentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); internal static readonly string DefaultManifestDirectory = Path.Combine(CurrentDirectory, "_manifest"); internal static readonly string TemporaryDirectory = Path.Combine(CurrentDirectory, "_temporary"); internal static readonly string BuildComponentPath = Path.Combine(CurrentDirectory, "..", "..", ".."); internal static readonly string ExternalDocumentListFile = Path.GetRandomFileName(); + internal static string SbomToolPath = Path.Combine(Directory.GetCurrentDirectory(), "sbom-tool"); internal const string PackageSupplier = "Test-Microsoft"; internal const string PackageName = "CoseSignTool"; internal const string PackageVersion = "0.0.1"; internal const string NamespaceBaseUri = "https://base0.uri"; - private Mock buildEngine; private List errors; + private List messages; [TestInitialize] public void Startup() @@ -36,7 +38,9 @@ public void Startup() // Setup the build engine this.buildEngine = new Mock(); this.errors = new List(); + this.messages = new List(); this.buildEngine.Setup(x => x.LogErrorEvent(It.IsAny())).Callback(e => errors.Add(e)); + this.buildEngine.Setup(x => x.LogMessageEvent(It.IsAny())).Callback(msg => messages.Add(msg)); } [TestCleanup] @@ -68,7 +72,8 @@ public void Sbom_Fails_With_Null_Empty_And_WhiteSpace_Required_Params( string packageSupplier, string packageName, string packageVersion, - string namespaceBaseUri) + string namespaceBaseUri, + string sbomToolPath) { // Arrange. var task = new GenerateSbom @@ -78,8 +83,11 @@ public void Sbom_Fails_With_Null_Empty_And_WhiteSpace_Required_Params( PackageName = packageName, PackageVersion = packageVersion, NamespaceBaseUri = namespaceBaseUri, - ManifestInfo = this.SbomSpecification.ToString(), - BuildEngine = this.buildEngine.Object + ManifestInfo = this.SbomSpecification, + BuildEngine = this.buildEngine.Object, +#if NET472 + SbomToolPath = sbomToolPath, +#endif }; // Act @@ -91,29 +99,38 @@ public void Sbom_Fails_With_Null_Empty_And_WhiteSpace_Required_Params( private static IEnumerable GetNullRequiredParamsData() { - yield return new object[] { null, PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, null, PackageName, PackageVersion, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, PackageSupplier, null, PackageVersion, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, null, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, null }; + yield return new object[] { null, PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, null, PackageName, PackageVersion, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, PackageSupplier, null, PackageVersion, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, null, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, null, SbomToolPath }; +#if NET472 + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri, null }; +#endif } private static IEnumerable GetEmptyRequiredParamsData() { - yield return new object[] { string.Empty, PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, string.Empty, PackageName, PackageVersion, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, PackageSupplier, string.Empty, PackageVersion, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, string.Empty, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, string.Empty }; + yield return new object[] { string.Empty, PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, string.Empty, PackageName, PackageVersion, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, PackageSupplier, string.Empty, PackageVersion, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, string.Empty, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, string.Empty, SbomToolPath }; +#if NET472 + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri, string.Empty }; +#endif } private static IEnumerable GetWhiteSpace_Tabs_NewLineParamsData() { - yield return new object[] { " ", PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, "\n", PackageName, PackageVersion, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, PackageSupplier, "\t", PackageVersion, NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, " \n \t \n \t \n ", NamespaceBaseUri }; - yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, "\t \t \t " }; + yield return new object[] { " ", PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, "\n", PackageName, PackageVersion, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, PackageSupplier, "\t", PackageVersion, NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, " \n \t \n \t \n ", NamespaceBaseUri, SbomToolPath }; + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, "\t \t \t ", SbomToolPath }; +#if NET472 + yield return new object[] { CurrentDirectory, PackageSupplier, PackageName, PackageVersion, NamespaceBaseUri, "\t \t \t " }; +#endif } /// @@ -137,8 +154,11 @@ public void Sbom_Fails_With_Invalid_NamespaceBaseUri(string namespaceBaseUri) PackageName = PackageName, PackageVersion = PackageVersion, NamespaceBaseUri = namespaceBaseUri, - ManifestInfo = this.SbomSpecification.ToString(), - BuildEngine = this.buildEngine.Object + ManifestInfo = this.SbomSpecification, + BuildEngine = this.buildEngine.Object, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -173,8 +193,11 @@ public void Sbom_Generation_Fails_For_Invalid_NamespaceUriUniquePart(string name PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, NamespaceUriUniquePart = namespaceUriUniquePart, - ManifestInfo = this.SbomSpecification.ToString(), - BuildEngine = this.buildEngine.Object + ManifestInfo = this.SbomSpecification, + BuildEngine = this.buildEngine.Object, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -192,9 +215,9 @@ public void Sbom_Generation_Fails_For_Invalid_NamespaceUriUniquePart(string name public void Sbom_Generation_Succeeds_For_Null_Verbosity() { // Arrange - // If Verbosity is null, the default value should be Verbose and is printed in the + // If Verbosity is null, the default value should be Information and is printed in the // tool's standard output. - var pattern = new Regex("Verbosity=.*Value=Verbose"); + var pattern = new Regex("Verbosity=.*Value=Information"); var stringWriter = new StringWriter(); Console.SetOut(stringWriter); var task = new GenerateSbom @@ -204,9 +227,12 @@ public void Sbom_Generation_Succeeds_For_Null_Verbosity() PackageName = PackageName, PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, Verbosity = null, - BuildEngine = this.buildEngine.Object + BuildEngine = this.buildEngine.Object, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -215,7 +241,11 @@ public void Sbom_Generation_Succeeds_For_Null_Verbosity() // Assert Assert.IsTrue(result); +#if NET472 + Assert.IsTrue(this.messages.Any(msg => pattern.IsMatch(msg.Message))); +#else Assert.IsTrue(pattern.IsMatch(output)); +#endif } /// @@ -226,9 +256,9 @@ public void Sbom_Generation_Succeeds_For_Null_Verbosity() public void Sbom_Generation_Succeeds_For_Invalid_Verbosity() { // Arrange - // If an invalid Verbosity is specified, the default value should be Verbose and is printed in the - // tool's standard output. - var pattern = new Regex("Verbosity=.*Value=Verbose"); + // If an invalid Verbosity is specified, the default value should be Information. It is also printed in the + // tool's standard output for the MSBuild Core task. + var pattern = new Regex("Verbosity=.*Value=Information"); var stringWriter = new StringWriter(); Console.SetOut(stringWriter); var task = new GenerateSbom @@ -239,8 +269,11 @@ public void Sbom_Generation_Succeeds_For_Invalid_Verbosity() PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, Verbosity = "Invalid Verbosity", - ManifestInfo = this.SbomSpecification.ToString(), - BuildEngine = this.buildEngine.Object + ManifestInfo = this.SbomSpecification, + BuildEngine = this.buildEngine.Object, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -249,22 +282,32 @@ public void Sbom_Generation_Succeeds_For_Invalid_Verbosity() // Assert Assert.IsTrue(result); +#if NET472 + Assert.IsTrue(this.messages.Any(msg => pattern.IsMatch(msg.Message))); +#else Assert.IsTrue(pattern.IsMatch(output)); +#endif } +#if !NET472 /// /// Test to ensure GenerateSbom correctly parses and provides each EventLevel verbosity /// values to the SBOM API. /// [TestMethod] - [DataRow("FATAL", "Fatal")] - [DataRow("information", "Information")] - [DataRow("vErBose", "Verbose")] - [DataRow("Warning", "Warning")] - [DataRow("eRRor", "Error")] - [DataRow("Debug", "Verbose")] - public void Sbom_Generation_Assigns_Correct_Verbosity_IgnoreCase(string inputVerbosity, string mappedVerbosity) + [DataRow("FATAL", "Fatal", false)] + [DataRow("information", "Information", true)] + [DataRow("vErBose", "Verbose", true)] + [DataRow("Warning", "Warning", false)] + [DataRow("eRRor", "Error", false)] + [DataRow("DeBug", "Verbose", true)] + public void Sbom_Generation_Assigns_Correct_Verbosity_IgnoreCase(string inputVerbosity, string mappedVerbosity, bool messageShouldBeLogged) { + if (!messageShouldBeLogged) + { + Assert.Inconclusive("Cases where the input Verbosity is more restrictive than `Information` are failing due to this issue: https://github.com/microsoft/sbom-tool/issues/616"); + } + // Arrange var pattern = new Regex($"Verbosity=.*Value={mappedVerbosity}"); var stringWriter = new StringWriter(); @@ -277,8 +320,8 @@ public void Sbom_Generation_Assigns_Correct_Verbosity_IgnoreCase(string inputVer PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, Verbosity = inputVerbosity, - ManifestInfo = this.SbomSpecification.ToString(), - BuildEngine = this.buildEngine.Object + ManifestInfo = this.SbomSpecification, + BuildEngine = this.buildEngine.Object, }; // Act @@ -286,7 +329,47 @@ public void Sbom_Generation_Assigns_Correct_Verbosity_IgnoreCase(string inputVer var output = stringWriter.ToString(); // Assert - Assert.IsTrue(result); - Assert.IsTrue(pattern.IsMatch(output)); + Assert.IsTrue(result, $"result: {result} is not set to true"); + Assert.AreEqual(messageShouldBeLogged, pattern.IsMatch(output)); + } +#else + /// + /// Test to ensure GenerateSbom correctly parses and provides each verbosity option + /// to the SBOM CLI. + /// + [TestMethod] + [DataRow("FATAL", "Fatal", false)] + [DataRow("information", "Information", true)] + [DataRow("vErBose", "Verbose", true)] + [DataRow("Warning", "Warning", false)] + [DataRow("eRRor", "Error", false)] + [DataRow("DeBug", "Debug", true)] + public void Sbom_Generation_Assigns_Correct_Verbosity_IgnoreCase(string inputVerbosity, string mappedVerbosity, bool messageShouldBeLogged) + { + // Arrange + var pattern = new Regex($"Verbosity=.*Value={mappedVerbosity}"); + var stringWriter = new StringWriter(); + Console.SetOut(stringWriter); + var task = new GenerateSbom + { + BuildDropPath = CurrentDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + Verbosity = inputVerbosity, + ManifestInfo = this.SbomSpecification, + BuildEngine = this.buildEngine.Object, + SbomToolPath = SbomToolPath, + }; + + // Act + var result = task.Execute(); + var output = stringWriter.ToString(); + + // Assert + Assert.IsTrue(result, $"result: {result} is not set to true"); + Assert.AreEqual(messageShouldBeLogged, this.messages.Any(msg => pattern.IsMatch(msg.Message))); } +#endif } diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs index c43d072d..3008bd84 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.IO; +using System.Reflection; using Microsoft.Build.Framework; -using Microsoft.Sbom.Contracts; using Microsoft.Sbom.Targets.Tests.Utility; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -17,11 +17,16 @@ namespace Microsoft.Sbom.Targets.Tests; [TestClass] public abstract class AbstractGenerateSbomTaskTests { - internal abstract SbomSpecification SbomSpecification { get; } + internal abstract string SbomSpecificationName { get; } - internal static readonly string CurrentDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + internal abstract string SbomSpecificationVersion { get; } + + internal static readonly string CurrentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); internal static readonly string DefaultManifestDirectory = Path.Combine(CurrentDirectory, "_manifest"); internal static readonly string TemporaryDirectory = Path.Combine(CurrentDirectory, "_temp"); + internal static readonly string ExternalDocumentListFile = Path.GetRandomFileName(); + internal static string SbomToolPath = Path.Combine(Directory.GetCurrentDirectory(), "sbom-tool"); + internal const string PackageSupplier = "Test-Microsoft"; internal const string PackageName = "CoseSignTool"; internal const string PackageVersion = "0.0.1"; @@ -32,16 +37,12 @@ public abstract class AbstractGenerateSbomTaskTests internal string ManifestPath; internal GeneratedSbomValidator GeneratedSbomValidator; - internal string SbomSpecificationDirectoryName => $"{this.SbomSpecification.Name}_{this.SbomSpecification.Version}".ToLowerInvariant(); + internal string SbomSpecification => $"{this.SbomSpecificationName}:{this.SbomSpecificationVersion}"; - [TestInitialize] - public void Startup() - { - // Setup the build engine - this.BuildEngine = new Mock(); - this.Errors = new List(); - this.BuildEngine.Setup(x => x.LogErrorEvent(It.IsAny())).Callback(e => Errors.Add(e)); + internal string SbomSpecificationDirectoryName => $"{this.SbomSpecificationName}_{this.SbomSpecificationVersion}".ToLowerInvariant(); + private void CleanupManifestDirectory() + { // Clean up the manifest directory if (Directory.Exists(DefaultManifestDirectory)) { @@ -53,9 +54,29 @@ public void Startup() { Directory.Delete(TemporaryDirectory, true); } + } + + [TestInitialize] + public void Startup() + { + // Setup the build engine + this.BuildEngine = new Mock(); + this.Errors = new List(); + this.BuildEngine.Setup(x => x.LogErrorEvent(It.IsAny())).Callback(e => Errors.Add(e)); + + this.CleanupManifestDirectory(); this.ManifestPath = Path.Combine(DefaultManifestDirectory, this.SbomSpecificationDirectoryName, "manifest.spdx.json"); this.GeneratedSbomValidator = new(this.SbomSpecification); +#if NET472 + Assert.IsTrue(Directory.Exists(SbomToolPath)); +#endif + } + + [TestCleanup] + public void Cleanup() + { + this.CleanupManifestDirectory(); } [TestMethod] @@ -70,7 +91,10 @@ public void Sbom_Is_Successfully_Generated() PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, BuildEngine = this.BuildEngine.Object, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -100,7 +124,10 @@ public void Sbom_Is_Successfully_Generated_Valid_URI(string namespaceBaseUri) PackageVersion = PackageVersion, NamespaceBaseUri = namespaceBaseUri, BuildEngine = this.BuildEngine.Object, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -126,7 +153,10 @@ public void Sbom_Is_Successfully_Generated_Valid_RequiredParams(string packageSu PackageVersion = packageVersion, NamespaceBaseUri = NamespaceBaseUri, BuildEngine = this.BuildEngine.Object, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -176,7 +206,10 @@ public void Sbom_Is_Successfully_Generated_In_Specified_Location() PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, BuildEngine = this.BuildEngine.Object, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -201,7 +234,10 @@ public void Sbom_Generation_Fails_With_NotFound_BuildDropPath() PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, BuildEngine = this.BuildEngine.Object, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -224,7 +260,10 @@ public void Sbom_Generation_Fails_With_NotFound_BuildComponentPath() PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, BuildEngine = this.BuildEngine.Object, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -248,7 +287,10 @@ public void Sbom_Generation_Fails_With_NotFound_ExternalDocumentListFile() PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, BuildEngine = this.BuildEngine.Object, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -272,7 +314,10 @@ public void Sbom_Generation_Fails_With_NotFound_ManifestDirPath() PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, BuildEngine = this.BuildEngine.Object, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -299,7 +344,10 @@ public void Sbom_Is_Successfully_Generated_With_Component_Path() PackageVersion = PackageVersion, NamespaceBaseUri = NamespaceBaseUri, BuildEngine = this.BuildEngine.Object, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act @@ -327,14 +375,81 @@ public void Sbom_Is_Successfully_Generated_With_Unique_Namespace_Part_Defined(st NamespaceBaseUri = NamespaceBaseUri, NamespaceUriUniquePart = uniqueNamespacePart, BuildEngine = this.BuildEngine.Object, - ManifestInfo = this.SbomSpecification.ToString(), + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif }; // Act var result = task.Execute(); // Assert - Assert.IsTrue(result); + Assert.IsTrue(result, $"{result} is not set to true."); this.GeneratedSbomValidator.AssertSbomIsValid(this.ManifestPath, CurrentDirectory, PackageName, PackageVersion, PackageSupplier, NamespaceBaseUri, expectedNamespaceUriUniquePart: uniqueNamespacePart); } + +#if NET472 + [TestMethod] + public void Sbom_Generation_Fails_With_Tool_Path_Not_Found() + { + // Arrange + var task = new GenerateSbom + { + BuildDropPath = CurrentDirectory, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification, + SbomToolPath = "C:\\Not-Found\\Path\\", + }; + + // Act + var result = task.Execute(); + + // Assert + Assert.IsFalse(result); + Assert.IsFalse(Directory.Exists(DefaultManifestDirectory)); + } +#endif + + // This test is failing due to this issue: https://github.com/microsoft/sbom-tool/issues/615 + [TestMethod] + public void Sbom_Fails_To_Generate_Due_To_File_In_Use() + { + var manifestDirPath = Path.Combine(TemporaryDirectory, "sub-directory"); + this.ManifestPath = Path.Combine(manifestDirPath, "_manifest", this.SbomSpecificationDirectoryName, "manifest.spdx.json"); + Directory.CreateDirectory(manifestDirPath); + // Arrange + var task = new GenerateSbom + { + BuildDropPath = CurrentDirectory, + ManifestDirPath = manifestDirPath, + PackageSupplier = PackageSupplier, + PackageName = PackageName, + PackageVersion = PackageVersion, + NamespaceBaseUri = NamespaceBaseUri, + BuildEngine = this.BuildEngine.Object, + ManifestInfo = this.SbomSpecification, +#if NET472 + SbomToolPath = SbomToolPath, +#endif + }; + + // Write JSON content to the manifest file, and create the directory if it doesn't exist + var jsonContent = "{}"; + Directory.CreateDirectory(Path.GetDirectoryName(ManifestPath)); + File.WriteAllText(ManifestPath, jsonContent); + // Open a handle to the manifest file to simulate it being in use + using (var fileStream = File.Open(this.ManifestPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) + { + // Act + var result = task.Execute(); + + // Assert + Assert.IsFalse(result); + } + } } diff --git a/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2InputTests.cs b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2InputTests.cs index 92431f09..1efe7e8a 100644 --- a/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2InputTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2InputTests.cs @@ -3,15 +3,13 @@ namespace Microsoft.Sbom.Targets.Tests; -using Microsoft.Sbom.Api.Utils; -using Microsoft.Sbom.Contracts; using Microsoft.VisualStudio.TestTools.UnitTesting; /// /// Class to test the generation of SBOM using SPDX 2.2 specification. /// [TestClass] -public class GenerateSbomTaskSPDX_2_2InputTests : AbstractGenerateSBomTaskInputTests +public class GenerateSbomTaskSPDX_2_2InputTests : AbstractGenerateSbomTaskInputTests { - internal override SbomSpecification SbomSpecification => Constants.SPDX22Specification; + internal override string SbomSpecification => "SPDX:2.2"; } diff --git a/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2Tests.cs b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2Tests.cs index a3acf920..63e66701 100644 --- a/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2Tests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/GenerateSbomTaskSPDX_2_2Tests.cs @@ -8,8 +8,6 @@ namespace Microsoft.Sbom.Targets.Tests; using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.Sbom.Api.Utils; -using Microsoft.Sbom.Contracts; using Microsoft.VisualStudio.TestTools.UnitTesting; /// @@ -18,5 +16,7 @@ namespace Microsoft.Sbom.Targets.Tests; [TestClass] public class GenerateSbomTaskSPDX_2_2Tests : AbstractGenerateSbomTaskTests { - internal override SbomSpecification SbomSpecification => Constants.SPDX22Specification; + internal override string SbomSpecificationName => "SPDX"; + + internal override string SbomSpecificationVersion => "2.2"; } diff --git a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj index 4278eba1..73da9053 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj +++ b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj @@ -1,11 +1,13 @@ - net8.0 + net8.0;net472 + net8.0 false True Microsoft.Sbom.Targets.Tests - $(StrongNameSigningKeyFilePath) + net8.0 + $(MSBuildThisFileDirectory)..\..\src\Microsoft.Sbom.Tool\ @@ -13,12 +15,28 @@ + + + + + + + + + <_SbomToolFiles Include="$(SBOMCLIToolProjectDir)bin\$(Configuration)\$(SbomCLIToolTargetFramework)\publish\**\*.*"> + false + + + + + - - - + + + + diff --git a/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs b/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs index 5e775811..1ee8c094 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs +++ b/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs @@ -10,8 +10,6 @@ namespace Microsoft.Sbom.Targets.Tests.Utility; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; -using Microsoft.Sbom.Api.Utils; -using Microsoft.Sbom.Contracts; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -21,9 +19,10 @@ namespace Microsoft.Sbom.Targets.Tests.Utility; #pragma warning disable CA5350 // Suppress Do Not Use Weak Cryptographic Algorithms as we use SHA1 intentionally internal class GeneratedSbomValidator { - private readonly SbomSpecification sbomSpecification; + private const string SPDX22Specification = "SPDX:2.2"; + private readonly string sbomSpecification; - public GeneratedSbomValidator(SbomSpecification sbomSpecification) + public GeneratedSbomValidator(string sbomSpecification) { this.sbomSpecification = sbomSpecification; } @@ -36,14 +35,14 @@ internal void AssertSbomIsValid(string manifestPath, string buildDropPath, strin var manifestContent = File.ReadAllText(manifestPath); var manifest = JsonConvert.DeserializeObject(manifestContent); - if (this.sbomSpecification.Equals(Constants.SPDX22Specification)) + if (this.sbomSpecification.Equals(SPDX22Specification)) { // Check the manifest has expected file data var filesValue = manifest["files"]; Assert.IsNotNull(filesValue); var expectedFilesHashes = this.GetBuildDropFileHashes(buildDropPath); - Assert.AreEqual(expectedFilesHashes.Count, filesValue.Count); + Assert.AreEqual(expectedFilesHashes.Count, filesValue.Count, $"Manifest {manifestPath} has {filesValue.Count} files instead of {expectedFilesHashes.Count}"); foreach (var file in filesValue) { var filePath = Path.GetFullPath(Path.Combine(buildDropPath, (string)file["fileName"])); @@ -92,7 +91,7 @@ internal void AssertSbomIsValid(string manifestPath, string buildDropPath, strin } else { - Assert.IsTrue(namespaceValue.Contains($"{expectedNamespaceUriBase.Trim()}/{expectedPackageName}/{expectedPackageVersion}", StringComparison.InvariantCultureIgnoreCase)); + Assert.IsTrue(namespaceValue.Contains($"{expectedNamespaceUriBase.Trim()}/{expectedPackageName}/{expectedPackageVersion}")); } } } @@ -128,7 +127,7 @@ private IDictionary> GetBuildDropFileHashes( private IList<(string, Func)> GetListOfHashAlgorithmCreators() { - if (this.sbomSpecification.Equals(Constants.SPDX22Specification)) + if (this.sbomSpecification.Equals(SPDX22Specification)) { return [("SHA1", SHA1.Create), ("SHA256", SHA256.Create)]; } From 31ed7e8b5879e5ba9e5a8ac8c199331174b6ad22 Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:55:58 -0700 Subject: [PATCH 20/36] Update NuGet Package Format and Surface Errors (#619) * update nuget package format and surface errors * simplify sbom output * update targets to use SbomPath output var for ToolTask * fix bad merge * append manifest folder name for manifestdirpath * add path.combine and property checks * append platform version * create ManifestDirPath if needed * temporarily comment out * remove manifestdirpath logic for now * use path.combine and full path --------- Co-authored-by: vpatakottu --- .../Microsoft.Sbom.Targets.targets | 45 +++++++++++-------- src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs | 29 +++++++++++- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets index 914941eb..c6700e7f 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -12,13 +12,24 @@ net472 net8.0 + $([System.IO.Path]::Combine($(MSBuildThisFileDirectory),..,tasks,$(GenerateSbom_TFM),sbom-tool)) + $([System.IO.Path]::Combine($(MSBuildThisFileDirectory),..,tasks,$(GenerateSbom_TFM),Microsoft.Sbom.Targets.dll)) + - $(MSBuildThisFileDirectory)\..\tasks\$(GenerateSbom_TFM)\sbom-tool + $(SbomToolBinaryOutputPath) _manifest + spdx_2.2 + + + + + + $(TargetsForTfmSpecificContentInPackage);CopySbomOutput + - + false @@ -39,6 +50,7 @@ + + - - - - true - _manifest - - - - true - _manifest - - - - true - _manifest - + + + + + $(TargetFramework)$(TargetPlatformVersion) + $(TargetFramework) + + + + + $([System.IO.Path]::Combine($(BuildOutputTargetFolder),$(TargetFrameworkWithPlatformVersion),$(ManifestFolderName),$(SbomSpecification))) + diff --git a/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs b/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs index 99f3692e..c86dcd38 100644 --- a/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs +++ b/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs @@ -3,7 +3,7 @@ namespace Microsoft.Sbom.Targets; -using System.Diagnostics.Tracing; +using System; using System.IO; using Microsoft.Build.Utilities; @@ -14,6 +14,31 @@ public partial class GenerateSbom : ToolTask { protected override string ToolName => "Microsoft.Sbom.Tool"; + /// + /// Executes the SBOM CLI Tool invocation. Need to add extra logic + /// to set SbomPath to the directory containing the SBOM. + /// + /// + public override bool Execute() + { + var taskResult = base.Execute(); + // Set the SbomPath output variable + if (taskResult) { + var manifestFolderName = "_manifest"; + if (!string.IsNullOrWhiteSpace(this.ManifestDirPath)) + { + var fullManifestDirPath = Path.GetFullPath(this.ManifestDirPath); + this.SbomPath = Path.Combine(fullManifestDirPath, manifestFolderName); + } else + { + var fullBuidDropPath = Path.GetFullPath(this.BuildDropPath); + this.SbomPath = Path.Combine(fullBuidDropPath, manifestFolderName); + } + } + + return taskResult; + } + /// /// Get full path to SBOM CLI tool. /// @@ -97,5 +122,7 @@ private void SetOutputImportance() { this.StandardOutputImportance = "Low"; } + + this.LogStandardErrorAsError = true; } } From d2c4dc6e9b3c936305c0cad1b69b6701ee3f0df7 Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:18:44 -0700 Subject: [PATCH 21/36] Add README for Microsoft.Sbom.Targets project (#651) * Adding readme * add code quotes --------- Co-authored-by: vpatakottu --- src/Microsoft.Sbom.Targets/README.md | 55 ++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/Microsoft.Sbom.Targets/README.md diff --git a/src/Microsoft.Sbom.Targets/README.md b/src/Microsoft.Sbom.Targets/README.md new file mode 100644 index 00000000..9b8d6c22 --- /dev/null +++ b/src/Microsoft.Sbom.Targets/README.md @@ -0,0 +1,55 @@ +# SBOM Generation for .NET Projects +## Microsoft.Sbom.Targets +This project implements a custom MSBuild task that generates an SBOM using the SBOM API and CLI tool. The MSBuild task binaries along with the associated targets are packaged as a NuGet package and can be consumed within a .NET project. Once installed, an SBOM will automatically be generated upon building the .NET project. + +## MSBuild Task Implementation +The custom MSBuild task is implemented across the following partial classes: +- `GenerateSbom.cs` +- `GenerateSbomTask.cs` +- `SbomCLIToolTask.cs` +- `SbomInputValidator.cs` + +Due to differences in [MSBuild versions](https://learn.microsoft.com/en-us/visualstudio/msbuild/tutorial-custom-task-code-generation?view=vs-2022#create-the-appsettingstronglytyped-project) between Visual Studio and the .Net Core CLI tool, the SBOM generation logic needed to be split into two parts: + +1) `GenerateSbomTask.cs` is invoked if the MSBuild version targets the "Core" (.NET Core) runtime bundled with the .NET Core CLI tool. This class utilizes the SBOM API to generate an SBOM. + +2) `SbomCLIToolTask.cs` is invoked if the MSBuild version targets the "Full" (.NET Framework) runtime bundled with Visual Studio. Because the SBOM API does not support .NET Framework, this class utilizes the SBOM CLI Tool to generate an SBOM. + +Finally, the `Microsoft.Sbom.Targets.targets` file creates a target that will execute the custom MSBuild task. This file will be automatically imported when consuming the NuGet package. + +## SBOM Generation Properties +The custom MSBuild task accepts most of the arguments available for the [SBOM CLI Tool](../../docs/sbom-tool-arguments.md). After the .targets file is imported into a .NET project, the following properties can be set: + +| Property | Default Value | Required | +|-----------------------------------------------------|-------------|---------| +| `` | `false` | No. To enable SBOM generation, set this to true. | +| `` | `$(OutDir)` | Yes | +| `` | `$(MSBuildProjectDirectory)` | No | +| `` | `$(Authors)`. If `$(Authors)` is null, it will set `$(AssemblyName)` | Yes | +| `` | `$(PackageId)`. If `$(PackageId)` is null, it will set `$(AssemblyName)` | Yes | +| `` | `$(Version)`. If `$(Version)` is null, it will set "1.0.0" | Yes | +| `` | `http://spdx.org/spdxdocs/$(SbomGenerationPackageName)` | Yes | +| `` | N/A | No | +| `` | N/A | No | +| `` | `false` | No | +| `` | `false` | No | +| `` | `Information` | No | +| `` | `SPDX:2.2` | No | +| `` | `true` | No | +| `` | N/A | No | + +## Local SBOM Generation Workflow +After building the Microsoft.Sbom.Targets project, it will generate a NuGet package containing the MSBuild task's binaries and associated .targets file in the `bin\$(Configuration)` folder. The following steps describe how to consume this NuGet package and generate an SBOM: + +1) Create a sample .NET project. +2) Open the project's NuGet package manager. +3) Add the path to the Microsoft.Sbom.Targets NuGet package as a package source. You can name it "Local". +4) Look for the Microsoft.Sbom.Targets package within the package manager and install it. +5) Add the following to your sample project's .csproj file: +``` + + true + +``` +6) Build the sample project. An SBOM should be generated under the `bin\$(Configuration)\$(TargetFramework)` folder. +7) Pack the sample project. The generated SBOM will be copied into the `$(BuildOutputTargetFolder)\$(TargetFramework)` folder. \ No newline at end of file From 4c9354c99470703808028e416e02a373d35e2e1c Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Thu, 8 Aug 2024 19:45:30 -0700 Subject: [PATCH 22/36] Workaround for generating a SBOM manifest at the root level of the Nuget Package (#656) * Add buildMultiTargeting folder to the Nuget package * Unzip and Zip again for including the SBOM into the Nuget package. * Append GUID to the temporary unzipped folder. --- .../Microsoft.Sbom.Targets.csproj | 1 + .../Microsoft.Sbom.Targets.targets | 44 +++++++++---------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj index be7eabbc..c7202f9b 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj @@ -60,6 +60,7 @@ by convention, the .NET SDK will look for build\.props and build\.targets for automatic inclusion in the build. --> + diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets index c6700e7f..2caffb5f 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -20,20 +20,12 @@ _manifest spdx_2.2 - - - - - $(TargetsForTfmSpecificContentInPackage);CopySbomOutput - - false - $(OutDir) $(MSBuildProjectDirectory) $(Authors) $(AssemblyName) @@ -47,12 +39,27 @@ information SPDX:2.2 true + $([System.Guid]::NewGuid()) - + + + + + + + $(PackageOutputPath)\$(PackageId).$(PackageVersion).nupkg + + + $(PackageOutputPath)\$(PackageId).$(PackageVersion).$(UnzipGuid).temp + + + + + - - - - - - $(TargetFramework)$(TargetPlatformVersion) - $(TargetFramework) - - - - - $([System.IO.Path]::Combine($(BuildOutputTargetFolder),$(TargetFrameworkWithPlatformVersion),$(ManifestFolderName),$(SbomSpecification))) - - + + + From 5667a0077bc4e706da008135dc885af60af2c6e0 Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Tue, 13 Aug 2024 20:10:46 -0700 Subject: [PATCH 23/36] Use Path.Combine for Unzip and Nupkg paths (#663) --- src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets index 2caffb5f..370a269c 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -49,10 +49,10 @@ - $(PackageOutputPath)\$(PackageId).$(PackageVersion).nupkg + $([System.IO.Path]::Combine($(PackageOutputPath), $(PackageId).$(PackageVersion).nupkg)) - $(PackageOutputPath)\$(PackageId).$(PackageVersion).$(UnzipGuid).temp + $([System.IO.Path]::Combine($(PackageOutputPath), $(PackageId).$(PackageVersion).$(UnzipGuid).temp)) From d0929da4fc781e54c2a792495a942c8d4423a681 Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:17:45 -0700 Subject: [PATCH 24/36] Add E2E tests for Microsoft.Sbom.Targets project (#658) * add base setup for tests * updates to test * cleanup * Add more tests * update package version * cleanup * mini fix for copying sample project * add unloading step * create separate project for E2E tests * cleanup * rearrange method * cleanup * check for platform * try with locator * disable analyzers for sample project --------- Co-authored-by: vpatakottu --- Directory.Packages.props | 4 +- Microsoft.Sbom.sln | 6 + .../Microsoft.Sbom.Targets.csproj | 6 +- .../Microsoft.Sbom.Targets.targets | 32 +- .../GenerateSbomE2ETests.cs | 344 ++++++++++++++++++ .../Microsoft.Sbom.Targets.E2E.Tests.csproj | 63 ++++ .../ProjectSample1/ProjectSample1.csproj | 21 ++ .../ProjectSample1/SampleLibrary.cs | 7 + .../Microsoft.Sbom.Targets.Tests.csproj | 2 +- .../Utility/GeneratedSbomValidator.cs | 2 +- 10 files changed, 463 insertions(+), 24 deletions(-) create mode 100644 test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs create mode 100644 test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj create mode 100644 test/Microsoft.Sbom.Targets.E2E.Tests/ProjectSamples/ProjectSample1/ProjectSample1.csproj create mode 100644 test/Microsoft.Sbom.Targets.E2E.Tests/ProjectSamples/ProjectSample1/SampleLibrary.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index d4f4fded..acb06ed8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,7 +14,9 @@ + + @@ -58,4 +60,4 @@ - \ No newline at end of file + diff --git a/Microsoft.Sbom.sln b/Microsoft.Sbom.sln index 2aa73d19..643283e9 100644 --- a/Microsoft.Sbom.sln +++ b/Microsoft.Sbom.sln @@ -55,6 +55,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Targets.Test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Tool.Tests", "test\Microsoft.Sbom.Tool.Tests\Microsoft.Sbom.Tool.Tests.csproj", "{FC5A9799-7C44-4BFA-BA22-55DCAF1A1B9F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Sbom.Targets.E2E.Tests", "test\Microsoft.Sbom.Targets.E2E.Tests\Microsoft.Sbom.Targets.E2E.Tests.csproj", "{3FDE7800-F61F-4C45-93AB-648A4C7979C7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -125,6 +127,10 @@ Global {FC5A9799-7C44-4BFA-BA22-55DCAF1A1B9F}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC5A9799-7C44-4BFA-BA22-55DCAF1A1B9F}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC5A9799-7C44-4BFA-BA22-55DCAF1A1B9F}.Release|Any CPU.Build.0 = Release|Any CPU + {3FDE7800-F61F-4C45-93AB-648A4C7979C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FDE7800-F61F-4C45-93AB-648A4C7979C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FDE7800-F61F-4C45-93AB-648A4C7979C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FDE7800-F61F-4C45-93AB-648A4C7979C7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj index c7202f9b..50aca5cb 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj @@ -2,7 +2,7 @@ Microsoft.Sbom.Targets - net8.0;net472 + net6.0;net8.0;net472 win-x64;osx-x64;linux-x64 true true @@ -68,8 +68,8 @@ - - + + diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets index 370a269c..99f43739 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -1,19 +1,10 @@ - - - - net472 net8.0 - $([System.IO.Path]::Combine($(MSBuildThisFileDirectory),..,tasks,$(GenerateSbom_TFM),sbom-tool)) - $([System.IO.Path]::Combine($(MSBuildThisFileDirectory),..,tasks,$(GenerateSbom_TFM),Microsoft.Sbom.Targets.dll)) + $([System.IO.Path]::Combine($(MSBuildThisFileDirectory),..,tasks,$(GenerateSbom_TFM),sbom-tool)) + $([System.IO.Path]::Combine($(MSBuildThisFileDirectory),..,tasks,$(GenerateSbom_TFM),Microsoft.Sbom.Targets.dll)) $(SbomToolBinaryOutputPath) @@ -22,7 +13,7 @@ - + false @@ -40,24 +31,29 @@ SPDX:2.2 true $([System.Guid]::NewGuid()) + $([System.String]::Copy('$(UnzipGuid)').Substring(0, 8)) - - + $([System.IO.Path]::GetFullPath('$(PackageOutputPath)')) + - $([System.IO.Path]::Combine($(PackageOutputPath), $(PackageId).$(PackageVersion).nupkg)) + $([System.IO.Path]::Combine($(PackageOutputFullPath), $(PackageId).$(PackageVersion).nupkg)) - $([System.IO.Path]::Combine($(PackageOutputPath), $(PackageId).$(PackageVersion).$(UnzipGuid).temp)) + $([System.IO.Path]::Combine($(PackageOutputFullPath), $(PackageId).$(PackageVersion).$(ShortUnzipGuidFolder).temp)) - + diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs b/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs new file mode 100644 index 00000000..e717718e --- /dev/null +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs @@ -0,0 +1,344 @@ +// 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.E2E.Tests; + +using System; +using System.IO; +using System.IO.Compression; +using System.Runtime.InteropServices; +using Castle.Core.Internal; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Locator; +using Microsoft.Build.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[TestClass] +public class GenerateSbomE2ETests +{ + /* + * The following tests validate the end-to-end workflow for importing the Microsoft.Sbom.Targets.targets + * into a .NET project, building it, packing it, and validating the generated SBOM contents. + */ + private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + private static string projectDirectory = Path.Combine(Directory.GetCurrentDirectory(), "ProjectSamples", "ProjectSample1"); + private static string sbomToolPath = Path.Combine(Directory.GetCurrentDirectory(), "sbom-tool"); + private static string generateSbomTaskPath = Path.Combine(Directory.GetCurrentDirectory(), "Microsoft.Sbom.Targets.dll"); + + private static string sbomSpecificationName = "SPDX"; + private static string sbomSpecificationVersion = "2.2"; + private static string sbomSpecificationDirectoryName = $"{sbomSpecificationName}_{sbomSpecificationVersion}".ToLowerInvariant(); + private string manifestPath; + private string expectedPackageName; + private string expectedVersion; + private string expectedSupplier; + private string assemblyName; + private string expectedNamespace; + private string configuration; + + [TestInitialize] + public void SetupLocator() + { + if (MSBuildLocator.CanRegister) + { + MSBuildLocator.RegisterDefaults(); + } + } + + [TestCleanup] + public void CleanOutputFolders() + { + var binDir = Path.Combine(projectDirectory, "bin"); + var objDir = Path.Combine(projectDirectory, "obj"); + + try + { + if (Directory.Exists(binDir)) + { + Directory.Delete(binDir, true); + } + + if (Directory.Exists(objDir)) + { + Directory.Delete(objDir, true); + } + + ProjectCollection.GlobalProjectCollection.UnloadAllProjects(); + } + catch (Exception ex) + { + Assert.Fail($"Failed to cleanup output directories. {ex}"); + } + } + + private Project SetupSampleProject() + { + // Create a Project object for ProjectSample1 + var projectFile = Path.Combine(projectDirectory, "ProjectSample1.csproj"); + var sampleProject = new Project(projectFile); + + // Get all the expected default properties + SetDefaultProperties(sampleProject); + + // Set the TargetFrameworks property to empty. By default, it sets this property to net6.0 and net8.0, which fails for net8.0 builds. + sampleProject.SetProperty("TargetFrameworks", string.Empty); + + // Set the paths to the sbom-tool CLI tool and Microsoft.Sbom.Targets.dll + sampleProject.SetProperty("SbomToolBinaryOutputPath", sbomToolPath); + sampleProject.SetProperty("GenerateSbomTaskAssemblyFilePath", generateSbomTaskPath); + + return sampleProject; + } + + private void SetDefaultProperties(Project sampleProject) + { + expectedPackageName = sampleProject.GetPropertyValue("PackageId"); + expectedVersion = sampleProject.GetPropertyValue("Version"); + assemblyName = sampleProject.GetPropertyValue("AssemblyName"); + configuration = sampleProject.GetPropertyValue("Configuration"); + + if (expectedPackageName.IsNullOrEmpty()) + { + expectedPackageName = assemblyName; + } + + if (expectedVersion.IsNullOrEmpty()) + { + expectedVersion = "1.0.0"; + } + } + + private void RestoreBuildPack(Project sampleProject) + { + var logger = new ConsoleLogger(); + + // Restore the project to create project.assets.json file + var restore = sampleProject.Build("Restore", new[] { logger }); + Assert.IsTrue(restore, "Failed to restore the project"); + + // Next, build the project + var build = sampleProject.Build(logger); + Assert.IsTrue(build, "Failed to build the project"); + + // Finally, pack the project + var pack = sampleProject.Build("Pack", new[] { logger }); + Assert.IsTrue(pack, "Failed to pack the project"); + } + + private void ExtractPackage() + { + // Unzip the contents of the NuGet package + var nupkgPath = Path.Combine(projectDirectory, "bin", configuration); + var nupkgFile = Path.Combine(nupkgPath, $"{expectedPackageName}.{expectedVersion}.nupkg"); + var zipFile = Path.Combine(nupkgPath, $"{expectedPackageName}.{expectedVersion}.zip"); + var extractPath = Path.Combine(projectDirectory, "bin", configuration, $"{Guid.NewGuid()}.temp"); + + // Rename the .nupkg file to .zip + File.Copy(nupkgFile, zipFile, true); + + // Extract the .zip file + ZipFile.ExtractToDirectory(zipFile, extractPath); + + manifestPath = Path.Combine(extractPath, "_manifest", sbomSpecificationDirectoryName, "manifest.spdx.json"); + } + + [TestMethod] + public void SbomGenerationSucceedsForDefaultProperties() + { + if (!IsWindows) + { + Assert.Inconclusive("This test is not (yet) supported on non-Windows platforms."); + return; + } + + // Create and setup a Project object for ProjectSample1 + var sampleProject = SetupSampleProject(); + + // Restore, build, and pack the project + RestoreBuildPack(sampleProject); + + // Extract the NuGet package + ExtractPackage(); + + // Validate the SBOM exists in the package. + Assert.IsTrue(File.Exists(manifestPath)); + } + + [TestMethod] + public void SbomGenerationSucceedsForValidNamespaceBaseUriUniquePart() + { + if (!IsWindows) + { + Assert.Inconclusive("This test is not (yet) supported on non-Windows platforms."); + return; + } + + // Create and setup a Project object for ProjectSample1 + var sampleProject = SetupSampleProject(); + + // Manually set the NamespaceUriUniquePart + var namespaceUriUniquePart = Guid.NewGuid().ToString(); + sampleProject.SetProperty("SbomGenerationNamespaceUriUniquePart", namespaceUriUniquePart); + + // Restore, build, and pack the project + RestoreBuildPack(sampleProject); + + // Extract the NuGet package + ExtractPackage(); + + // Validate the SBOM exists in the package. + Assert.IsTrue(File.Exists(manifestPath)); + } + + [TestMethod] + public void SbomGenerationSucceedsForValidRequiredParams() + { + if (!IsWindows) + { + Assert.Inconclusive("This test is not (yet) supported on non-Windows platforms."); + return; + } + + // Create and setup a Project object for ProjectSample1 + var sampleProject = SetupSampleProject(); + + // Set require params + expectedPackageName = "SampleName"; + expectedVersion = "3.2.5"; + expectedSupplier = "SampleSupplier"; + expectedNamespace = "https://example.com"; + + sampleProject.SetProperty("PackageId", expectedPackageName); + sampleProject.SetProperty("Version", expectedVersion); + sampleProject.SetProperty("SbomGenerationPackageName", expectedPackageName); + sampleProject.SetProperty("SbomGenerationPackageVersion", expectedVersion); + sampleProject.SetProperty("SbomGenerationPackageSupplier", expectedSupplier); + sampleProject.SetProperty("SbomGenerationNamespaceBaseUri", expectedNamespace); + + // Restore, build, and pack the project + RestoreBuildPack(sampleProject); + + // Extract the NuGet package + ExtractPackage(); + + // Validate the SBOM exists in the package. + Assert.IsTrue(File.Exists(manifestPath)); + } + + [TestMethod] + public void SbomGenerationFailsForInvalidNamespaceUri() + { + if (!IsWindows) + { + Assert.Inconclusive("This test is not (yet) supported on non-Windows platforms."); + return; + } + + // Create and setup a Project object for ProjectSample1 + var sampleProject = SetupSampleProject(); + + // Set invalid namespace + expectedNamespace = "incorrect_uri"; + sampleProject.SetProperty("SbomGenerationNamespaceBaseUri", expectedNamespace); + + // Restore, build, and pack the project + var logger = new ConsoleLogger(); + + // Restore the project to create project.assets.json file + var restore = sampleProject.Build("Restore", new[] { logger }); + Assert.IsTrue(restore, "Failed to restore the project"); + + // Next, build the project + var build = sampleProject.Build(logger); + Assert.IsTrue(build, "Failed to build the project"); + + // Ensure the packing step fails + var pack = sampleProject.Build("Pack", new[] { logger }); + Assert.IsFalse(pack, "Packing succeeded when it should have failed"); + } + + [TestMethod] + public void SbomGenerationFailsForInvalidSupplierName() + { + if (!IsWindows) + { + Assert.Inconclusive("This test is not (yet) supported on non-Windows platforms."); + return; + } + + // Create and setup a Project object for ProjectSample1 + var sampleProject = SetupSampleProject(); + + // Set invalid supplier name + sampleProject.SetProperty("Authors", string.Empty); + sampleProject.SetProperty("AssemblyName", string.Empty); + sampleProject.SetProperty("SbomGenerationPackageSupplier", string.Empty); + + // Restore, build, and pack the project + var logger = new ConsoleLogger(); + + // Restore the project to create project.assets.json file + var restore = sampleProject.Build("Restore", new[] { logger }); + Assert.IsTrue(restore, "Failed to restore the project"); + + // Next, build the project + var build = sampleProject.Build(logger); + Assert.IsTrue(build, "Failed to build the project"); + + // Ensure the packing step fails + var pack = sampleProject.Build("Pack", new[] { logger }); + Assert.IsFalse(pack, "Packing succeeded when it should have failed"); + } + + [TestMethod] + public void SbomGenerationSkipsForUnsetGenerateSBOMFlag() + { + if (!IsWindows) + { + Assert.Inconclusive("This test is not (yet) supported on non-Windows platforms."); + return; + } + + // Create and setup a Project object for ProjectSample1 + var sampleProject = SetupSampleProject(); + + // Set the GenerateSBOM property to empty. + sampleProject.SetProperty("GenerateSBOM", "false"); + + // Restore, build, and pack the project + RestoreBuildPack(sampleProject); + + // Extract the NuGet package + ExtractPackage(); + + // Ensure the manifest file was not created + Assert.IsTrue(!File.Exists(manifestPath)); + } + + [TestMethod] + public void SbomGenerationSucceedsForMultiTargetedProject() + { + if (!IsWindows) + { + Assert.Inconclusive("This test is not (yet) supported on non-Windows platforms."); + return; + } + + // Create and setup a Project object for ProjectSample1 + var sampleProject = SetupSampleProject(); + + // Set multi-target frameworks + sampleProject.SetProperty("TargetFramework", string.Empty); + sampleProject.SetProperty("TargetFrameworks", "net472;net6.0"); + + // Restore, build, and pack the project + RestoreBuildPack(sampleProject); + + // Extract the NuGet package + ExtractPackage(); + + // Validate the SBOM exists in the package. + Assert.IsTrue(File.Exists(manifestPath)); + } +} diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj b/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj new file mode 100644 index 00000000..88e02cc9 --- /dev/null +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj @@ -0,0 +1,63 @@ + + + + net6.0;net472 + true + false + True + Microsoft.Sbom.Targets.E2E.Tests + net6.0 + $(MSBuildThisFileDirectory)..\..\src\Microsoft.Sbom.Tool\ + $(MSBuildThisFileDirectory)..\..\src\Microsoft.Sbom.Targets\Microsoft.Sbom.Targets.targets + + + + TRACE + + + + + + + + + + + + + + + + + + + + + + + + + + + <_SbomToolFiles Include="$(SBOMCLIToolProjectDir)bin\$(Configuration)\$(SbomCLIToolTargetFramework)\publish\**\*.*"> + false + + + + + + + + + + false + + + + + + + + + + diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/ProjectSamples/ProjectSample1/ProjectSample1.csproj b/test/Microsoft.Sbom.Targets.E2E.Tests/ProjectSamples/ProjectSample1/ProjectSample1.csproj new file mode 100644 index 00000000..ab349408 --- /dev/null +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/ProjectSamples/ProjectSample1/ProjectSample1.csproj @@ -0,0 +1,21 @@ + + + Library + true + net6.0 + ProjectSample + 1.2.4 + false + true + true + false + + + + + $(NoWarn);NU1507;NU5128 + + + + + diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/ProjectSamples/ProjectSample1/SampleLibrary.cs b/test/Microsoft.Sbom.Targets.E2E.Tests/ProjectSamples/ProjectSample1/SampleLibrary.cs new file mode 100644 index 00000000..05389171 --- /dev/null +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/ProjectSamples/ProjectSample1/SampleLibrary.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; + +public class SampleLibrary +{ +} diff --git a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj index 73da9053..4c43dd2d 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj +++ b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj @@ -39,4 +39,4 @@ - + \ No newline at end of file diff --git a/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs b/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs index 1ee8c094..895c0a51 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs +++ b/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs @@ -66,7 +66,7 @@ internal void AssertSbomIsValid(string manifestPath, string buildDropPath, strin Assert.IsNotNull(packagesValue); if (string.IsNullOrEmpty(buildComponentPath)) { - Assert.IsTrue(packagesValue.Count == 1); + Assert.IsTrue(packagesValue.Count == 1, $"Expected 1 package but actual value was {packagesValue.Count}"); } else { From 6e555355316e21b789bc58a7c215b31d997354fa Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Fri, 16 Aug 2024 11:35:55 -0700 Subject: [PATCH 25/36] Remove GenerateSBOMTest project (#673) * Remove GenerateSBOMTest project * Remove N/A comment --- src/GenerateSBOMTest/GenerateSBOMTest.sln | 25 ------------------- .../GenerateSBOMTest/GenerateSBOMTest.csproj | 19 -------------- .../GenerateSBOMTest/Program.cs | 3 --- .../GenerateSbomTask.cs | 1 - 4 files changed, 48 deletions(-) delete mode 100644 src/GenerateSBOMTest/GenerateSBOMTest.sln delete mode 100644 src/GenerateSBOMTest/GenerateSBOMTest/GenerateSBOMTest.csproj delete mode 100644 src/GenerateSBOMTest/GenerateSBOMTest/Program.cs diff --git a/src/GenerateSBOMTest/GenerateSBOMTest.sln b/src/GenerateSBOMTest/GenerateSBOMTest.sln deleted file mode 100644 index 0c4039a6..00000000 --- a/src/GenerateSBOMTest/GenerateSBOMTest.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.11.34929.205 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenerateSBOMTest", "GenerateSBOMTest\GenerateSBOMTest.csproj", "{42BFEE5C-290D-4E99-9247-6AE26FA57227}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {42BFEE5C-290D-4E99-9247-6AE26FA57227}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {42BFEE5C-290D-4E99-9247-6AE26FA57227}.Debug|Any CPU.Build.0 = Debug|Any CPU - {42BFEE5C-290D-4E99-9247-6AE26FA57227}.Release|Any CPU.ActiveCfg = Release|Any CPU - {42BFEE5C-290D-4E99-9247-6AE26FA57227}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {424A9B69-FF02-48BE-8D4C-B7D85BCFF76C} - EndGlobalSection -EndGlobal diff --git a/src/GenerateSBOMTest/GenerateSBOMTest/GenerateSBOMTest.csproj b/src/GenerateSBOMTest/GenerateSBOMTest/GenerateSBOMTest.csproj deleted file mode 100644 index 038641f4..00000000 --- a/src/GenerateSBOMTest/GenerateSBOMTest/GenerateSBOMTest.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - - - diff --git a/src/GenerateSBOMTest/GenerateSBOMTest/Program.cs b/src/GenerateSBOMTest/GenerateSBOMTest/Program.cs deleted file mode 100644 index 2bb61757..00000000 --- a/src/GenerateSBOMTest/GenerateSBOMTest/Program.cs +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -Console.WriteLine("Hello, World!"); diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index 82b6ce26..1a6c00e7 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -105,7 +105,6 @@ public override bool Execute() } catch (Exception e) { - // TODO: Add automated tests for the different exceptions. Log.LogError($"SBOM generation failed: {e.Message}"); return false; } From 756c822b6f746590d85d32505f0ee90ef3cdbfa3 Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Fri, 16 Aug 2024 11:37:40 -0700 Subject: [PATCH 26/36] Add ContinueOnError=ErrorAndContinue to the ZipDirectory, GenerateSBOM and Unzip (#672) --- .../Microsoft.Sbom.Targets.targets | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets index 99f43739..b1f6a8cc 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -47,7 +47,7 @@ $([System.IO.Path]::Combine($(PackageOutputFullPath), $(PackageId).$(PackageVersion).$(ShortUnzipGuidFolder).temp)) - + - - + + From c74ef7e4ecad7f1a40ed555ab7cf276a48fb000a Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Fri, 16 Aug 2024 13:38:23 -0700 Subject: [PATCH 27/36] User/gustavoca/update with main (#675) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump Component Detection version (#624) * Bump Component Detection version * Bump NuGet Config and Framework versions * Raise dependabot PR limit (#629) * build(deps): bump stefanzweifel/git-auto-commit-action (#552) Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/8756aa072ef5b4a080af5dc8fef36c5d586e521d...8621497c8c39c72f3e2a999a26b4ca1b5058a842) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dave Tryon <45672944+DaveTryon@users.noreply.github.com> * build(deps): bump Microsoft.NET.Test.Sdk from 17.7.2 to 17.10.0 (#630) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.7.2 to 17.10.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.7.2...v17.10.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump MSTest.TestAdapter from 3.1.1 to 3.5.0 (#644) Bumps [MSTest.TestAdapter](https://github.com/microsoft/testfx) from 3.1.1 to 3.5.0. - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.1.1...v3.5.0) --- updated-dependencies: - dependency-name: MSTest.TestAdapter dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump Spectre.Console.Cli from 0.48.0 to 0.49.1 (#637) Bumps [Spectre.Console.Cli](https://github.com/spectreconsole/spectre.console) from 0.48.0 to 0.49.1. - [Release notes](https://github.com/spectreconsole/spectre.console/releases) - [Commits](https://github.com/spectreconsole/spectre.console/compare/0.48.0...0.49.1) --- updated-dependencies: - dependency-name: Spectre.Console.Cli dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump github/codeql-action from 3.25.12 to 3.25.15 (#625) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.12 to 3.25.15. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/4fa2a7953630fd2f3fb380f21be14ede0169dd4f...afb54ba388a7dca6ecae48f608c4ff05ff4cc77a) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump MSTest.TestFramework from 3.1.1 to 3.5.0 (#642) Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.1.1 to 3.5.0. - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.1.1...v3.5.0) --- updated-dependencies: - dependency-name: MSTest.TestFramework dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump Microsoft.VisualStudio.Threading.Analyzers (#638) Bumps [Microsoft.VisualStudio.Threading.Analyzers](https://github.com/microsoft/vs-threading) from 17.7.30 to 17.10.48. - [Release notes](https://github.com/microsoft/vs-threading/releases) - [Commits](https://github.com/microsoft/vs-threading/compare/v17.7.30...v17.10.48) --- updated-dependencies: - dependency-name: Microsoft.VisualStudio.Threading.Analyzers dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump github/codeql-action from 3.25.15 to 3.26.0 (#654) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.15 to 3.26.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/afb54ba388a7dca6ecae48f608c4ff05ff4cc77a...eb055d739abdc2e8de2e5f4ba1a8b246daa779aa) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump MSTest.TestAdapter from 3.5.0 to 3.5.1 (#653) Bumps [MSTest.TestAdapter](https://github.com/microsoft/testfx) from 3.5.0 to 3.5.1. - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.5.0...v3.5.1) --- updated-dependencies: - dependency-name: MSTest.TestAdapter dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sarah Oslund * build(deps): bump MSTest.TestFramework from 3.5.0 to 3.5.1 (#652) Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.5.0 to 3.5.1. - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.5.0...v3.5.1) --- updated-dependencies: - dependency-name: MSTest.TestFramework dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sarah Oslund * build(deps): bump Moq from 4.17.2 to 4.20.70 (#640) Bumps [Moq](https://github.com/moq/moq) from 4.17.2 to 4.20.70. - [Release notes](https://github.com/moq/moq/releases) - [Changelog](https://github.com/devlooped/moq/blob/main/CHANGELOG.md) - [Commits](https://github.com/moq/moq/compare/v4.17.2...v4.20.70) --- updated-dependencies: - dependency-name: Moq dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump coverlet.collector from 6.0.0 to 6.0.2 (#641) Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 6.0.0 to 6.0.2. - [Release notes](https://github.com/coverlet-coverage/coverlet/releases) - [Commits](https://github.com/coverlet-coverage/coverlet/compare/v6.0.0...v6.0.2) --- updated-dependencies: - dependency-name: coverlet.collector dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump StyleCop.Analyzers (#636) Bumps [StyleCop.Analyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) from 1.2.0-beta.507 to 1.2.0-beta.556. - [Release notes](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/releases) - [Changelog](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/KnownChanges.md) - [Commits](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/compare/1.2.0-beta.507...1.2.0-beta.556) --- updated-dependencies: - dependency-name: StyleCop.Analyzers dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump Microsoft.SourceLink.GitHub from 1.1.1 to 8.0.0 (#645) Bumps [Microsoft.SourceLink.GitHub](https://github.com/dotnet/sourcelink) from 1.1.1 to 8.0.0. - [Release notes](https://github.com/dotnet/sourcelink/releases) - [Commits](https://github.com/dotnet/sourcelink/compare/1.1.1...8.0.0) --- updated-dependencies: - dependency-name: Microsoft.SourceLink.GitHub dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump MinVer from 4.3.0 to 5.0.0 (#634) Bumps [MinVer](https://github.com/adamralph/minver) from 4.3.0 to 5.0.0. - [Changelog](https://github.com/adamralph/minver/blob/main/CHANGELOG.md) - [Commits](https://github.com/adamralph/minver/compare/4.3.0...5.0.0) --- updated-dependencies: - dependency-name: MinVer dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump Microsoft.Extensions.Http, Microsoft.Extensions.Logging.Abstractions and Microsoft.Extensions.DependencyInjection (#649) Bumps [Microsoft.Extensions.Http](https://github.com/dotnet/runtime), [Microsoft.Extensions.Logging.Abstractions](https://github.com/dotnet/runtime) and [Microsoft.Extensions.DependencyInjection](https://github.com/dotnet/runtime). These dependencies needed to be updated together. Updates `Microsoft.Extensions.Http` from 7.0.0 to 8.0.0 - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v7.0.0...v8.0.0) Updates `Microsoft.Extensions.Logging.Abstractions` from 7.0.1 to 8.0.0 - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v7.0.1...v8.0.0) Updates `Microsoft.Extensions.DependencyInjection` from 7.0.0 to 8.0.0 - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v7.0.0...v8.0.0) --- updated-dependencies: - dependency-name: Microsoft.Extensions.Http dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Logging.Abstractions dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.DependencyInjection dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump Microsoft.Extensions.Logging.Abstractions and Microsoft.Extensions.DependencyInjection.Abstractions (#650) Bumps [Microsoft.Extensions.Logging.Abstractions](https://github.com/dotnet/runtime) and [Microsoft.Extensions.DependencyInjection.Abstractions](https://github.com/dotnet/runtime). These dependencies needed to be updated together. Updates `Microsoft.Extensions.Logging.Abstractions` from 7.0.1 to 8.0.1 - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v7.0.1...v8.0.1) Updates `Microsoft.Extensions.DependencyInjection.Abstractions` from 8.0.0 to 8.0.1 - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.0...v8.0.1) --- updated-dependencies: - dependency-name: Microsoft.Extensions.Logging.Abstractions dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.DependencyInjection.Abstractions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump Scrutor from 4.2.0 to 4.2.2 (#646) Bumps [Scrutor](https://github.com/khellang/Scrutor) from 4.2.0 to 4.2.2. - [Release notes](https://github.com/khellang/Scrutor/releases) - [Commits](https://github.com/khellang/Scrutor/compare/v4.2.0...v4.2.2) --- updated-dependencies: - dependency-name: Scrutor dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix tests * Bump Microsoft.Extensions.Hosting --------- Signed-off-by: dependabot[bot] Co-authored-by: José Renan Co-authored-by: Dave Tryon <45672944+DaveTryon@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sarah Oslund --- .github/dependabot.yml | 1 + .github/workflows/codeql-analysis.yml | 6 +- .github/workflows/gen-docs.yml | 2 +- Directory.Packages.props | 120 +++++++++--------- .../Microsoft.Sbom.Targets.csproj | 1 + .../GenerateSbomE2ETests.cs | 4 +- 6 files changed, 68 insertions(+), 66 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5c22897f..a0561642 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,7 @@ updates: directory: "/" schedule: interval: "daily" + open-pull-requests-limit: 30 # Default value of 5 is too low as we catch up - package-ecosystem: "github-actions" directory: "/" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d0a79ac8..eba7f119 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,12 +26,12 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Initialize CodeQL - uses: github/codeql-action/init@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # v3.25.12 + uses: github/codeql-action/init@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 with: languages: csharp - name: Autobuild - uses: github/codeql-action/autobuild@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # v3.25.12 + uses: github/codeql-action/autobuild@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # v3.25.12 + uses: github/codeql-action/analyze@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # v3.26.0 diff --git a/.github/workflows/gen-docs.yml b/.github/workflows/gen-docs.yml index 0913a43a..c70ac5ce 100644 --- a/.github/workflows/gen-docs.yml +++ b/.github/workflows/gen-docs.yml @@ -38,7 +38,7 @@ jobs: EOF - name: Commit - uses: stefanzweifel/git-auto-commit-action@8756aa072ef5b4a080af5dc8fef36c5d586e521d # v5.0.0 + uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1 with: commit_message: 'Auto update docs/*.md' file_pattern: 'docs/*.md' diff --git a/Directory.Packages.props b/Directory.Packages.props index acb06ed8..3fb04219 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,63 +1,63 @@ - - - - Compile - - - - 4.2.2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Compile + + + + 4.8.9 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj index 50aca5cb..bcdcdf15 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.csproj @@ -68,6 +68,7 @@ + diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs b/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs index e717718e..63352b10 100644 --- a/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs @@ -98,12 +98,12 @@ private void SetDefaultProperties(Project sampleProject) assemblyName = sampleProject.GetPropertyValue("AssemblyName"); configuration = sampleProject.GetPropertyValue("Configuration"); - if (expectedPackageName.IsNullOrEmpty()) + if (string.IsNullOrEmpty(expectedPackageName)) { expectedPackageName = assemblyName; } - if (expectedVersion.IsNullOrEmpty()) + if (string.IsNullOrEmpty(expectedPackageName)) { expectedVersion = "1.0.0"; } From a315afebc6a957acd33e43b793b6356ee871a69c Mon Sep 17 00:00:00 2001 From: gustavoaca1997 Date: Fri, 16 Aug 2024 15:44:33 -0700 Subject: [PATCH 28/36] Update README --- src/Microsoft.Sbom.Targets/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Sbom.Targets/README.md b/src/Microsoft.Sbom.Targets/README.md index 9b8d6c22..985c8b5c 100644 --- a/src/Microsoft.Sbom.Targets/README.md +++ b/src/Microsoft.Sbom.Targets/README.md @@ -23,7 +23,6 @@ The custom MSBuild task accepts most of the arguments available for the [SBOM CL | Property | Default Value | Required | |-----------------------------------------------------|-------------|---------| | `` | `false` | No. To enable SBOM generation, set this to true. | -| `` | `$(OutDir)` | Yes | | `` | `$(MSBuildProjectDirectory)` | No | | `` | `$(Authors)`. If `$(Authors)` is null, it will set `$(AssemblyName)` | Yes | | `` | `$(PackageId)`. If `$(PackageId)` is null, it will set `$(AssemblyName)` | Yes | @@ -36,7 +35,6 @@ The custom MSBuild task accepts most of the arguments available for the [SBOM CL | `` | `Information` | No | | `` | `SPDX:2.2` | No | | `` | `true` | No | -| `` | N/A | No | ## Local SBOM Generation Workflow After building the Microsoft.Sbom.Targets project, it will generate a NuGet package containing the MSBuild task's binaries and associated .targets file in the `bin\$(Configuration)` folder. The following steps describe how to consume this NuGet package and generate an SBOM: @@ -51,5 +49,5 @@ After building the Microsoft.Sbom.Targets project, it will generate a NuGet pack true ``` -6) Build the sample project. An SBOM should be generated under the `bin\$(Configuration)\$(TargetFramework)` folder. -7) Pack the sample project. The generated SBOM will be copied into the `$(BuildOutputTargetFolder)\$(TargetFramework)` folder. \ No newline at end of file +6) Build the sample project. +7) Pack the sample project. The SBOM will be generated under the `_manifest` folder at the root of the NuGet package. From d0fdcfcb90c76aa6c59dae75f6f605f83151598a Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:02:18 -0700 Subject: [PATCH 29/36] Address Feedback (#679) * Address feedback and remove SbomPath * remove whitespace * remove comment --------- Co-authored-by: vpatakottu --- src/Microsoft.Sbom.Api/SBOMGenerator.cs | 4 +-- .../Contracts/SBOMGenerationResult.cs | 8 +----- src/Microsoft.Sbom.Targets/GenerateSbom.cs | 10 +++----- .../GenerateSbomTask.cs | 3 +-- .../Microsoft.Sbom.Targets.targets | 2 -- src/Microsoft.Sbom.Targets/README.md | 2 +- src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs | 25 ------------------- .../GenerateSbomE2ETests.cs | 4 ++- .../Microsoft.Sbom.Targets.E2E.Tests.csproj | 2 -- .../Microsoft.Sbom.Targets.Tests.csproj | 1 - .../Utility/GeneratedSbomValidator.cs | 5 ++-- 11 files changed, 14 insertions(+), 52 deletions(-) diff --git a/src/Microsoft.Sbom.Api/SBOMGenerator.cs b/src/Microsoft.Sbom.Api/SBOMGenerator.cs index a226c81b..06c9d259 100644 --- a/src/Microsoft.Sbom.Api/SBOMGenerator.cs +++ b/src/Microsoft.Sbom.Api/SBOMGenerator.cs @@ -80,7 +80,7 @@ public async Task GenerateSbomAsync( var entityErrors = recorder.Errors.Select(error => error.ToEntityError()).ToList(); - return new SbomGenerationResult(isSuccess, entityErrors, isSuccess ? inputConfiguration.ManifestDirPath.ToString() : null); + return new SbomGenerationResult(isSuccess, entityErrors); } /// @@ -120,7 +120,7 @@ public async Task GenerateSbomAsync( // This is the generate workflow var result = await generationWorkflow.RunAsync(); - return new SbomGenerationResult(result, new List(), result ? inputConfiguration.ManifestDirPath.ToString() : null); + return new SbomGenerationResult(result, new List()); } /// diff --git a/src/Microsoft.Sbom.Contracts/Contracts/SBOMGenerationResult.cs b/src/Microsoft.Sbom.Contracts/Contracts/SBOMGenerationResult.cs index f6f2010e..b57bde7d 100644 --- a/src/Microsoft.Sbom.Contracts/Contracts/SBOMGenerationResult.cs +++ b/src/Microsoft.Sbom.Contracts/Contracts/SBOMGenerationResult.cs @@ -21,15 +21,9 @@ public class SbomGenerationResult /// public IList Errors { get; private set; } - /// - /// Gets the path where the SBOM was generated, if the generation was successful. - /// - public string? ManifestDirPath { get; private set; } - - public SbomGenerationResult(bool isSuccessful, IList errors, string manifestDirPath = null) + public SbomGenerationResult(bool isSuccessful, IList errors) { IsSuccessful = isSuccessful; Errors = errors ?? new List(); - this.ManifestDirPath = manifestDirPath; } } diff --git a/src/Microsoft.Sbom.Targets/GenerateSbom.cs b/src/Microsoft.Sbom.Targets/GenerateSbom.cs index 18e6fa62..0bdd6ff5 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbom.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbom.cs @@ -89,7 +89,9 @@ public partial class GenerateSbom public bool DeleteManifestDirIfPresent { get; set; } = true; /// - /// Gets or sets the path where the SBOM will be generated. + /// Gets or sets the path where the SBOM will be generated. For now, this property + /// will be unset as the _manifest directory is intended to be at the root of a NuGet package + /// specified by BuildDropPath. /// public string ManifestDirPath { get; set; } @@ -97,10 +99,4 @@ public partial class GenerateSbom /// Gets or sets the path to the SBOM CLI tool /// public string SbomToolPath { get; set; } - - /// - /// Gets or sets the path to the generated SBOM directory. - /// - [Output] - public string SbomPath { get; set; } } diff --git a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs index 1a6c00e7..b817ea3b 100644 --- a/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs +++ b/src/Microsoft.Sbom.Targets/GenerateSbomTask.cs @@ -100,7 +100,6 @@ public override bool Execute() externalDocumentReferenceListFile: this.ExternalDocumentListFile)).GetAwaiter().GetResult(); #pragma warning restore VSTHRD002 // Avoid problematic synchronous waits - SbomPath = !string.IsNullOrWhiteSpace(result.ManifestDirPath) ? Path.GetFullPath(result.ManifestDirPath) : null; return result.IsSuccessful; } catch (Exception e) @@ -113,7 +112,7 @@ public override bool Execute() /// /// Check for ManifestInfo and create an SbomSpecification accordingly. /// - /// A list of the parsed manifest info. Null ig the manifest info is null or empty. + /// A list of the parsed manifest info. Null if the manifest info is null or empty. private IList ValidateAndAssignSpecifications() { if (!string.IsNullOrWhiteSpace(this.ManifestInfo)) diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets index b1f6a8cc..b0baab43 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -68,10 +68,8 @@ Verbosity="$(SbomGenerationVerbosity)" ManifestInfo="$(SbomGenerationManifestInfo)" DeleteManifestDirIfPresent="$(SbomGenerationDeleteManifestDirIfPresent)" - ManifestDirPath="" SbomToolPath="$(SbomToolPath)" ContinueOnError="ErrorAndContinue"> - diff --git a/src/Microsoft.Sbom.Targets/README.md b/src/Microsoft.Sbom.Targets/README.md index 985c8b5c..df74d50b 100644 --- a/src/Microsoft.Sbom.Targets/README.md +++ b/src/Microsoft.Sbom.Targets/README.md @@ -1,6 +1,6 @@ # SBOM Generation for .NET Projects ## Microsoft.Sbom.Targets -This project implements a custom MSBuild task that generates an SBOM using the SBOM API and CLI tool. The MSBuild task binaries along with the associated targets are packaged as a NuGet package and can be consumed within a .NET project. Once installed, an SBOM will automatically be generated upon building the .NET project. +This project implements a custom MSBuild task that generates an SBOM using the SBOM API and CLI tool. The MSBuild task binaries along with the associated targets are packaged as a NuGet package and can be consumed within a .NET project. Once installed, an SBOM will automatically be generated upon packing the .NET project. ## MSBuild Task Implementation The custom MSBuild task is implemented across the following partial classes: diff --git a/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs b/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs index c86dcd38..396563dd 100644 --- a/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs +++ b/src/Microsoft.Sbom.Targets/SbomCLIToolTask.cs @@ -14,31 +14,6 @@ public partial class GenerateSbom : ToolTask { protected override string ToolName => "Microsoft.Sbom.Tool"; - /// - /// Executes the SBOM CLI Tool invocation. Need to add extra logic - /// to set SbomPath to the directory containing the SBOM. - /// - /// - public override bool Execute() - { - var taskResult = base.Execute(); - // Set the SbomPath output variable - if (taskResult) { - var manifestFolderName = "_manifest"; - if (!string.IsNullOrWhiteSpace(this.ManifestDirPath)) - { - var fullManifestDirPath = Path.GetFullPath(this.ManifestDirPath); - this.SbomPath = Path.Combine(fullManifestDirPath, manifestFolderName); - } else - { - var fullBuidDropPath = Path.GetFullPath(this.BuildDropPath); - this.SbomPath = Path.Combine(fullBuidDropPath, manifestFolderName); - } - } - - return taskResult; - } - /// /// Get full path to SBOM CLI tool. /// diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs b/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs index 63352b10..04826388 100644 --- a/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs @@ -7,7 +7,6 @@ namespace Microsoft.Sbom.Targets.E2E.Tests; using System.IO; using System.IO.Compression; using System.Runtime.InteropServices; -using Castle.Core.Internal; using Microsoft.Build.Evaluation; using Microsoft.Build.Locator; using Microsoft.Build.Logging; @@ -19,6 +18,9 @@ public class GenerateSbomE2ETests /* * The following tests validate the end-to-end workflow for importing the Microsoft.Sbom.Targets.targets * into a .NET project, building it, packing it, and validating the generated SBOM contents. + * + * NOTE: These tests are only compatible with net6.0 and net472, as there are issues when resolving NuGet assemblies when + * targeting net8.0. */ private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj b/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj index 88e02cc9..a55cf4b6 100644 --- a/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj @@ -20,8 +20,6 @@ - - diff --git a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj index 4c43dd2d..70c4c91c 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj +++ b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj @@ -17,7 +17,6 @@ - diff --git a/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs b/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs index 895c0a51..44cb95d1 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs +++ b/test/Microsoft.Sbom.Targets.Tests/Utility/GeneratedSbomValidator.cs @@ -8,8 +8,6 @@ namespace Microsoft.Sbom.Targets.Tests.Utility; using System.IO; using System.Linq; using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -93,6 +91,9 @@ internal void AssertSbomIsValid(string manifestPath, string buildDropPath, strin { Assert.IsTrue(namespaceValue.Contains($"{expectedNamespaceUriBase.Trim()}/{expectedPackageName}/{expectedPackageVersion}")); } + } else + { + Assert.Fail("An unexpected SBOM specification was used. Please specify SPDX 2.2."); } } From fc999054df347b28f43ec97a22deb7f5e59c9a9b Mon Sep 17 00:00:00 2001 From: vpatakottu <47004464+vpatakottu@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:56:54 -0700 Subject: [PATCH 30/36] address more feedback (#682) Co-authored-by: vpatakottu --- .github/workflows/build.yml | 2 -- .../AbstractGenerateSbomTaskTests.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 028e4c1b..c8a976e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,11 +4,9 @@ on: push: branches: - main - - 'feature/**' pull_request: branches: - main - - 'feature/**' permissions: contents: read diff --git a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs index 3008bd84..97e57b40 100644 --- a/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs +++ b/test/Microsoft.Sbom.Targets.Tests/AbstractGenerateSbomTaskTests.cs @@ -415,7 +415,6 @@ public void Sbom_Generation_Fails_With_Tool_Path_Not_Found() } #endif - // This test is failing due to this issue: https://github.com/microsoft/sbom-tool/issues/615 [TestMethod] public void Sbom_Fails_To_Generate_Due_To_File_In_Use() { From 82d5fe5f610aec949d41e1c6694fe4ef501b035f Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Thu, 22 Aug 2024 19:08:57 -0700 Subject: [PATCH 31/36] Pack each project separately (#681) * Pack each project separately * Remove extra dotnet apck --- pipelines/sbom-tool-main-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipelines/sbom-tool-main-build.yaml b/pipelines/sbom-tool-main-build.yaml index 22593f0e..ed5733ba 100644 --- a/pipelines/sbom-tool-main-build.yaml +++ b/pipelines/sbom-tool-main-build.yaml @@ -105,7 +105,7 @@ extends: ] condition: and(succeeded(), startswith(variables['Build.SourceBranch'], 'refs/tags/')) - - powershell: 'dotnet pack Microsoft.Sbom.sln -c $(BuildConfiguration) --no-restore --no-build -o $(Build.ArtifactStagingDirectory)/nuget --include-symbols -p:SymbolPackageFormat=snupkg' + - powershell: 'Get-ChildItem -Recurse -Filter *.csproj -Path src | ForEach-Object { dotnet pack $_.FullName -c $(BuildConfiguration) --no-restore --no-build -o $(Build.ArtifactStagingDirectory)/nuget --include-symbols -p:SymbolPackageFormat=snupkg }' displayName: 'Pack NuGet package' - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@3 From 65e9d011e6e0506476bd5605ba74ba7a73f0e61b Mon Sep 17 00:00:00 2001 From: gustavoaca1997 Date: Thu, 22 Aug 2024 19:47:20 -0700 Subject: [PATCH 32/36] Inspect the content of the Nuget package instead of extracting to disk during e2e tests. --- Directory.Packages.props | 119 +++++++++--------- .../GenerateSbomE2ETests.cs | 50 +++----- .../Microsoft.Sbom.Targets.E2E.Tests.csproj | 1 + 3 files changed, 80 insertions(+), 90 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 3fb04219..576070d8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,63 +1,64 @@ - - - - Compile - - - - 4.8.9 - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Compile + + + + 4.8.9 + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs b/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs index 04826388..297577bd 100644 --- a/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs @@ -6,6 +6,7 @@ namespace Microsoft.Sbom.Targets.E2E.Tests; using System; using System.IO; using System.IO.Compression; +using System.Linq; using System.Runtime.InteropServices; using Microsoft.Build.Evaluation; using Microsoft.Build.Locator; @@ -31,7 +32,6 @@ public class GenerateSbomE2ETests private static string sbomSpecificationName = "SPDX"; private static string sbomSpecificationVersion = "2.2"; private static string sbomSpecificationDirectoryName = $"{sbomSpecificationName}_{sbomSpecificationVersion}".ToLowerInvariant(); - private string manifestPath; private string expectedPackageName; private string expectedVersion; private string expectedSupplier; @@ -128,21 +128,24 @@ private void RestoreBuildPack(Project sampleProject) Assert.IsTrue(pack, "Failed to pack the project"); } - private void ExtractPackage() + private void InspectPackageIsWellFormed(bool isManifestPathGenerated = true) { + const string backSlash = "\\"; + const string forwardSlash = "/"; // Unzip the contents of the NuGet package var nupkgPath = Path.Combine(projectDirectory, "bin", configuration); var nupkgFile = Path.Combine(nupkgPath, $"{expectedPackageName}.{expectedVersion}.nupkg"); - var zipFile = Path.Combine(nupkgPath, $"{expectedPackageName}.{expectedVersion}.zip"); - var extractPath = Path.Combine(projectDirectory, "bin", configuration, $"{Guid.NewGuid()}.temp"); + var manifestRelativePath = Path.Combine("_manifest", sbomSpecificationDirectoryName, "manifest.spdx.json") + .Replace(backSlash, forwardSlash); - // Rename the .nupkg file to .zip - File.Copy(nupkgFile, zipFile, true); - - // Extract the .zip file - ZipFile.ExtractToDirectory(zipFile, extractPath); - - manifestPath = Path.Combine(extractPath, "_manifest", sbomSpecificationDirectoryName, "manifest.spdx.json"); + // Check the content of the NuGet package + using (var archive = ZipFile.Open(nupkgFile, ZipArchiveMode.Read)) + { + Assert.IsTrue(archive.Entries.Count() > 0); + // Nuget's zip code expects forward slashes as path separators. + Assert.IsTrue(archive.Entries.All(entry => !entry.FullName.Contains(backSlash))); + Assert.AreEqual(isManifestPathGenerated, archive.Entries.Any(entry => entry.FullName.Equals(manifestRelativePath))); + } } [TestMethod] @@ -161,10 +164,7 @@ public void SbomGenerationSucceedsForDefaultProperties() RestoreBuildPack(sampleProject); // Extract the NuGet package - ExtractPackage(); - - // Validate the SBOM exists in the package. - Assert.IsTrue(File.Exists(manifestPath)); + InspectPackageIsWellFormed(); } [TestMethod] @@ -187,10 +187,7 @@ public void SbomGenerationSucceedsForValidNamespaceBaseUriUniquePart() RestoreBuildPack(sampleProject); // Extract the NuGet package - ExtractPackage(); - - // Validate the SBOM exists in the package. - Assert.IsTrue(File.Exists(manifestPath)); + InspectPackageIsWellFormed(); } [TestMethod] @@ -222,10 +219,7 @@ public void SbomGenerationSucceedsForValidRequiredParams() RestoreBuildPack(sampleProject); // Extract the NuGet package - ExtractPackage(); - - // Validate the SBOM exists in the package. - Assert.IsTrue(File.Exists(manifestPath)); + InspectPackageIsWellFormed(); } [TestMethod] @@ -312,10 +306,7 @@ public void SbomGenerationSkipsForUnsetGenerateSBOMFlag() RestoreBuildPack(sampleProject); // Extract the NuGet package - ExtractPackage(); - - // Ensure the manifest file was not created - Assert.IsTrue(!File.Exists(manifestPath)); + InspectPackageIsWellFormed(isManifestPathGenerated: false); } [TestMethod] @@ -338,9 +329,6 @@ public void SbomGenerationSucceedsForMultiTargetedProject() RestoreBuildPack(sampleProject); // Extract the NuGet package - ExtractPackage(); - - // Validate the SBOM exists in the package. - Assert.IsTrue(File.Exists(manifestPath)); + InspectPackageIsWellFormed(); } } diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj b/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj index a55cf4b6..09b56f10 100644 --- a/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj @@ -20,6 +20,7 @@ + From fe6c158099f9df496a4ffd62e1cdaa8a800da461 Mon Sep 17 00:00:00 2001 From: Gustavo Castellanos Alfonzo Date: Thu, 22 Aug 2024 19:54:44 -0700 Subject: [PATCH 33/36] User/gustavoca/dont extract e2e tests (#684) * Inspect the content of the Nuget package instead of extracting to disk during e2e tests. * Remove extra changes in Directory.Packages.Props --- Directory.Packages.props | 1 + .../GenerateSbomE2ETests.cs | 50 +++++++------------ .../Microsoft.Sbom.Targets.E2E.Tests.csproj | 1 + 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 3fb04219..25d8a371 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -49,6 +49,7 @@ + diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs b/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs index 04826388..297577bd 100644 --- a/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/GenerateSbomE2ETests.cs @@ -6,6 +6,7 @@ namespace Microsoft.Sbom.Targets.E2E.Tests; using System; using System.IO; using System.IO.Compression; +using System.Linq; using System.Runtime.InteropServices; using Microsoft.Build.Evaluation; using Microsoft.Build.Locator; @@ -31,7 +32,6 @@ public class GenerateSbomE2ETests private static string sbomSpecificationName = "SPDX"; private static string sbomSpecificationVersion = "2.2"; private static string sbomSpecificationDirectoryName = $"{sbomSpecificationName}_{sbomSpecificationVersion}".ToLowerInvariant(); - private string manifestPath; private string expectedPackageName; private string expectedVersion; private string expectedSupplier; @@ -128,21 +128,24 @@ private void RestoreBuildPack(Project sampleProject) Assert.IsTrue(pack, "Failed to pack the project"); } - private void ExtractPackage() + private void InspectPackageIsWellFormed(bool isManifestPathGenerated = true) { + const string backSlash = "\\"; + const string forwardSlash = "/"; // Unzip the contents of the NuGet package var nupkgPath = Path.Combine(projectDirectory, "bin", configuration); var nupkgFile = Path.Combine(nupkgPath, $"{expectedPackageName}.{expectedVersion}.nupkg"); - var zipFile = Path.Combine(nupkgPath, $"{expectedPackageName}.{expectedVersion}.zip"); - var extractPath = Path.Combine(projectDirectory, "bin", configuration, $"{Guid.NewGuid()}.temp"); + var manifestRelativePath = Path.Combine("_manifest", sbomSpecificationDirectoryName, "manifest.spdx.json") + .Replace(backSlash, forwardSlash); - // Rename the .nupkg file to .zip - File.Copy(nupkgFile, zipFile, true); - - // Extract the .zip file - ZipFile.ExtractToDirectory(zipFile, extractPath); - - manifestPath = Path.Combine(extractPath, "_manifest", sbomSpecificationDirectoryName, "manifest.spdx.json"); + // Check the content of the NuGet package + using (var archive = ZipFile.Open(nupkgFile, ZipArchiveMode.Read)) + { + Assert.IsTrue(archive.Entries.Count() > 0); + // Nuget's zip code expects forward slashes as path separators. + Assert.IsTrue(archive.Entries.All(entry => !entry.FullName.Contains(backSlash))); + Assert.AreEqual(isManifestPathGenerated, archive.Entries.Any(entry => entry.FullName.Equals(manifestRelativePath))); + } } [TestMethod] @@ -161,10 +164,7 @@ public void SbomGenerationSucceedsForDefaultProperties() RestoreBuildPack(sampleProject); // Extract the NuGet package - ExtractPackage(); - - // Validate the SBOM exists in the package. - Assert.IsTrue(File.Exists(manifestPath)); + InspectPackageIsWellFormed(); } [TestMethod] @@ -187,10 +187,7 @@ public void SbomGenerationSucceedsForValidNamespaceBaseUriUniquePart() RestoreBuildPack(sampleProject); // Extract the NuGet package - ExtractPackage(); - - // Validate the SBOM exists in the package. - Assert.IsTrue(File.Exists(manifestPath)); + InspectPackageIsWellFormed(); } [TestMethod] @@ -222,10 +219,7 @@ public void SbomGenerationSucceedsForValidRequiredParams() RestoreBuildPack(sampleProject); // Extract the NuGet package - ExtractPackage(); - - // Validate the SBOM exists in the package. - Assert.IsTrue(File.Exists(manifestPath)); + InspectPackageIsWellFormed(); } [TestMethod] @@ -312,10 +306,7 @@ public void SbomGenerationSkipsForUnsetGenerateSBOMFlag() RestoreBuildPack(sampleProject); // Extract the NuGet package - ExtractPackage(); - - // Ensure the manifest file was not created - Assert.IsTrue(!File.Exists(manifestPath)); + InspectPackageIsWellFormed(isManifestPathGenerated: false); } [TestMethod] @@ -338,9 +329,6 @@ public void SbomGenerationSucceedsForMultiTargetedProject() RestoreBuildPack(sampleProject); // Extract the NuGet package - ExtractPackage(); - - // Validate the SBOM exists in the package. - Assert.IsTrue(File.Exists(manifestPath)); + InspectPackageIsWellFormed(); } } diff --git a/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj b/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj index a55cf4b6..09b56f10 100644 --- a/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj +++ b/test/Microsoft.Sbom.Targets.E2E.Tests/Microsoft.Sbom.Targets.E2E.Tests.csproj @@ -20,6 +20,7 @@ + From 52329d44aa1b42029aedfeb8541acabb51c7c389 Mon Sep 17 00:00:00 2001 From: gustavoaca1997 Date: Fri, 30 Aug 2024 14:01:54 -0700 Subject: [PATCH 34/36] Remove instance of Newtonsoft.Json --- .../Microsoft.Sbom.Targets.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj index 70c4c91c..53358e10 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj +++ b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj @@ -17,7 +17,6 @@ - From 4bf0217bda9277f86483d7ce62e565bce66d96da Mon Sep 17 00:00:00 2001 From: gustavoaca1997 Date: Fri, 30 Aug 2024 14:04:00 -0700 Subject: [PATCH 35/36] Remove not needed Message --- src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets index b0baab43..12e9bd69 100644 --- a/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets +++ b/src/Microsoft.Sbom.Targets/Microsoft.Sbom.Targets.targets @@ -71,7 +71,6 @@ SbomToolPath="$(SbomToolPath)" ContinueOnError="ErrorAndContinue"> - From 2faeea6ed594cb3daa276ba17675fc1a054a0a45 Mon Sep 17 00:00:00 2001 From: gustavoaca1997 Date: Fri, 30 Aug 2024 15:34:19 -0700 Subject: [PATCH 36/36] Revert "Remove instance of Newtonsoft.Json" This reverts commit 52329d44aa1b42029aedfeb8541acabb51c7c389. --- .../Microsoft.Sbom.Targets.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj index 53358e10..70c4c91c 100644 --- a/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj +++ b/test/Microsoft.Sbom.Targets.Tests/Microsoft.Sbom.Targets.Tests.csproj @@ -17,6 +17,7 @@ +