diff --git a/cmd/index/bundles/command.go b/cmd/index/bundles/command.go index 80b2d06b..ce024d45 100644 --- a/cmd/index/bundles/command.go +++ b/cmd/index/bundles/command.go @@ -23,13 +23,16 @@ import ( "os" "os/exec" "strings" + "sync" + + alphamodel "github.com/operator-framework/operator-registry/alpha/model" "github.com/operator-framework/audit/pkg/actions" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/spf13/cobra" - // For connecting to query the legacy index database + // To allow create connection to query the index database _ "github.com/mattn/go-sqlite3" "github.com/operator-framework/api/pkg/operators/v1alpha1" log "github.com/sirupsen/logrus" @@ -152,88 +155,109 @@ func removeDuplicates(elements []string) []string { return result } +// Define structured types for warnings and errors +type Warning struct { + OperatorName string + ExecutableName string + Status string + Image string +} + +type Error struct { + OperatorName string + RPMName string + ExecutableName string + Status string + Image string +} + // ExecuteExternalValidator runs the external validator on the provided image reference. -func ExecuteExternalValidator(imageRef string) (bool, []string, []string, error) { +func ExecuteExternalValidator(imageRef string) (bool, []Warning, []Error, error) { extValidatorCmd := "sudo check-payload scan operator --spec " + imageRef + " --log_file=/dev/null --output-format=csv" cmd := exec.Command("bash", "-c", extValidatorCmd) - - // Log the command being executed for debugging purposes log.Infof("Executing external validator with command: %s", extValidatorCmd) - - // Remove the image that check-payload has downloaded using the rmi command - log.Infof("Removing image with command: %s rmi %s", flags.ContainerEngine, imageRef) - rmiCmd := exec.Command(flags.ContainerEngine, "rmi", imageRef) - _, _ = pkg.RunCommand(rmiCmd) - output, err := cmd.CombinedOutput() + if err != nil { - return false, nil, nil, err + log.Infof("command failed: %v, output: %s", err, string(output)) } lines := strings.Split(string(output), "\n") - processingMode := "" // can be "warning", "error", or empty - hasReports := false + var warnings []Warning + var errors []Error + inFailureReport := false + inWarningReport := false - var warnings, errors []string for _, line := range lines { - if line == "---- Warning Report" { - processingMode = "warning" - hasReports = true + log.Infof("External validator line: %s", line) + + switch { + case line == "---- Failure Report": + inFailureReport = true continue - } else if line == "---- Error Report" { - processingMode = "error" - hasReports = true + case line == "---- Warning Report": + inWarningReport = true continue - } else if strings.HasPrefix(line, "Operator Name,Executable Name,Status,Image") { + case line == "---- Successful run" || line == "": + inFailureReport = false + inWarningReport = false continue + case inFailureReport: + parseFailureReportLine(line, &errors) + case inWarningReport: + parseWarningReportLine(line, &warnings) } + } - if processingMode == "" { - continue - } + success := len(errors) == 0 + return success, warnings, errors, nil +} - columns := strings.Split(line, ",") - if len(columns) < 4 { - continue - } - operatorName, executableName, status, image := columns[0], columns[1], columns[2], columns[3] - if processingMode == "warning" { - warnings = append(warnings, fmt.Sprintf("Warning for Operator '%s', Executable '%s': %s (Image: %s)", - operatorName, executableName, status, image)) - } else if processingMode == "error" { - errors = append(errors, fmt.Sprintf("Error for Operator '%s', Executable '%s': %s (Image: %s)", - operatorName, executableName, status, image)) - } +func parseFailureReportLine(line string, errors *[]Error) { + columns := strings.Split(line, ",") + if len(columns) >= 5 { + operatorName, rpmName, executableName, status, image := columns[0], columns[1], columns[2], columns[3], columns[4] + *errors = append(*errors, Error{ + OperatorName: strings.TrimSpace(operatorName), + RPMName: strings.TrimSpace(rpmName), + ExecutableName: strings.TrimSpace(executableName), + Status: strings.TrimSpace(status), + Image: strings.TrimSpace(image), + }) } +} - if !hasReports { - successMessage := fmt.Sprintf("FIPS compliance check passed successfully for image: %s", imageRef) - warnings = append(warnings, successMessage) // or choose a different way to report this success +func parseWarningReportLine(line string, warnings *[]Warning) { + columns := strings.Split(line, ",") + if len(columns) >= 4 { + operatorName, executableName, status, image := columns[0], columns[1], columns[2], columns[3] + *warnings = append(*warnings, Warning{ + OperatorName: strings.TrimSpace(operatorName), + ExecutableName: strings.TrimSpace(executableName), + Status: strings.TrimSpace(status), + Image: strings.TrimSpace(image), + }) } - - return true, warnings, errors, nil } // ProcessValidatorResults takes the results from the external validator and appends them to the report data. -func ProcessValidatorResults(success bool, warnings, errors []string, report *index.Data) { - // Create a slice to hold combined errors and warnings - combinedErrors := make([]string, 0) +func ProcessValidatorResults(success bool, warnings []Warning, errors []Error, auditBundle *models.AuditBundle) { + var combinedErrors []string - // If the external validator fails, append the errors if !success { - combinedErrors = append(combinedErrors, errors...) + for _, err := range errors { + combinedErrors = append(combinedErrors, fmt.Sprintf("ERROR for Operator '%s', Executable '%s': %s (Image: %s)", + err.OperatorName, err.ExecutableName, err.Status, err.Image)) + } } - // Prepend warnings with "WARNING:" and append to combinedErrors for _, warning := range warnings { - combinedErrors = append(combinedErrors, "WARNING: "+warning) + combinedErrors = append(combinedErrors, fmt.Sprintf("WARNING for Operator '%s', Executable '%s': %s (Image: %s)", + warning.OperatorName, warning.ExecutableName, warning.Status, warning.Image)) } - // Assuming there's a mechanism to identify which bundle is being processed - // Here, I'm just using the last bundle in the report as an example - if len(report.AuditBundle) > 0 { - report.AuditBundle[len(report.AuditBundle)-1].Errors = combinedErrors - } + log.Infof("Adding FIPS check info to auditBundle with %s", combinedErrors) + auditBundle.Errors = append(auditBundle.Errors, combinedErrors...) } func validation(cmd *cobra.Command, args []string) error { @@ -317,12 +341,13 @@ func run(cmd *cobra.Command, args []string) error { if err := reportData.OutputReport(); err != nil { return err } + pkg.CleanupTemporaryDirs() log.Info("Operation completed.") return nil } -func handleFIPS(operatorBundlePath string, csv *v1alpha1.ClusterServiceVersion, reportData index.Data) error { +func handleFIPS(operatorBundlePath string, csv *v1alpha1.ClusterServiceVersion, auditBundle *models.AuditBundle) error { isClaimingFIPSCompliant, err := CheckFIPSAnnotations(csv) if err != nil { return err @@ -338,11 +363,11 @@ func handleFIPS(operatorBundlePath string, csv *v1alpha1.ClusterServiceVersion, for _, imageRef := range uniqueImageRefs { success, warnings, errors, err := ExecuteExternalValidator(imageRef) if err != nil { - log.Errorf("Error while executing FIPS compliance check on image: %s. Error: %s", - imageRef, err.Error()) - return err + log.Errorf("Error while executing FIPS compliance check on image: %s. Error: %s", imageRef, err.Error()) + continue } - ProcessValidatorResults(success, warnings, errors, &reportData) + log.Infof("Processing FIPS check results on image: %s.", imageRef) + ProcessValidatorResults(success, warnings, errors, auditBundle) } return nil } @@ -373,46 +398,102 @@ func GetDataFromFBC(report index.Data) (index.Data, error) { return report, fmt.Errorf("unable to load the file based config : %s", err) } model, err := declcfg.ConvertToModel(*fbc) - var auditBundle *models.AuditBundle if err != nil { return report, fmt.Errorf("unable to file based config to internal model: %s", err) } - // iterate the model by bundle to match up with how code in getDataFromIndexDB() does it - for packageName, Package := range model { - for _, Channel := range Package.Channels { - for _, Bundle := range Channel.Bundles { - log.Infof("Generating data from the bundle (%s)", Bundle.Name) - auditBundle = models.NewAuditBundle(Bundle.Name, Bundle.Image) + + const maxConcurrency = 4 + packageChan := make(chan *alphamodel.Package, maxConcurrency) + resultsChan := make(chan *index.Data, maxConcurrency) + var wg sync.WaitGroup + + // Start worker goroutines + for i := 0; i < maxConcurrency; i++ { + wg.Add(1) + go packageWorker(packageChan, resultsChan, &wg) + } + + // Send packages to the workers + go func() { + for _, Package := range model { + packageChan <- Package + } + close(packageChan) + }() + + // Close the results channel when all workers are done + go func() { + wg.Wait() + close(resultsChan) + }() + + // Collect results + for result := range resultsChan { + report.AuditBundle = append(report.AuditBundle, result.AuditBundle...) + } + + return report, nil +} + +func packageWorker(packageChan <-chan *alphamodel.Package, resultsChan chan<- *index.Data, wg *sync.WaitGroup) { + defer wg.Done() + for Package := range packageChan { + // Initialize a local variable to store results for this package + var result index.Data + + // Iterate over the channels in the package + for _, channel := range Package.Channels { + headBundle, err := channel.Head() + if err != nil { + continue + } + + for _, bundle := range channel.Bundles { + auditBundle := models.NewAuditBundle(bundle.Name, bundle.Image) + if headBundle == bundle { + auditBundle.IsHeadOfChannel = true + } else { + if flags.HeadOnly { + continue + } + } + + log.Infof("Generating data from the bundle (%s)", bundle.Name) var csv *v1alpha1.ClusterServiceVersion - err := json.Unmarshal([]byte(Bundle.CsvJSON), &csv) + err := json.Unmarshal([]byte(bundle.CsvJSON), &csv) if err == nil { auditBundle.CSVFromIndexDB = csv } else { auditBundle.Errors = append(auditBundle.Errors, fmt.Errorf("unable to parse the csv from the index.db: %s", err).Error()) } - auditBundle = actions.GetDataFromBundleImage(auditBundle, report.Flags.DisableScorecard, - report.Flags.DisableValidators, report.Flags.ServerMode, report.Flags.Label, - report.Flags.LabelValue, flags.ContainerEngine, report.Flags.IndexImage) - // an extra inner loop is needed because of the way the model is set up vs. how the report is generated - for _, Channel := range Package.Channels { - auditBundle.Channels = append(auditBundle.Channels, Channel.Name) + + // Call GetDataFromBundleImage + auditBundle = actions.GetDataFromBundleImage(auditBundle, flags.DisableScorecard, + flags.DisableValidators, flags.ServerMode, flags.Label, + flags.LabelValue, flags.ContainerEngine, flags.IndexImage) + + // Extra inner loop for channels + for _, channel := range Package.Channels { + auditBundle.Channels = append(auditBundle.Channels, channel.Name) } - auditBundle.PackageName = packageName + + auditBundle.PackageName = Package.Name auditBundle.DefaultChannel = Package.DefaultChannel.Name - // this collects olm.bundle.objects not found in the index version and this seems correct - for _, property := range Bundle.Properties { + + // Collect properties not found in the index version + for _, property := range bundle.Properties { auditBundle.PropertiesDB = append(auditBundle.PropertiesDB, pkg.PropertiesAnnotation{Type: property.Type, Value: string(property.Value)}) } - headBundle, err := Channel.Head() + headBundle, err := channel.Head() if err == nil { - if headBundle == Bundle { + if headBundle == bundle { auditBundle.IsHeadOfChannel = true } } if flags.StaticCheckFIPSCompliance { - err = handleFIPS(auditBundle.OperatorBundleImagePath, csv, report) + err = handleFIPS(auditBundle.OperatorBundleImagePath, csv, auditBundle) if err != nil { // Check for specific error types and provide more informative messages if exitError, ok := err.(*exec.ExitError); ok { @@ -429,11 +510,13 @@ func GetDataFromFBC(report index.Data) (index.Data, error) { } } } - report.AuditBundle = append(report.AuditBundle, *auditBundle) + result.AuditBundle = append(result.AuditBundle, *auditBundle) } } + + // Send the result to the results channel + resultsChan <- &result } - return report, nil } func GetDataFromIndexDB(report index.Data) (index.Data, error) { diff --git a/DEV_GUIDE_ACTIONS.md b/docs/dev/DEV_GUIDE_ACTIONS.md similarity index 100% rename from DEV_GUIDE_ACTIONS.md rename to docs/dev/DEV_GUIDE_ACTIONS.md diff --git a/DEV_GUIDE_ADDING_CMDS.md b/docs/dev/DEV_GUIDE_ADDING_CMDS.md similarity index 100% rename from DEV_GUIDE_ADDING_CMDS.md rename to docs/dev/DEV_GUIDE_ADDING_CMDS.md diff --git a/DEV_GUIDE_CUSTOM.md b/docs/dev/DEV_GUIDE_CUSTOM.md similarity index 100% rename from DEV_GUIDE_CUSTOM.md rename to docs/dev/DEV_GUIDE_CUSTOM.md diff --git a/DEV_GUIDE_EXT_TOOLS.md b/docs/dev/DEV_GUIDE_EXT_TOOLS.md similarity index 100% rename from DEV_GUIDE_EXT_TOOLS.md rename to docs/dev/DEV_GUIDE_EXT_TOOLS.md diff --git a/DEV_GUIDE_MODELS.md b/docs/dev/DEV_GUIDE_MODELS.md similarity index 100% rename from DEV_GUIDE_MODELS.md rename to docs/dev/DEV_GUIDE_MODELS.md diff --git a/DEV_GUIDE_VALIDATION.md b/docs/dev/DEV_GUIDE_VALIDATION.md similarity index 100% rename from DEV_GUIDE_VALIDATION.md rename to docs/dev/DEV_GUIDE_VALIDATION.md diff --git a/go.mod b/go.mod index da7d6a97..a3c814b7 100644 --- a/go.mod +++ b/go.mod @@ -86,12 +86,14 @@ require ( go.opentelemetry.io/otel/sdk v1.10.0 // indirect go.opentelemetry.io/otel/trace v1.10.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect - golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.18.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/term v0.3.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/term v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.15.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect diff --git a/go.sum b/go.sum index 16d35c3f..4a71e8bf 100644 --- a/go.sum +++ b/go.sum @@ -864,6 +864,7 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -897,6 +898,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -952,6 +955,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc= golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1054,12 +1059,16 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1071,6 +1080,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1135,6 +1146,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/hack/check-license.sh b/hack/check-license.sh old mode 100644 new mode 100755 diff --git a/hack/fips/process_report.py b/hack/fips/process_report.py new file mode 100755 index 00000000..a8935688 --- /dev/null +++ b/hack/fips/process_report.py @@ -0,0 +1,207 @@ +import json +import sys +import argparse +from datetime import datetime + + +def summarize_json(file_path): + """Read and return JSON data from the given file.""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except Exception as e: + print(f"An error occurred: {e}") + sys.exit(1) + + +def parse_arguments(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser(description='Process report and generate HTML view.') + parser.add_argument('file_path', help='Path to the JSON file to process') + parser.add_argument('--fips', action='store_true', help='Filter to show only FIPS-compliant packages') + return parser.parse_args() + + +def is_fips_compliant(entry): + """Check if the entry is FIPS-compliant based on its annotations.""" + annotations = entry.get('csv', {}).get('metadata', {}).get('annotations', {}) + fips_compliant = annotations.get('features.operators.openshift.io/fips-compliant') == 'true' + infrastructure_features = annotations.get('operators.openshift.io/infrastructure-features', "") + fips_in_infrastructure = '"fips"' in infrastructure_features + return fips_compliant or fips_in_infrastructure + + +def reorganize_data_by_package_name(data, fips_only): + """Reorganize the 'Columns' data to group by 'packageName' and label with 'csv > metadata > name'.""" + grouped_data = {} + + for entry in data.get('Columns', []): + if not fips_only or is_fips_compliant(entry): + package_name = entry.get('packageName') + bundle_name = entry.get('csv', {}).get('metadata', {}).get('name', 'Unknown') + + if package_name not in grouped_data: + grouped_data[package_name] = {} + + grouped_data[package_name][bundle_name] = entry + + return grouped_data + + +def format_date(date_str): + """Format the date from 'YYYY-MM-DD' to 'Month DD, YYYY'.""" + try: + return datetime.strptime(date_str, '%Y-%m-%d').strftime('%b. %d, %Y') + except ValueError: + return 'Unknown Date' + + +def generate_report_note(args): + """Generate a note for the report based on command-line arguments.""" + notes = [] + if args.fips: + notes.append("filtered for claimed FIPS Compliance") + # Additional flags can be handled here + return " -- " + ", ".join(notes) if notes else "" + + +def determine_color(errors_warnings): + """Determine the color based on error and warning content.""" + has_error = any("ERROR" in msg for msg in errors_warnings.get('errors', [])) + has_warning = any("Warning" in msg for msg in errors_warnings.get('warnings', [])) + + if has_error: + return "lightcoral" # Light red for errors + elif has_warning: + return "lightyellow" # Light orange for warnings + return "lightgreen" # Light green when no errors or warnings + + +def apply_color_to_data(data): + """Apply color highlighting to the data based on errors and warnings.""" + for package_name, package_data in data.items(): + package_errors_warnings = { + 'errors': [], + 'warnings': [] + } + for bundle_name, bundle_data in package_data.items(): + errors_warnings = bundle_data.get('errors', []) + bundle_data.get('warnings', []) + color = determine_color({'errors': errors_warnings}) + bundle_data['color'] = color # Apply color to the bundle + package_errors_warnings['errors'].extend(bundle_data.get('errors', [])) + package_errors_warnings['warnings'].extend(bundle_data.get('warnings', [])) + + # Determine and apply color to the package + package_color = determine_color(package_errors_warnings) + package_data['color'] = package_color + + +def json_to_html_report(flags, data, generated_at, report_note): + """Convert JSON data to an HTML report.""" + apply_color_to_data(data) # Apply color before generating the HTML + formatted_date = format_date(generated_at) + flags_rows = ''.join(f"
Note: "errors" in the below report refers to static check-payload found FIPS errors. Packages with any bundle having these errors are shown with red below.
+