diff --git a/README.md b/README.md index cd59de2..b9652c7 100755 --- a/README.md +++ b/README.md @@ -200,6 +200,8 @@ Options: --group Provide the group of software the merged BOM describes. --name Provide the name of software the merged BOM describes (required for hierarchical merging). --version Provide the version of software the merged BOM describes (required for hierarchical merging). + --validate-output Perform validation of the resulting document before writing it, and do not write if it fails. + --validate-output-relaxed Perform validation of the resulting document, and still write the file for troubleshooting if it fails. ``` Note: To perform a hierarchical merge all BOMs need the subject of the BOM diff --git a/src/cyclonedx/Commands/MergeCommand.cs b/src/cyclonedx/Commands/MergeCommand.cs index 7a96404..b9eb441 100644 --- a/src/cyclonedx/Commands/MergeCommand.cs +++ b/src/cyclonedx/Commands/MergeCommand.cs @@ -39,6 +39,8 @@ public static void Configure(RootCommand rootCommand) subCommand.Add(new Option("--group", "Provide the group of software the merged BOM describes.")); subCommand.Add(new Option("--name", "Provide the name of software the merged BOM describes (required for hierarchical merging).")); subCommand.Add(new Option("--version", "Provide the version of software the merged BOM describes (required for hierarchical merging).")); + subCommand.Add(new Option("--validate-output", "Perform validation of the resulting document before writing it, and do not write if it fails.")); + subCommand.Add(new Option("--validate-output-relaxed", "Perform validation of the resulting document, and still write the file for troubleshooting if it fails.")); subCommand.Handler = CommandHandler.Create(Merge); rootCommand.Add(subCommand); } @@ -104,13 +106,55 @@ public static async Task Merge(MergeCommandOptions options) outputBom.Version = 1; outputBom.SerialNumber = "urn:uuid:" + System.Guid.NewGuid().ToString(); + ValidationResult validationResult = null; + if (options.ValidateOutput || options.ValidateOutputRelaxed) + { + // Note that C# CLI args parser seems to set both booleans + // for one "--validate-output-relaxed" flag + Console.WriteLine("Validating merged BOM..."); + + // TOTHINK: let it pick versions (no arg) if current does not cut it... + // else SpecificationVersionHelpers.CurrentVersion ? + validationResult = Json.Validator.Validate(Json.Serializer.Serialize(outputBom), outputBom.SpecVersion); + + if (validationResult.Messages != null) + { + foreach (var message in validationResult.Messages) + { + Console.WriteLine(message); + } + } + + if (validationResult.Valid) + { + Console.WriteLine("Merged BOM validated successfully."); + } + else + { + Console.WriteLine("Merged BOM is not valid."); + if (!(options.ValidateOutputRelaxed)) + { + // Not-relaxed mode: abort! + Console.WriteLine("NOT writing output file..."); + Console.WriteLine($" Total {outputBom.Components?.Count ?? 0} components"); + return (int)ExitCode.SignatureFailedVerification; + } + } + } + if (!outputToConsole) { Console.WriteLine("Writing output file..."); Console.WriteLine($" Total {outputBom.Components?.Count ?? 0} components"); } - return await CliUtils.OutputBomHelper(outputBom, options.OutputFormat, options.OutputFile).ConfigureAwait(false); + int res = await CliUtils.OutputBomHelper(outputBom, options.OutputFormat, options.OutputFile).ConfigureAwait(false); + if (validationResult != null && (!validationResult.Valid)) + { + // Relaxed mode: abort after writing the file! + return (int)ExitCode.SignatureFailedVerification; + } + return res; } private static async Task> InputBoms(IEnumerable inputFilenames, CycloneDXBomFormat inputFormat, bool outputToConsole) diff --git a/src/cyclonedx/Commands/MergeCommandOptions.cs b/src/cyclonedx/Commands/MergeCommandOptions.cs index f3078c4..8e586a2 100644 --- a/src/cyclonedx/Commands/MergeCommandOptions.cs +++ b/src/cyclonedx/Commands/MergeCommandOptions.cs @@ -28,5 +28,7 @@ public class MergeCommandOptions public string Group { get; set; } public string Name { get; set; } public string Version { get; set; } + public bool ValidateOutput { get; set; } + public bool ValidateOutputRelaxed { get; set; } } } \ No newline at end of file