From 0d69a45b6c8001a3923b9a66d5cfd7fcce3e1037 Mon Sep 17 00:00:00 2001 From: Andy Mills <61879371+CloudBeard@users.noreply.github.com> Date: Fri, 28 Jun 2024 18:03:24 -0400 Subject: [PATCH] feat(tools): add looping for lint (#481) * feat: add looping for lint * updated to handle errors without exiting. * added conditional for success/fail * need a wrapper in go-oscal but should work * feat(validate)!: #408 create resources in kubernetes domain (#415) * refactor!(common): #388 update common/types (and all references) to use pointers * refactor!(common): #388 update Domain struct to use pointers * refactor!(common): #388 update Provider struct to use pointers * refactor!(evaluate): #388 EvaluateResults now uses pointers refactor!(component): change NewOscalComponentDefinitionFromBytes to return a pointer * refactor!(resource-store): #388 changed AddFromLink params to use pointers, updated references * refactor!(assessment-results): #388 NewAssessmentResults now returns a pointer * refactor!(catalog): now runs validation rather than requiring the source for the extension refactor!(catalog): now uses pointers * refactor!(component): #388 NewOscalComponentDefinition no longer uses source in favor of oscal validation refactor!(component): delete the NewOscalComponentDefinitionFromBytes method in favor of NewOscalComponentDefinition refactor!(common): rename WriteFile -> WriteOscalModel refactor(generate): WriteFile -> WriteOscalModel refactor(composition): removed validation logic in favor of NewOscalCOmponentDefinition handling it feat(common): WriteOscalModel now handles json file extensions feat(evaluate): now runs file extension validation for json/yaml feat(validate): now checks input file for extension tests: update tests, update test data to pass oscal validation * refactor!(common): #388 moved WriteOscalModel to the oscal package (complete-schema) * feat(oscal): create the multiModelValidate method for use in oscal constructors and updated all relavant constructors * refactor!(component): #388 update mergeComponents and ComponentFromCatalog to use pointers refactor(generate): update all refs to ComponentFromCatalog * initial resource creation * refactor(component): pointer refactor * fix(component): failing e2e * refactor(component): ControlToImplementRequirement now takes pointer to control * fix(common): add omitempty to the marshalling for Validation * refactor!(domains/kubernetes): #388 KubernetesSpec.Wait and Resource.ResourceRule are now pointers and have omitempty, updated all references * refactor!(domains/kubernetes): #388 ResourceRule.Field is now a pointer and has omitempty tag * refactor!(providers/kyverno): #388 KyvernoSpec is now a pointer * refactor!(providers/opa): #388 OpaSpec is now a pointer * refactor(providers): add omitempty to Kyverno and opa spec tags * refactor!(providers): update output fields to be pointers * refactor!(domains): update api and k8s domain specs to be pointers * refactor!(types): #388 update LulaValidation provider, domain, domain-resources and result to use pointers * fix(evaluate): add nil check on thresholdResult and newResult to top of method to prevent nil pointer * chore(validate): add TODO to remove WriteReport as it is unused (question) * fix: graceful exit conditions, empty result pointer * feat: merged pointers * feat: updated error handling, initial docs and flag functions * feat: added flags to lula validate * feat: added requirement-store, updated validation * feat(validate): updated validation refactor, other small fixes * feat: added tests, dev command updates * fix(dev): updated tests, fixed dev cmd issues * fix: refactor yaml multi doc functionality * fix: get-resources read from stdin * fix: remove debug file * fix: clean-up per comments * Update src/pkg/message/interactive.go clarifying confirmation text Co-authored-by: Brandt Keller <43887158+brandtkeller@users.noreply.github.com> * docs: description of assessments and failure conditions * feat(validate): non-interactive flag added * fix(dev): get-resources missing validation opt * fix: clean-up create resources * add staticResources check before execution * fix: updated create observation fcn and usage --------- Co-authored-by: Cole (Mike) Winberry Co-authored-by: Cole (Mike) Winberry <86802655+mike-winberry@users.noreply.github.com> Co-authored-by: Brandt Keller <43887158+brandtkeller@users.noreply.github.com> * chore(deps): update module github.com/spf13/cobra to v1.8.1 (#485) | datasource | package | from | to | | ---------- | ---------------------- | ------ | ------ | | go | github.com/spf13/cobra | v1.8.0 | v1.8.1 | Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update googleapis/release-please-action digest to 7987652 (#472) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update github/codeql-action action to v3.25.10 (#476) | datasource | package | from | to | | ----------- | -------------------- | ------- | -------- | | github-tags | github/codeql-action | v3.25.8 | v3.25.10 | Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore: fix documentation links (#487) * chore(docs): cleanup unused readme conflicting in docs build (#489) * chore(deps): update module github.com/defenseunicorns/go-oscal to v0.4.3 (#470) | datasource | package | from | to | | ---------- | ----------------------------------- | ------ | ------ | | go | github.com/defenseunicorns/go-oscal | v0.4.1 | v0.4.3 | Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update actions/checkout action to v4.1.7 (#479) | datasource | package | from | to | | ----------- | ---------------- | ------ | ------ | | github-tags | actions/checkout | v4.1.6 | v4.1.7 | Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update module github.com/defenseunicorns/go-oscal to v0.5.0 (#492) * chore(deps): update module github.com/defenseunicorns/go-oscal to v0.5.0 | datasource | package | from | to | | ---------- | ----------------------------------- | ------ | ------ | | go | github.com/defenseunicorns/go-oscal | v0.4.3 | v0.5.0 | * chore(lint): update lint with new go-oscal ValidationCommand changes * chore(tests): update pod_validation_test with new usage of ValidationCommand * chore(tests): update e2e pod_validation_test to use JsonSchemaError for validation failures --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Cole (Mike) Winberry * added conditional for success/fail * need a wrapper in go-oscal but should work * updating loop * fixing local branch * fixed my loop logic * fix(lint): lint now waits until exiting the loop to write to file and now handles multiple validation results * fix(tools): lint command properly handles attempting all validations prior to deciding exit state, still bails on non-validation errors as they happen * fix(tools): lint fatal error message validation -> linting * fix(tools): lint messaging updated with linting where it makes sense --------- Co-authored-by: Megan Wolf <97549300+meganwolf0@users.noreply.github.com> Co-authored-by: Cole (Mike) Winberry Co-authored-by: Cole (Mike) Winberry <86802655+mike-winberry@users.noreply.github.com> Co-authored-by: Brandt Keller <43887158+brandtkeller@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Bryan Finster --- src/cmd/tools/lint.go | 85 ++++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 25 deletions(-) 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") }