diff --git a/Directory.Packages.props b/Directory.Packages.props
index 655ef9d9..27c99cd4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -12,7 +12,7 @@
-
+
@@ -25,7 +25,6 @@
-
@@ -41,9 +40,11 @@
-
-
+
+
+
+
diff --git a/Microsoft.Sbom.sln b/Microsoft.Sbom.sln
index 6069741f..54633129 100644
--- a/Microsoft.Sbom.sln
+++ b/Microsoft.Sbom.sln
@@ -19,6 +19,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.SPDX22SBOMPa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.SPDX22SBOMParser.Tests", "test\Microsoft.Sbom.SPDX22SBOMParser.Tests\Microsoft.Sbom.SPDX22SBOMParser.Tests.csproj", "{ADDEE422-40D1-48D9-A5FB-BBE990272B78}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Api", "src\Microsoft.Sbom.Api\Microsoft.Sbom.Api.csproj", "{725723C5-DCA4-4BAD-8883-CC94E5F5A5A8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Sbom.Api.Tests", "test\Microsoft.Sbom.Api.Tests\Microsoft.Sbom.Api.Tests.csproj", "{4F94EA4F-CC6B-4FA0-8A7E-654EAA26B625}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -53,6 +57,14 @@ Global
{ADDEE422-40D1-48D9-A5FB-BBE990272B78}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ADDEE422-40D1-48D9-A5FB-BBE990272B78}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADDEE422-40D1-48D9-A5FB-BBE990272B78}.Release|Any CPU.Build.0 = Release|Any CPU
+ {725723C5-DCA4-4BAD-8883-CC94E5F5A5A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {725723C5-DCA4-4BAD-8883-CC94E5F5A5A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {725723C5-DCA4-4BAD-8883-CC94E5F5A5A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {725723C5-DCA4-4BAD-8883-CC94E5F5A5A8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4F94EA4F-CC6B-4FA0-8A7E-654EAA26B625}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4F94EA4F-CC6B-4FA0-8A7E-654EAA26B625}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4F94EA4F-CC6B-4FA0-8A7E-654EAA26B625}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4F94EA4F-CC6B-4FA0-8A7E-654EAA26B625}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Microsoft.Sbom.Api/Bindings.cs b/src/Microsoft.Sbom.Api/Bindings.cs
new file mode 100644
index 00000000..6f3975ea
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Bindings.cs
@@ -0,0 +1,247 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using AutoMapper;
+using Microsoft.Sbom.Extensions;
+using Microsoft.Sbom.Extensions.Entities;
+using Microsoft.Sbom.Api.Converters;
+using Microsoft.Sbom.Api.Convertors;
+using Microsoft.Sbom.Api.Entities.Output;
+using Microsoft.Sbom.Api.Executors;
+using Microsoft.Sbom.Api.Filters;
+using Microsoft.Sbom.Api.Hashing;
+using Microsoft.Sbom.Api.Logging;
+using Microsoft.Sbom.Api.Manifest;
+using Microsoft.Sbom.Api.Manifest.Configuration;
+using Microsoft.Sbom.Api.Output;
+using Microsoft.Sbom.Api.Output.Telemetry;
+using Microsoft.Sbom.Api.Providers;
+using Microsoft.Sbom.Api.SignValidator;
+using Microsoft.Sbom.Api.Utils;
+using Microsoft.Sbom.Api.Workflows;
+using Microsoft.Sbom.Api.Workflows.Helpers;
+using Microsoft.Sbom.Common;
+using Microsoft.Sbom.Contracts.Interfaces;
+using Ninject;
+using Ninject.Extensions.Conventions;
+using Ninject.Modules;
+using Serilog;
+using Microsoft.Sbom.Api.Config;
+using Microsoft.Sbom.Common.Config.Validators;
+using Microsoft.Sbom.Common.Extensions;
+
+namespace Microsoft.Sbom.Api
+{
+ ///
+ /// Creates the Ninject bindings for the whole project.
+ ///
+ ///
+ /// Microsoft.ManifestTool.Api.dll is the assembly name of the SBOM API project.
+ /// Using pattern matching until all bindings are in the same assembly.
+ ///
+ public class Bindings : NinjectModule
+ {
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1123:Do not place regions within elements", Justification = "Enable documentation of code")]
+ public override void Load()
+ {
+ Bind().ToProvider().InSingletonScope();
+
+ Bind().ToSelf();
+ Bind().To();
+ Bind().To().InSingletonScope();
+ Bind().To().InSingletonScope();
+ Bind().ToSelf();
+ Bind().To().Named(nameof(FileArrayGenerator));
+ Bind().To().Named(nameof(PackageArrayGenerator));
+ Bind().To().Named(nameof(RelationshipsArrayGenerator));
+ Bind().To().Named(nameof(ExternalDocumentReferenceGenerator));
+ Bind().ToSelf();
+ Bind().To().InSingletonScope();
+
+ Bind().To().Named(nameof(DownloadedRootPathFilter)).OnActivation(f => f.Init());
+ Bind().To().Named(nameof(ManifestFolderFilter)).OnActivation(f => f.Init());
+
+ Bind().ToProvider();
+
+ #region Bind all manifest parsers
+
+ // Search external assemblies
+ Kernel.Bind(scan => scan
+ .FromAssembliesMatching("*Parsers*")
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ // Search this assembly in case --self-contained is used with dotnet publish
+ Kernel.Bind(scan => scan
+ .FromThisAssembly()
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ Bind().ToProvider().InSingletonScope();
+ Bind().ToSelf().InSingletonScope().OnActivation(m => m.Init());
+
+ #endregion
+
+ #region Bind all manifest generators
+
+ // Search external assemblies
+ Kernel.Bind(scan => scan
+ .FromAssembliesMatching("*Parsers*")
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ // Search this assembly in case --self-contained is used with dotnet publish
+ Kernel.Bind(scan => scan
+ .FromThisAssembly()
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ Bind().ToSelf().InSingletonScope().OnActivation(mg => mg.Init());
+
+ #endregion
+
+ #region Bind all signature validators
+ Kernel.Bind(scan => scan
+ .FromAssembliesMatching("*Parsers*")
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ Kernel.Bind(scan => scan
+ .FromThisAssembly()
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ Bind().ToSelf().InSingletonScope().OnActivation(s => s.Init());
+
+ #endregion
+
+ #region Manifest Config
+
+ Kernel.Bind(scan => scan
+ .FromAssembliesMatching("*Parsers*")
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ Kernel.Bind(scan => scan
+ .FromThisAssembly()
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ Bind().To().InSingletonScope();
+ Bind().To();
+
+ #endregion
+
+ #region QuickBuild Manifest workflow bindings
+ Bind().To();
+ Bind().To();
+ #endregion
+
+ #region AutoMapper bindings
+ var mapperConfiguration = new MapperConfiguration(cfg => cfg.AddProfile());
+ mapperConfiguration.AssertConfigurationIsValid();
+ Bind().ToConstant(mapperConfiguration).InSingletonScope();
+ Bind().ToMethod(ctx =>
+ new Mapper(mapperConfiguration, type => ctx.Kernel.Get(type)));
+
+ #endregion
+
+ #region Workflows
+
+ Bind().To().Named(nameof(DropValidatorWorkflow));
+ Bind().To().Named(nameof(SBOMGenerationWorkflow));
+
+ #endregion
+
+ Kernel.Bind(scan => scan
+ .FromThisAssembly()
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllBaseClasses());
+
+ #region Bind metadata providers
+
+ Kernel.Bind(scan => scan
+ .FromAssembliesInPath(new AssemblyConfig().AssemblyDirectory)
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ Bind().To();
+
+ #endregion
+
+ #region Bind all sources providers.
+ Kernel.Bind(scan => scan
+ .FromThisAssembly()
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+ #endregion
+
+ #region Converters
+
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+
+ #endregion
+
+ #region Executors
+
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().ToSelf().InThreadScope();
+ Bind().To().InThreadScope();
+
+ #endregion
+
+ #region Bind all hash algorithm providers
+
+ // TODO: Put all dependent assemblies in the plugins folder and search using
+ // that path here.
+ Kernel.Bind(scan => scan
+ .FromAssembliesMatching("*Parsers*", "*Contract*")
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ // We should move all algorithm implementations into their own lib, so that
+ // we can remove this additional scan.
+ Kernel.Bind(scan => scan.
+ FromThisAssembly()
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllInterfaces());
+
+ Bind().To().InSingletonScope();
+
+ #endregion
+
+ Bind().To().InSingletonScope();
+ Bind().ToSelf().InSingletonScope();
+ Bind().ToSelf().InSingletonScope();
+ Bind().ToSelf().InSingletonScope();
+ Bind().To().InSingletonScope();
+ Bind().To().InSingletonScope();
+ }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/ApiConfigurationBuilder.cs b/src/Microsoft.Sbom.Api/Config/ApiConfigurationBuilder.cs
new file mode 100644
index 00000000..eb47d576
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/ApiConfigurationBuilder.cs
@@ -0,0 +1,168 @@
+// 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.Linq;
+using Microsoft.Sbom.Extensions.Entities;
+using Microsoft.Sbom.Api.Utils;
+using Microsoft.Sbom.Common;
+using Microsoft.Sbom.Common.Config;
+using Microsoft.Sbom.Contracts;
+using Serilog.Events;
+using Constants = Microsoft.Sbom.Common.Constants;
+
+namespace Microsoft.Sbom.Api.Config
+{
+ ///
+ /// Builds the configuration object for the SBOM api.
+ ///
+ internal class ApiConfigurationBuilder
+ {
+ ///
+ /// Gets a generate configuration.
+ ///
+ /// Path where package exists. If scanning start here.
+ /// Output path to where manifest is generated.
+ /// Use null to scan.
+ /// Use null to scan.
+ ///
+ ///
+ ///
+ ///
+ /// A generate configuration.
+ internal Configuration GetConfiguration(
+ string rootPath,
+ string manifestDirPath,
+ IEnumerable files,
+ IEnumerable packages,
+ SBOMMetadata metadata,
+ IList specifications = null,
+ RuntimeConfiguration runtimeConfiguration = null,
+ string externalDocumentReferenceListFile = null,
+ string componentPath = null)
+ {
+ if (string.IsNullOrWhiteSpace(rootPath))
+ {
+ throw new ArgumentException($"'{nameof(rootPath)}' cannot be null or whitespace.", nameof(rootPath));
+ }
+
+ if (metadata is null)
+ {
+ throw new ArgumentNullException(nameof(metadata));
+ }
+
+ RuntimeConfiguration sanitizedRuntimeConfiguration = SanitiseRuntimeConfiguration(runtimeConfiguration);
+
+ var configuration = new Configuration();
+ configuration.BuildDropPath = GetConfigurationSetting(rootPath);
+ configuration.ManifestDirPath = GetConfigurationSetting(manifestDirPath);
+ configuration.ManifestToolAction = ManifestToolActions.Generate;
+ configuration.PackageName = GetConfigurationSetting(metadata.PackageName);
+ configuration.PackageVersion = GetConfigurationSetting(metadata.PackageVersion);
+ configuration.Parallelism = GetConfigurationSetting(sanitizedRuntimeConfiguration.WorkflowParallelism);
+ configuration.GenerationTimestamp = GetConfigurationSetting(sanitizedRuntimeConfiguration.GenerationTimestamp);
+ configuration.NamespaceUriBase = GetConfigurationSetting(sanitizedRuntimeConfiguration.NamespaceUriBase);
+ configuration.NamespaceUriUniquePart = GetConfigurationSetting(sanitizedRuntimeConfiguration.NamespaceUriUniquePart);
+ configuration.FollowSymlinks = GetConfigurationSetting(sanitizedRuntimeConfiguration.FollowSymlinks);
+
+ SetVerbosity(sanitizedRuntimeConfiguration, configuration);
+
+ if (packages != null)
+ {
+ configuration.PackagesList = GetConfigurationSetting(packages);
+ }
+
+ if (files != null)
+ {
+ configuration.FilesList = GetConfigurationSetting(files);
+ }
+
+ if (externalDocumentReferenceListFile != null)
+ {
+ configuration.ExternalDocumentReferenceListFile = GetConfigurationSetting(externalDocumentReferenceListFile);
+ }
+
+ if (!string.IsNullOrWhiteSpace(componentPath))
+ {
+ configuration.BuildComponentPath = GetConfigurationSetting(componentPath);
+ }
+
+ // Convert sbom specifications to manifest info.
+ if (specifications != null)
+ {
+ if (specifications.Count == 0)
+ {
+ throw new ArgumentException($"'{nameof(specifications)}' must have at least 1 specification.", nameof(specifications));
+ }
+
+ IList manifestInfos = specifications
+ .Select(s => s.ToManifestInfo())
+ .ToList();
+
+ configuration.ManifestInfo = GetConfigurationSetting(manifestInfos);
+ }
+
+ return configuration;
+ }
+
+ private void SetVerbosity(RuntimeConfiguration sanitizedRuntimeConfiguration, Configuration configuration)
+ {
+ switch (sanitizedRuntimeConfiguration.Verbosity)
+ {
+ case System.Diagnostics.Tracing.EventLevel.Critical:
+ configuration.Verbosity = GetConfigurationSetting(LogEventLevel.Fatal);
+ break;
+ case System.Diagnostics.Tracing.EventLevel.Informational:
+ configuration.Verbosity = GetConfigurationSetting(LogEventLevel.Information);
+ break;
+ case System.Diagnostics.Tracing.EventLevel.Error:
+ configuration.Verbosity = GetConfigurationSetting(LogEventLevel.Error);
+ break;
+ case System.Diagnostics.Tracing.EventLevel.LogAlways:
+ configuration.Verbosity = GetConfigurationSetting(LogEventLevel.Verbose);
+ break;
+ case System.Diagnostics.Tracing.EventLevel.Warning:
+ configuration.Verbosity = GetConfigurationSetting(LogEventLevel.Warning);
+ break;
+ case System.Diagnostics.Tracing.EventLevel.Verbose:
+ configuration.Verbosity = GetConfigurationSetting(LogEventLevel.Verbose);
+ break;
+ default:
+ configuration.Verbosity = GetConfigurationSetting(Constants.DefaultLogLevel);
+ break;
+ }
+ }
+
+ private ConfigurationSetting GetConfigurationSetting(T value)
+ {
+ return new ConfigurationSetting
+ {
+ Value = value,
+ Source = SettingSource.SBOMApi
+ };
+ }
+
+ private RuntimeConfiguration SanitiseRuntimeConfiguration(RuntimeConfiguration runtimeConfiguration)
+ {
+ if (runtimeConfiguration == null)
+ {
+ runtimeConfiguration = new RuntimeConfiguration
+ {
+ WorkflowParallelism = Constants.DefaultParallelism,
+ Verbosity = System.Diagnostics.Tracing.EventLevel.Warning,
+ DeleteManifestDirectoryIfPresent = false,
+ FollowSymlinks = true
+ };
+ }
+
+ if (runtimeConfiguration.WorkflowParallelism < Constants.MinParallelism
+ || runtimeConfiguration.WorkflowParallelism > Constants.MaxParallelism)
+ {
+ runtimeConfiguration.WorkflowParallelism = Constants.DefaultParallelism;
+ }
+
+ return runtimeConfiguration;
+ }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/ArgRevivers.cs b/src/Microsoft.Sbom.Api/Config/ArgRevivers.cs
new file mode 100644
index 00000000..a83f6355
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/ArgRevivers.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Sbom.Extensions.Entities;
+using Microsoft.Sbom.Contracts.Enums;
+using PowerArgs;
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Sbom.Api.Config
+{
+ public class ArgRevivers
+ {
+ ///
+ /// Creates a list of objects from a string value
+ /// The string manifest infos are seperated by commas.
+ ///
+ [ArgReviver]
+ public static IList ReviveManifestInfo(string _, string value)
+ {
+ try
+ {
+ IList manifestInfos = new List();
+ string[] values = value.Split(',');
+ foreach (var manifestInfoStr in values)
+ {
+ manifestInfos.Add(ManifestInfo.Parse(manifestInfoStr));
+ }
+
+ return manifestInfos;
+ }
+ catch (Exception e)
+ {
+ throw new ValidationArgException($"Unable to parse manifest info string list: {value}. Error: {e.Message}");
+ }
+ }
+
+ ///
+ /// Creates an object from a string value.
+ ///
+ [ArgReviver]
+ public static AlgorithmName ReviveAlgorithmName(string _, string value)
+ {
+ try
+ {
+ // Return a placeholder object for now. The config post processor will convert this into
+ // a real AlgorithmName object. We only need to preserve the string value (name) of the algorithm.
+ return new AlgorithmName(value, null);
+ }
+ catch (Exception e)
+ {
+ throw new ValidationArgException($"Unable to parse algorithm name: {value}. Error: {e.Message}");
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/Args/CommonArgs.cs b/src/Microsoft.Sbom.Api/Config/Args/CommonArgs.cs
new file mode 100644
index 00000000..bb7a2139
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/Args/CommonArgs.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Sbom.Extensions.Entities;
+using Microsoft.Sbom.Common.Config;
+using PowerArgs;
+using Serilog.Events;
+using System.Collections.Generic;
+
+namespace Microsoft.Sbom.Api.Config.Args
+{
+ ///
+ /// Defines the common arguments used by all actions of the ManifestTool
+ ///
+ public class CommonArgs
+ {
+ ///
+ /// Gets or sets display this amount of detail in the logging output.
+ ///
+ [ArgDescription("Display this amount of detail in the logging output.")]
+ public LogEventLevel? Verbosity { get; set; }
+
+ ///
+ /// Gets or sets the number of parallel threads to use for the workflows.
+ ///
+ [ArgDescription("The number of parallel threads to use for the workflows.")]
+ public int? Parallelism { get; set; }
+
+ ///
+ /// Gets or sets a JSON config file that can be used to specify all the arguments for an action
+ ///
+ [ArgDescription("The json file that contains the configuration for the DropValidator.")]
+ public string ConfigFilePath { get; set; }
+
+ ///
+ /// Gets or sets the action currently being performed by the manifest tool.
+ ///
+ [ArgIgnore]
+ public ManifestToolActions ManifestToolAction { get; set; }
+
+ [ArgShortcut("t")]
+ [ArgDescription("Specify a file where we should write detailed telemetry for the workflow.")]
+ public string TelemetryFilePath { get; set; }
+
+ ///
+ /// Gets or sets if set to false, we will not follow symlinks while traversing the build drop folder. Default is set to 'true'.
+ ///
+ [ArgDescription("If set to false, we will not follow symlinks while traversing the build drop folder. Default is set to 'true'.")]
+ public bool? FollowSymlinks { get; set; }
+
+ ///
+ /// Gets or sets the name and version of the manifest format that we are using.
+ ///
+ [ArgDescription("A list of the name and version of the manifest format that we are using.")]
+ [ArgShortcut("mi")]
+ public IList ManifestInfo { get; set; }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/Args/GenerationArgs.cs b/src/Microsoft.Sbom.Api/Config/Args/GenerationArgs.cs
new file mode 100644
index 00000000..0309570d
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/Args/GenerationArgs.cs
@@ -0,0 +1,98 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using PowerArgs;
+
+namespace Microsoft.Sbom.Api.Config.Args
+{
+ ///
+ /// The command line arguments provided for the generate action in ManifestTool
+ ///
+ public class GenerationArgs : CommonArgs
+ {
+ ///
+ /// Gets or sets the root folder of the drop directory for which the manifest file will be generated.
+ ///
+ [ArgShortcut("b")]
+ [ArgRequired(IfNot = "ConfigFilePath")]
+ [ArgDescription("The root folder of the drop directory for which the manifest file will be generated.")]
+ public string BuildDropPath { get; set; }
+
+ ///
+ /// Gets or sets the folder containing the build components and packages.
+ ///
+ [ArgShortcut("bc")]
+ [ArgDescription("The folder containing the build components and packages.")]
+ public string BuildComponentPath { get; set; }
+
+ ///
+ /// Gets or sets the file path containing a list of files for which the manifest file will be generated.
+ /// List file is an unordered set of files formated as one file per line separated
+ /// by Environment.NewLine. Blank lines are discarded.
+ ///
+ [ArgShortcut("bl")]
+ [ArgDescription("The file path to a file containing a list of files one file per line for which the manifest file will be generated. Only files listed in the file will be inlcuded in the generated manifest.")]
+ public string BuildListFile { get; set; }
+
+ ///
+ /// Gets or sets the root folder where the generated manifest (and other files like bsi.json) files will be placed.
+ /// By default we will generate this folder in the same level as the build drop with the name '_manifest'
+ ///
+ [ArgShortcut("m")]
+ [ArgDescription("The path of the directory where the generated manifest files will be placed." +
+ " If this parameter is not specified, the files will be placed in {BuildDropPath}/_manifest directory.")]
+ public string ManifestDirPath { get; set; }
+
+ ///
+ /// Gets or sets the name of the package this SBOM represents.
+ ///
+ [ArgShortcut("pn")]
+ [ArgDescription("The name of the package this SBOM represents. If this is not provided, we will try to infer this " +
+ "name from the build that generated this package, if that also fails, the SBOM generation fails.")]
+ public string PackageName { get; set; }
+
+ [ArgShortcut("pv")]
+ [ArgDescription("The version of the package this SBOM represents. If this is not provided, we will " +
+ "try to infer the version from the build that generated this package, if that also fails, the " +
+ "SBOM generation fails.")]
+ public string PackageVersion { get; set; }
+
+ [ArgDescription("Comma separated list of docker image names or hashes to be scanned for packages, ex: ubuntu:16.04, 56bab49eef2ef07505f6a1b0d5bd3a601dfc3c76ad4460f24c91d6fa298369ab.")]
+ [ArgShortcut("di")]
+ public string DockerImagesToScan { get; set; }
+
+ [ArgShortcut("cd")]
+ [ArgDescription("Additional set of arguments for Component Detector. An appropriate usage of this would be a space-delimited list of `--key value` pairs, respresenting command-line switches.")]
+ public string AdditionalComponentDetectorArgs { get; set; }
+
+ ///
+ /// Gets or sets the path to a file containing a list of external SBOMs that will be included as external document reference in the output SBOM.
+ ///
+ [ArgShortcut("er")]
+ [ArgDescription("The path to a file containing a list of external SBOMs that will be included as external document reference in the output SBOM. SPDX 2.2 is the only supported format for now.")]
+ public string ExternalDocumentReferenceListFile { get; set; }
+
+ ///
+ /// Gets or sets unique part of the namespace uri for SPDX 2.2 SBOMs. This value should be globally unique.
+ /// If this value is not provided, we generate a unique guid that will make the namespace globally unique.
+ ///
+ [ArgShortcut("nsu")]
+ [ArgDescription("A unique valid URI part that will be appended to the SPDX SBOM namespace URI. This value should be globally unique.")]
+ public string NamespaceUriUniquePart { get; set; }
+
+ ///
+ /// Gets or sets the base of the URI that will be used to generate this SBOM. This should be a value that identifies that
+ /// the SBOM belongs to a single publisher (or company)
+ ///
+ [ArgShortcut("nsb")]
+ [ArgDescription("The base path of the SBOM namespace URI.")]
+ public string NamespaceUriBase { get; set; }
+
+ ///
+ /// Gets or sets a timestamp in the format yyyy-MM-ddTHH:mm:ssZ that will be used as the generated timestamp for the SBOM.
+ ///
+ [ArgShortcut("gt")]
+ [ArgDescription("A timestamp in the format 'yyyy-MM-ddTHH:mm:ssZ' that will be used as the generated timestamp for the SBOM.")]
+ public string GenerationTimestamp { get; set; }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/Args/ValidationArgs.cs b/src/Microsoft.Sbom.Api/Config/Args/ValidationArgs.cs
new file mode 100644
index 00000000..f8f461ad
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/Args/ValidationArgs.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using PowerArgs;
+using Microsoft.Sbom.Extensions.Entities;
+using System.Collections.Generic;
+using Microsoft.Sbom.Contracts.Enums;
+
+namespace Microsoft.Sbom.Api.Config.Args
+{
+ ///
+ /// The command line arguments provided for the validate action in ManifestTool
+ ///
+ public class ValidationArgs : CommonArgs
+ {
+ ///
+ /// Gets or sets the root folder of the drop directory to validate.
+ ///
+ [ArgShortcut("b")]
+ [ArgRequired(IfNot = "ConfigFilePath")]
+ [ArgDescription("The root folder of the drop directory to validate.")]
+ public string BuildDropPath { get; set; }
+
+ ///
+ /// Gets or sets the path to the _manifest folder..
+ ///
+ [ArgShortcut("m")]
+ [ArgDescription("The path of the directory where the manifest will be validated." +
+ " If this parameter is not specified, the manifest will be validated in {BuildDropPath}/_manifest directory.")]
+ public string ManifestDirPath { get; set; }
+
+ ///
+ /// Gets or sets the path where the output json should be written.
+ ///
+ [ArgShortcut("o")]
+ [ArgRequired(IfNot = "ConfigFilePath")]
+ [ArgDescription("The path where the output json should be written.")]
+ public string OutputPath { get; set; }
+
+ ///
+ /// Gets or sets the path of the signed catalog file used to validate the manifest.json
+ ///
+ [ArgDescription("The path of signed catalog file that is used to verify the signature of the manifest json file.")]
+ public string CatalogFilePath { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether if set, will validate the manifest using the signed catalog file.
+ ///
+ [ArgShortcut("s")]
+ [ArgDescription("If set, will validate the manifest using the signed catalog file.")]
+ public bool ValidateSignature { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether if set, will not fail validation on the files presented in Manifest but missing on the disk.
+ ///
+ [ArgShortcut("im")]
+ [ArgDescription("If set, will not fail validation on the files presented in Manifest but missing on the disk.")]
+ public bool IgnoreMissing { get; set; }
+
+ ///
+ /// Gets or sets if you're downloading only a part of the drop using the '-r' or 'root' parameter
+ /// in the drop client, specify the same string value here in order to skip
+ /// validating paths that are not downloaded.
+ ///
+ [ArgDescription(@"If you're downloading only a part of the drop using the '-r' or 'root' parameter in the drop client, specify the same string value here in order to skip validating paths that are not downloaded.")]
+ [ArgShortcut("r")]
+ public string RootPathFilter { get; set; }
+
+ ///
+ /// Gets or sets the Hash algorithm to use while verifying or generating the hash value of a file.
+ ///
+ [ArgDescription("The Hash algorithm to use while verifying or generating the hash value of a file")]
+ public AlgorithmName HashAlgorithm { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Sbom.Api/Config/ConfigFile.cs b/src/Microsoft.Sbom.Api/Config/ConfigFile.cs
new file mode 100644
index 00000000..5f19f43b
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/ConfigFile.cs
@@ -0,0 +1,159 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Sbom.Extensions.Entities;
+using Microsoft.Sbom.Common.Config;
+using Microsoft.Sbom.Contracts.Enums;
+using Serilog.Events;
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Sbom.Api.Config
+{
+ ///
+ /// This is the schema for the config file that is used to provide
+ /// the validator with additional params in a JSON format. Most of
+ /// these fields can also be provided through the command line. In case
+ /// of a conflict (same value provided in config file and command line, we
+ /// throw an input error.
+ ///
+ public class ConfigFile
+ {
+ ///
+ /// Gets or sets the root folder of the drop directory to validate.
+ ///
+ public string BuildDropPath { get; set; }
+
+ ///
+ /// Gets or sets the folder containing the build components and packages.
+ ///
+ public string BuildComponentPath { get; set; }
+
+ ///
+ /// Gets or sets the file path containing a list of files for which the manifest file will be generated.
+ ///
+ public string BuildListFile { get; set; }
+
+ ///
+ /// Gets or sets the path of the manifest json to use for validation.
+ ///
+ [Obsolete("This property is obsolete. Value will by generated by the system.")]
+ public string ManifestPath { get; set; }
+
+ ///
+ /// Gets or sets the root folder where the generated manifest (and other files like bsi.json) files will be placed.
+ /// By default we will generate this folder in the same level as the build drop with the name '_manifest'
+ ///
+ public string ManifestDirPath { get; set; }
+
+ ///
+ /// Gets or sets the path where the output json should be written.
+ ///
+ public string OutputPath { get; set; }
+
+ ///
+ /// Gets or sets the path of the signed catalog file used to validate the manifest.json
+ ///
+ public string CatalogFilePath { get; set; }
+
+ ///
+ /// Gets or sets if set, will validate the manifest using the signed catalog file.
+ ///
+ public bool? ValidateSignature { get; set; }
+
+ ///
+ /// Gets or sets if set, will not fail validation on the files presented in Manifest but missing on the disk.
+ ///
+ public bool? IgnoreMissing { get; set; }
+
+ ///
+ /// Gets or sets if you're downloading only a part of the drop using the '-r' or 'root' parameter
+ /// in the drop client, specify the same string value here in order to skip
+ /// validating paths that are not downloaded.
+ ///
+ public string RootPathFilter { get; set; }
+
+ ///
+ /// Gets or sets display this amount of detail in the logging output.
+ ///
+ public LogEventLevel? Verbosity { get; set; }
+
+ ///
+ /// Gets or sets the number of parallel threads to run for the validator.
+ ///
+ public int? Parallelism { get; set; }
+
+ ///
+ /// Gets or sets a list of the name and version of the manifest format that we are using.
+ ///
+ public IList ManifestInfo { get; set; }
+
+ ///
+ /// Gets or sets the Hash algorithm to use while verifying or generating the hash value of a file.
+ ///
+ public AlgorithmName HashAlgorithm { get; set; }
+
+ ///
+ /// Gets or sets the name of the package this SBOM represents.
+ ///
+ public string PackageName { get; set; }
+
+ ///
+ /// Gets or sets the version of the package this SBOM represents.
+ ///
+ public string PackageVersion { get; set; }
+
+ ///
+ /// Gets or sets a JSON config file that can be used to specify all the arguments for an action
+ ///
+ [JsonIgnore]
+ public string ConfigFilePath { get; set; }
+
+ [JsonIgnore]
+ public ManifestToolActions ManifestToolAction { get; set; }
+
+ ///
+ /// Gets or sets if specified, we will store the generated telemetry for the execution
+ /// of the SBOM tool at this path.
+ ///
+ public string TelemetryFilePath { get; set; }
+
+ ///
+ /// Gets or sets comma separated list of docker image names or hashes to be scanned for packages, ex: ubuntu:16.04, 56bab49eef2ef07505f6a1b0d5bd3a601dfc3c76ad4460f24c91d6fa298369ab.
+ ///
+ public string DockerImagesToScan { get; set; }
+
+ ///
+ /// Gets or sets the file path containing a list of external SBOMs to include as external document reference.
+ ///
+ public string ExternalDocumentReferenceListFile { get; set; }
+
+ ///
+ /// Gets or sets additional set of command-line arguments for Component Detector.
+ ///
+ public string AdditionalComponentDetectorArgs { get; set; }
+
+ ///
+ /// Gets or sets unique part of the namespace uri for SPDX 2.2 SBOMs. This value should be globally unique.
+ /// If this value is not provided, we generate a unique guid that will make the namespace globally unique.
+ ///
+ public string NamespaceUriUniquePart { get; set; }
+
+ ///
+ /// Gets or sets the base of the URI that will be used to generate this SBOM. This should be a value that identifies that
+ /// the SBOM belongs to a single publisher (or company)
+ ///
+ public string NamespaceUriBase { get; set; }
+
+ ///
+ /// Gets or sets a timestamp in the format yyyy-MM-ddTHH:mm:ssZ that will be used as the generated timestamp for the SBOM.
+ ///
+ public string GenerationTimestamp { get; set; }
+
+ ///
+ /// Gets or sets if set to false, we will not follow symlinks while traversing the build drop folder.
+ ///
+ public bool? FollowSymlinks { get; set; }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/ConfigFileParser.cs b/src/Microsoft.Sbom.Api/Config/ConfigFileParser.cs
new file mode 100644
index 00000000..a4d80bba
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/ConfigFileParser.cs
@@ -0,0 +1,35 @@
+// 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.IO;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Microsoft.Sbom.Common;
+
+namespace Microsoft.Sbom.Api.Config
+{
+ ///
+ /// Used to parse the configuration as a from a JSON file.
+ ///
+ public class ConfigFileParser
+ {
+ private readonly IFileSystemUtils fileSystemUtils;
+
+ public ConfigFileParser(IFileSystemUtils fileSystemUtils)
+ {
+ this.fileSystemUtils = fileSystemUtils ?? throw new ArgumentNullException(nameof(fileSystemUtils));
+ }
+
+ public async Task ParseFromJsonFile(string filePath)
+ {
+ if (string.IsNullOrEmpty(filePath))
+ {
+ throw new ArgumentNullException($"{nameof(filePath)} cannot be emtpy.");
+ }
+
+ using Stream openStream = fileSystemUtils.OpenRead(filePath);
+ return await JsonSerializer.DeserializeAsync(openStream);
+ }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/ConfigPostProcessor.cs b/src/Microsoft.Sbom.Api/Config/ConfigPostProcessor.cs
new file mode 100644
index 00000000..0a9a49a3
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/ConfigPostProcessor.cs
@@ -0,0 +1,89 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using AutoMapper;
+using DropValidator.Api.Config;
+using Microsoft.Sbom.Api.Config.Validators;
+using Microsoft.Sbom.Api.Hashing;
+using Microsoft.Sbom.Common;
+using Microsoft.Sbom.Common.Config;
+using Microsoft.Sbom.Common.Config.Validators;
+using Microsoft.Sbom.Contracts.Enums;
+using PowerArgs;
+using System;
+using System.ComponentModel;
+using Constants = Microsoft.Sbom.Api.Utils.Constants;
+
+namespace Microsoft.Sbom.Api.Config
+{
+ ///
+ /// Runs finalizing operations on the configuration once it has been successfully parsed.
+ ///
+ public class ConfigPostProcessor : IMappingAction
+ {
+ private readonly ConfigValidator[] configValidators;
+ private readonly ConfigSanitizer configSanitizer;
+
+ public ConfigPostProcessor(ConfigValidator[] configValidators, ConfigSanitizer configSanitizer)
+ {
+ this.configValidators = configValidators ?? throw new ArgumentNullException(nameof(configValidators));
+ this.configSanitizer = configSanitizer ?? throw new ArgumentNullException(nameof(configSanitizer));
+ }
+
+ public void Process(Configuration source, Configuration destination, ResolutionContext context)
+ {
+ // Set current action on config validators
+ configValidators.ForEach(c => c.CurrentAction = destination.ManifestToolAction);
+
+ foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(destination))
+ {
+ // Assign default values if any using the default value attribute.
+ if (property.GetValue(destination) == null &&
+ property.Attributes[typeof(System.ComponentModel.DefaultValueAttribute)]
+ is System.ComponentModel.DefaultValueAttribute defaultValueAttribute)
+ {
+ SetDefautValue(destination, defaultValueAttribute.Value, property);
+ }
+
+ // Run validators on all properties.
+ configValidators.ForEach(v => v.Validate(property.DisplayName, property.GetValue(destination), property.Attributes));
+ }
+
+ // Sanitize configuration
+ destination = configSanitizer.SanitizeConfig(destination);
+ }
+
+ private void SetDefautValue(Configuration destination, object value, PropertyDescriptor property)
+ {
+ if (value is string valueString)
+ {
+ property.SetValue(destination, new ConfigurationSetting
+ {
+ Value = valueString,
+ Source = SettingSource.Default
+ });
+ }
+
+ if (value is int valueInt)
+ {
+ property.SetValue(destination, new ConfigurationSetting
+ {
+ Value = valueInt,
+ Source = SettingSource.Default
+ });
+ }
+
+ if (value is bool valueBool)
+ {
+ property.SetValue(destination, new ConfigurationSetting
+ {
+ Value = valueBool,
+ Source = SettingSource.Default
+ });
+ }
+
+ // Fall through, only primitive types are currently supported.
+ // Add more primitive types if needed here.
+ }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/ConfigSanitizer.cs b/src/Microsoft.Sbom.Api/Config/ConfigSanitizer.cs
new file mode 100644
index 00000000..614503b3
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/ConfigSanitizer.cs
@@ -0,0 +1,150 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Sbom.Extensions.Entities;
+using Microsoft.Sbom.Api.Hashing;
+using Microsoft.Sbom.Api.Utils;
+using Microsoft.Sbom.Common;
+using Microsoft.Sbom.Common.Config;
+using Microsoft.Sbom.Contracts.Enums;
+using PowerArgs;
+using System;
+using System.Collections.Generic;
+using Constants = Microsoft.Sbom.Api.Utils.Constants;
+
+namespace Microsoft.Sbom.Api.Config
+{
+ ///
+ /// Sanitizes a validated configuration by setting additional parameters or fixing default parameters if needed.
+ ///
+ public class ConfigSanitizer
+ {
+ private readonly IHashAlgorithmProvider hashAlgorithmProvider;
+ private readonly IFileSystemUtils fileSystemUtils;
+ private readonly IAssemblyConfig assemblyConfig;
+
+ public ConfigSanitizer(IHashAlgorithmProvider hashAlgorithmProvider, IFileSystemUtils fileSystemUtils, IAssemblyConfig assemblyConfig)
+ {
+ this.hashAlgorithmProvider = hashAlgorithmProvider ?? throw new ArgumentNullException(nameof(hashAlgorithmProvider));
+ this.fileSystemUtils = fileSystemUtils ?? throw new ArgumentNullException(nameof(fileSystemUtils));
+ this.assemblyConfig = assemblyConfig ?? throw new ArgumentNullException(nameof(assemblyConfig));
+ }
+
+ public Configuration SanitizeConfig(Configuration configuration)
+ {
+ if (configuration is null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ configuration.HashAlgorithm = GetHashAlgorithmName(configuration);
+
+ // set ManifestDirPath after validation of DirectoryExist and DirectoryPathIsWritable, this wouldn't exist because it needs to be created by the tool.
+ configuration.ManifestDirPath = GetManifestDirPath(configuration.ManifestDirPath, configuration.BuildDropPath.Value, configuration.ManifestToolAction);
+
+ // Set namespace value if provided in the assembly
+ configuration.NamespaceUriBase = GetNamespaceBaseUriFromAssembly(configuration);
+
+ // Set default ManifestInfo for validation in case user doesn't provide a value.
+ configuration.ManifestInfo = GetDefaultManifestInfoForValidationAction(configuration);
+
+ return configuration;
+ }
+
+ private ConfigurationSetting> GetDefaultManifestInfoForValidationAction(Configuration configuration)
+ {
+ if (configuration.ManifestToolAction != ManifestToolActions.Validate
+ || (configuration.ManifestInfo.Value != null && configuration.ManifestInfo.Value.Count != 0))
+ {
+ return configuration.ManifestInfo;
+ }
+
+ var defaultManifestInfo = assemblyConfig.DefaultManifestInfoForValidationAction;
+ if (defaultManifestInfo == null && (configuration.ManifestInfo.Value == null || configuration.ManifestInfo.Value.Count == 0))
+ {
+ throw new ValidationArgException($"Please provide a value for the ManifestInfo (-mi) parameter to validate the SBOM.");
+ }
+
+ return new ConfigurationSetting>
+ {
+ Source = SettingSource.Default,
+ Value = new List()
+ {
+ defaultManifestInfo
+ }
+ };
+ }
+
+ private ConfigurationSetting GetHashAlgorithmName(Configuration configuration)
+ {
+ if (configuration.ManifestToolAction != ManifestToolActions.Validate)
+ {
+ return configuration.HashAlgorithm;
+ }
+
+ // Convert to actual hash algorithm values.
+ var oldValue = configuration.HashAlgorithm;
+ var newValue = hashAlgorithmProvider.Get(oldValue?.Value?.Name);
+
+ return new ConfigurationSetting
+ {
+ Source = oldValue.Source,
+ Value = newValue
+ };
+ }
+
+ private ConfigurationSetting GetNamespaceBaseUriFromAssembly(Configuration configuration)
+ {
+ // If assembly name is not defined returned the current value.
+ if (string.IsNullOrWhiteSpace(assemblyConfig.DefaultSBOMNamespaceBaseUri))
+ {
+ return configuration.NamespaceUriBase;
+ }
+
+ // If the user provides the parameter even when the assembly attribute is provided,
+ // show a warning on the console.
+ if (!string.IsNullOrWhiteSpace(configuration.NamespaceUriBase?.Value))
+ {
+ Console.WriteLine(assemblyConfig.DefaultSBOMNamespaceBaseUriWarningMessage);
+ }
+
+ return new ConfigurationSetting
+ {
+ Source = SettingSource.Default,
+ Value = assemblyConfig.DefaultSBOMNamespaceBaseUri
+ };
+ }
+
+ ///
+ /// Set ManifestDirPath if the value is null or empty to default value
+ ///
+ private ConfigurationSetting GetManifestDirPath(ConfigurationSetting manifestDirPathConfig, string buildDropPath, ManifestToolActions manifestToolAction)
+ {
+ if (string.IsNullOrEmpty(manifestDirPathConfig?.Value))
+ {
+ return new ConfigurationSetting
+ {
+ Value = fileSystemUtils.JoinPaths(buildDropPath, Constants.ManifestFolder),
+ Source = SettingSource.Default
+ };
+ }
+
+ return new ConfigurationSetting
+ {
+ Value = EnsurePathEndsWithManifestFolderForGenerate(manifestDirPathConfig.Value, manifestToolAction),
+ Source = manifestDirPathConfig.Source
+ };
+ }
+
+ private string EnsurePathEndsWithManifestFolderForGenerate(string value, ManifestToolActions manifestToolAction)
+ {
+ if (manifestToolAction == ManifestToolActions.Generate)
+ {
+ // For generate action, add the _manifest folder at the end of the path
+ return fileSystemUtils.JoinPaths(value, Constants.ManifestFolder);
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/ConfigurationBuilder.cs b/src/Microsoft.Sbom.Api/Config/ConfigurationBuilder.cs
new file mode 100644
index 00000000..05a4586d
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/ConfigurationBuilder.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using AutoMapper;
+using PowerArgs;
+using System.Threading.Tasks;
+using Microsoft.Sbom.Api.Config.Args;
+using Microsoft.Sbom.Common.Config;
+
+namespace Microsoft.Sbom.Api.Config
+{
+ ///
+ /// Converts the command line arguments and config file parameters to objects.
+ /// Finally combines the two into one object.
+ ///
+ /// Throws an error if the same parameters are defined in both the config file and command line.
+ ///
+ /// The action args parameter.
+ public class ConfigurationBuilder
+ {
+ private readonly IMapper mapper;
+ private readonly ConfigFileParser configFileParser;
+
+ public ConfigurationBuilder(IMapper mapper, ConfigFileParser configFileParser)
+ {
+ this.mapper = mapper;
+ this.configFileParser = configFileParser;
+ }
+
+ public async Task GetConfiguration(T args)
+ {
+ Configuration commandLineArgs;
+
+ // Set current action for the config validators and convert command line arguments to configuration
+ switch (args)
+ {
+ case ValidationArgs validationArgs:
+ validationArgs.ManifestToolAction = ManifestToolActions.Validate;
+ commandLineArgs = mapper.Map(validationArgs);
+ break;
+ case GenerationArgs generationArgs:
+ generationArgs.ManifestToolAction = ManifestToolActions.Generate;
+ commandLineArgs = mapper.Map(generationArgs);
+ break;
+ default:
+ throw new ValidationArgException($"Unsupported configuration type found {typeof(T)}");
+ }
+
+ // Read config file if present, or use default.
+ var configFromFile = commandLineArgs.ConfigFilePath != null ?
+ await configFileParser.ParseFromJsonFile(commandLineArgs.ConfigFilePath.Value) :
+ new ConfigFile();
+
+ // Convert config file arguments to configuration.
+ var configFileArgs = mapper.Map(configFromFile);
+
+ // Combine both configs, include defaults.
+ return mapper.Map(commandLineArgs, configFileArgs);
+ }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/Extensions/ConfigurationExtensions.cs b/src/Microsoft.Sbom.Api/Config/Extensions/ConfigurationExtensions.cs
new file mode 100644
index 00000000..00d35d98
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/Extensions/ConfigurationExtensions.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Sbom.Api.Utils;
+using Microsoft.Sbom.Common.Config;
+using Microsoft.Sbom.Common.Config.Attributes;
+using PowerArgs;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace DropValidator.Api.Config.Extensions
+{
+ ///
+ /// Provides extension methods for an instance of .
+ ///
+ public static class ConfigurationExtensions
+ {
+ ///
+ /// Get the name and value of each IConfiguration property that is annotated with .
+ ///
+ ///
+ ///
+ private static IEnumerable<(string Name, object Value)> GetComponentDetectorArgs(this IConfiguration configuration) => typeof(IConfiguration)
+ .GetProperties()
+ .Where(prop => prop.GetCustomAttributes(typeof(ComponentDetectorArgumentAttribute), true).Any()
+ && prop.PropertyType.GetGenericTypeDefinition() == typeof(ConfigurationSetting<>)
+ && prop.GetValue(configuration) != null)
+ .Select(prop => (prop.Attr().ParameterName, prop.GetValue(configuration)));
+
+ ///
+ /// Adds component detection arguments to the builder.
+ ///
+ ///
+ ///
+ ///
+ private static ComponentDetectionCliArgumentBuilder AddToCommandLineBuilder(this (string Name, object Value) arg, ComponentDetectionCliArgumentBuilder builder) =>
+ !string.IsNullOrWhiteSpace(arg.Name) ? builder.AddArg(arg.Name, arg.Value.ToString()) : builder.ParseAndAddArgs(arg.Value.ToString());
+
+ ///
+ /// Adds command line arguments for all properties annotated with to the current CD CLI arguments builder and returns array of arguments.
+ ///
+ ///
+ ///
+ ///
+ public static string[] ToComponentDetectorCommandLineParams(this IConfiguration configuration, ComponentDetectionCliArgumentBuilder builder)
+ {
+ configuration
+ .GetComponentDetectorArgs()
+ .ForEach(arg => arg.AddToCommandLineBuilder(builder));
+ return builder.Build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Sbom.Api/Config/ManifestToolCmdRunner.cs b/src/Microsoft.Sbom.Api/Config/ManifestToolCmdRunner.cs
new file mode 100644
index 00000000..a5e52e0e
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/ManifestToolCmdRunner.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using AutoMapper;
+using Ninject;
+using PowerArgs;
+using System;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using Microsoft.Sbom.Api.Config.Args;
+using Microsoft.Sbom.Api.Exceptions;
+using Microsoft.Sbom.Api.Output.Telemetry;
+using Microsoft.Sbom.Api.Workflows;
+using Microsoft.Sbom.Common.Config;
+
+namespace Microsoft.Sbom.Api.Config
+{
+ [ArgDescription("The manifest tool validates or generates a manifest for a build artifact.")]
+ [ArgExceptionBehavior(ArgExceptionPolicy.StandardExceptionHandling)]
+ [ArgProductName("ManifestTool.exe")]
+ public class ManifestToolCmdRunner
+ {
+ private readonly StandardKernel kernel;
+
+ public ManifestToolCmdRunner()
+ {
+ IsFailed = false;
+ kernel = new StandardKernel(new Bindings());
+ }
+
+ public ManifestToolCmdRunner(StandardKernel kernel)
+ {
+ IsFailed = false;
+ this.kernel = kernel ?? throw new ArgumentNullException(nameof(kernel));
+ }
+
+ ///
+ /// Gets or sets a value indicating whether displays help info.
+ ///
+ [ArgShortcut("?")]
+ [ArgShortcut("h")]
+ [HelpHook]
+ [JsonIgnore]
+ [ArgDescription("Prints this help message")]
+ public bool Help { get; set; }
+
+ ///
+ /// Gets a value indicating whether if set to true, indicates that there was a problem while parsing the configuration.
+ ///
+ [ArgIgnore]
+ public bool IsFailed { get; private set; }
+
+ ///
+ /// Gets a value indicating whether if set to true, indicates that there was a problem accesing a path specified in the parameters.
+ ///
+ [ArgIgnore]
+ public bool IsAccessError { get; private set; }
+
+ ///
+ /// Validate a build artifact using the manifest. Optionally also verify the signing certificate of the manfiest.
+ ///
+ ///
+ [ArgActionMethod, ArgDescription("Validate a build artifact using the manifest. " +
+ "Optionally also verify the signing certificate of the manfiest.")]
+ public async Task Validate(ValidationArgs validationArgs)
+ {
+ try
+ {
+ var mapper = kernel.Get();
+ var configFileParser = kernel.Get();
+ var configBuilder = new ConfigurationBuilder(mapper, configFileParser);
+
+ kernel.Bind().ToConstant(await configBuilder.GetConfiguration(validationArgs));
+ var result = await kernel.Get(nameof(DropValidatorWorkflow)).RunAsync();
+ await kernel.Get().FinalizeAndLogTelemetryAsync();
+
+ IsFailed = !result;
+ }
+ catch (Exception e)
+ {
+ var message = e.InnerException != null ? e.InnerException.Message : e.Message;
+ Console.WriteLine($"Encountered error while running ManifestTool validation workflow. Error: {message}");
+ IsFailed = true;
+ }
+ }
+
+ ///
+ /// Generate a manifest.json and a bsi.json for all the files in the given build drop folder.
+ ///
+ [ArgActionMethod, ArgDescription("Generate a manifest.json and a bsi.json for all the files " +
+ "in the given build drop folder.")]
+ public async Task Generate(GenerationArgs generationArgs)
+ {
+ try
+ {
+ var mapper = kernel.Get();
+ var configFileParser = kernel.Get();
+ var configBuilder = new ConfigurationBuilder(mapper, configFileParser);
+
+ kernel.Bind().ToConstant(await configBuilder.GetConfiguration(generationArgs));
+
+ var result = await kernel.Get(nameof(SBOMGenerationWorkflow)).RunAsync();
+ await kernel.Get().FinalizeAndLogTelemetryAsync();
+ IsFailed = !result;
+ }
+ catch (AccessDeniedValidationArgException e)
+ {
+ var message = e.InnerException != null ? e.InnerException.Message : e.Message;
+ Console.WriteLine($"Encountered error while running ManifestTool generation workflow. Error: {message}");
+ IsFailed = true;
+ IsAccessError = true;
+ }
+ catch (Exception e)
+ {
+ var message = e.InnerException != null ? e.InnerException.Message : e.Message;
+ Console.WriteLine($"Encountered error while running ManifestTool generation workflow. Error: {message}");
+ IsFailed = true;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Sbom.Api/Config/Validators/ConfigValidator.cs b/src/Microsoft.Sbom.Api/Config/Validators/ConfigValidator.cs
new file mode 100644
index 00000000..4e82b505
--- /dev/null
+++ b/src/Microsoft.Sbom.Api/Config/Validators/ConfigValidator.cs
@@ -0,0 +1,85 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Sbom.Api.Utils;
+using System;
+using System.ComponentModel;
+
+namespace Microsoft.Sbom.Common.Config.Validators
+{
+ ///
+ /// Abstract class from which all validators must inherit.
+ /// This class only validates configuration properties that are of the type
+ ///
+ public abstract class ConfigValidator
+ {
+ private readonly IAssemblyConfig assemblyConfig;
+
+ ///
+ /// This is the attribute that a property must have in order to be validated by this validator.
+ ///
+ private readonly Type supportedAttribute;
+
+ ///
+ /// Gets or sets the current action being performed on the manifest tool.
+ ///
+ public ManifestToolActions CurrentAction { get; set; }
+
+ protected ConfigValidator(Type supportedAttribute, IAssemblyConfig assemblyConfig)
+ {
+ this.supportedAttribute = supportedAttribute ?? throw new ArgumentNullException(nameof(supportedAttribute));
+ this.assemblyConfig = assemblyConfig ?? throw new ArgumentNullException(nameof(assemblyConfig));
+ }
+
+ ///
+ /// Validates a given property, throws a if validation fails.
+ ///
+ /// The name of the property.
+ /// The value of the property.
+ /// The attributes assigned to this property.
+ public void Validate(string propertyName, object propertyValue, AttributeCollection attributeCollection)
+ {
+ if (string.IsNullOrEmpty(propertyName))
+ {
+ throw new ArgumentException($"'{nameof(propertyName)}' cannot be null or empty", nameof(propertyName));
+ }
+
+ Attribute attribute = attributeCollection[supportedAttribute];
+ if (attribute == null)
+ {
+ return;
+ }
+
+ // If default value for namespace base uri is provided in the assembly info, skip value check requirements.
+ if (propertyName == Api.Utils.Constants.NamespaceUriBasePropertyName && !string.IsNullOrEmpty(assemblyConfig.DefaultSBOMNamespaceBaseUri))
+ {
+ return;
+ }
+
+ switch (propertyValue)
+ {
+ case null:
+ // If the value is null, let the implementing validator handle it.
+ ValidateInternal(propertyName, propertyValue, attribute);
+ break;
+
+ case ConfigurationSetting