diff --git a/src/cmd/tools/lint.go b/src/cmd/tools/lint.go index e1dd52e3..5f40ef0a 100644 --- a/src/cmd/tools/lint.go +++ b/src/cmd/tools/lint.go @@ -2,6 +2,8 @@ package tools import ( "encoding/json" + "fmt" + "strings" "github.com/defenseunicorns/go-oscal/src/pkg/validation" "github.com/defenseunicorns/lula/src/config" @@ -10,15 +12,15 @@ import ( ) type flags struct { - InputFile string // -f --input-file - ResultFile string // -r --result-file + InputFiles []string // -f --input-files + ResultFile string // -r --result-file } var opts = &flags{} var lintHelp = ` -To lint an existing OSCAL file: - lula tools lint -f +To lint existing OSCAL files: + lula tools lint -f ,, ` func init() { @@ -28,42 +30,75 @@ func init() { PersistentPreRun: func(cmd *cobra.Command, args []string) { config.SkipLogFile = true }, - Long: "Validate an OSCAL document is properly configured against the OSCAL schema", + Long: "Validate OSCAL documents are properly configured against the OSCAL schema", Example: lintHelp, Run: func(cmd *cobra.Command, args []string) { - spinner := message.NewProgressSpinner("Linting %s", opts.InputFile) - defer spinner.Stop() - - validationResp, err := validation.ValidationCommand(opts.InputFile) - // fatal for non-validation errors - if err != nil { - message.Fatalf(err, "Failed to lint %s: %s", opts.InputFile, err) + var validationResults []validation.ValidationResult + if len(opts.InputFiles) == 0 { + message.Fatalf(nil, "No input files specified") } - for _, warning := range validationResp.Warnings { - message.Warn(warning) + for _, inputFile := range opts.InputFiles { + spinner := message.NewProgressSpinner("Linting %s", inputFile) + defer spinner.Stop() + + validationResp, err := validation.ValidationCommand(inputFile) + // fatal for non-validation errors + if err != nil { + message.Fatalf(err, "Failed to lint %s: %s", inputFile, err) + } + + for _, warning := range validationResp.Warnings { + message.Warn(warning) + } + + // append the validation result to the results array + validationResults = append(validationResults, validationResp.Result) + + // If result file is not specified, print the validation result + if opts.ResultFile == "" { + jsonBytes, err := json.MarshalIndent(validationResp.Result, "", " ") + if err != nil { + message.Fatalf(err, "Failed to marshal validation result") + } + message.Infof("Validation result for %s: %s", inputFile, string(jsonBytes)) + } + // New conditional for logging success or failed linting + if validationResp.Result.Valid { + message.Infof("Successfully validated %s is valid OSCAL version %s %s\n", inputFile, validationResp.Validator.GetSchemaVersion(), validationResp.Validator.GetModelType()) + spinner.Success() + } else { + message.WarnErrf(nil, "Failed to lint %s", inputFile) + spinner.Stop() + } } + // If result file is specified, write the validation results to the file if opts.ResultFile != "" { - validation.WriteValidationResult(validationResp.Result, opts.ResultFile) - } else { - jsonBytes, err := json.MarshalIndent(validationResp.Result, "", " ") - if err != nil { - message.Fatalf(err, "Failed to marshal validation result") + // If there is only one validation result, write it to the file + if len(validationResults) == 1 { + validation.WriteValidationResult(validationResults[0], opts.ResultFile) + } else { + // If there are multiple validation results, write them to the file + validation.WriteValidationResults(validationResults, opts.ResultFile) } - message.Infof("Validation result: %s", string(jsonBytes)) } - if validationResp.JsonSchemaError != nil { - message.Fatalf(err, "Failed to lint %s", opts.InputFile) + // If there is at least one validation result that is not valid, exit with a fatal error + failedFiles := []string{} + for _, result := range validationResults { + if !result.Valid { + failedFiles = append(failedFiles, result.Metadata.DocumentPath) + } + } + if len(failedFiles) > 0 { + message.Fatal(nil, fmt.Sprintf("The following files failed linting: %s", strings.Join(failedFiles, ", "))) } - message.Infof("Successfully validated %s is valid OSCAL version %s %s\n", opts.InputFile, validationResp.Validator.GetSchemaVersion(), validationResp.Validator.GetModelType()) - spinner.Success() }, } toolsCmd.AddCommand(lintCmd) - lintCmd.Flags().StringVarP(&opts.InputFile, "input-file", "f", "", "the path to a oscal json schema file") + lintCmd.Flags().StringSliceVarP(&opts.InputFiles, "input-files", "f", []string{}, "the paths to oscal json schema files (comma-separated)") lintCmd.Flags().StringVarP(&opts.ResultFile, "result-file", "r", "", "the path to write the validation result") }