Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cmd/certsuite/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ var (
}
)

// NewCommand Creates a check command that aggregates image certification and result verification actions
//
// This function builds a new Cobra command for the tool’s check
// functionality. It registers two child commands: one to verify image
// certificates and another to validate test results against expected outputs or
// logs. The resulting command is returned for inclusion in the main CLI
// hierarchy.
func NewCommand() *cobra.Command {
checkCmd.AddCommand(imagecert.NewCommand())
checkCmd.AddCommand(results.NewCommand())
Expand Down
15 changes: 15 additions & 0 deletions cmd/certsuite/check/image_cert_status/image_cert_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ var checkImageCertStatusCmd = &cobra.Command{
RunE: checkImageCertStatus,
}

// checkImageCertStatus checks whether a container image is certified
//
// The function reads command-line flags for an image name, registry, tag, or
// digest, then uses a validator from the certdb package to determine
// certification status. It prints formatted information about the selected
// image and outputs a colored result indicating success or failure. Errors are
// returned if required parameters are missing or if the validator cannot be
// obtained.
func checkImageCertStatus(cmd *cobra.Command, _ []string) error {
imageName, _ := cmd.Flags().GetString("name")
imageRegistry, _ := cmd.Flags().GetString("registry")
Expand Down Expand Up @@ -64,6 +72,13 @@ func checkImageCertStatus(cmd *cobra.Command, _ []string) error {
return nil
}

// NewCommand configures and returns the image certificate status command
//
// This function sets up persistent flags for specifying an image name,
// registry, tag, digest, and an optional offline database path. It enforces
// that a name and registry must be provided together while ensuring the name
// and digest cannot both be set at once. Finally, it returns the fully
// configured command object.
func NewCommand() *cobra.Command {
checkImageCertStatusCmd.PersistentFlags().String("name", "", "name of the image to verify")
checkImageCertStatusCmd.PersistentFlags().String("registry", "", "registry where the image is stored")
Expand Down
57 changes: 57 additions & 0 deletions cmd/certsuite/check/results/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,25 @@ const (
resultMiss = "MISSING"
)

// TestCaseList Stores the names of test cases categorized by outcome
//
// This structure keeps three slices, each holding strings that represent test
// case identifiers. The Pass slice lists all tests that succeeded, Fail
// contains those that failed, and Skip holds tests that were not executed. It
// is used to report results in a concise format.
type TestCaseList struct {
Pass []string `yaml:"pass"`
Fail []string `yaml:"fail"`
Skip []string `yaml:"skip"`
}

// TestResults Holds a collection of test case results
//
// This structure contains a slice of individual test case outcomes, allowing
// the program to group related results together. The embedded field
// automatically inherits all fields and methods from the underlying test case
// list type, enabling direct access to the collection’s elements. It serves
// as a container for serializing or reporting aggregated test data.
type TestResults struct {
TestCaseList `yaml:"testCases"`
}
Expand All @@ -42,6 +55,14 @@ var checkResultsCmd = &cobra.Command{
RunE: checkResults,
}

// checkResults compares recorded test outcomes against a reference template
//
// The function reads actual test results from a log file, optionally generates
// a YAML template of those results, or loads expected results from an existing
// template. It then checks each test case for mismatches between actual and
// expected values, reporting any discrepancies in a formatted table and
// terminating the program if differences are found. If all results match, it
// prints a success message.
func checkResults(cmd *cobra.Command, _ []string) error {
templateFileName, _ := cmd.Flags().GetString("template")
generateTemplate, _ := cmd.Flags().GetBool("generate-template")
Expand Down Expand Up @@ -90,6 +111,13 @@ func checkResults(cmd *cobra.Command, _ []string) error {
return nil
}

// getTestResultsDB Parses a log file to build a test result map
//
// The function opens the specified log file, reads it line by line, and
// extracts test case names and their recorded results using a regular
// expression. Each matched pair is stored in a map where the key is the test
// case name and the value is its result string. It returns this map along with
// an error if any step fails.
func getTestResultsDB(logFileName string) (map[string]string, error) {
resultsDB := make(map[string]string)

Expand Down Expand Up @@ -124,6 +152,13 @@ func getTestResultsDB(logFileName string) (map[string]string, error) {
return resultsDB, nil
}

// getExpectedTestResults loads expected test outcomes from a YAML template
//
// The function reads a specified file, decodes its YAML content into a
// structured list of test cases classified as pass, skip, or fail, then builds
// a map associating each case with the corresponding result string. It returns
// this map along with any error that occurs during file reading or
// unmarshalling.
func getExpectedTestResults(templateFileName string) (map[string]string, error) {
templateFile, err := os.ReadFile(templateFileName)
if err != nil {
Expand All @@ -150,6 +185,14 @@ func getExpectedTestResults(templateFileName string) (map[string]string, error)
return expectedTestResults, nil
}

// printTestResultsMismatch Displays a formatted table of test cases that did not match the expected results
//
// The function receives a list of mismatched test case identifiers along with
// maps of actual and expected outcomes. It prints a header, then iterates over
// each mismatched case, retrieving the corresponding expected and actual
// values—using a placeholder when either is missing—and outputs them in
// aligned columns. Finally, it draws separators to delineate each row for
// readability.
func printTestResultsMismatch(mismatchedTestCases []string, actualResults, expectedResults map[string]string) {
fmt.Printf("\n")
fmt.Println(strings.Repeat("-", 96)) //nolint:mnd // table line
Expand All @@ -169,6 +212,14 @@ func printTestResultsMismatch(mismatchedTestCases []string, actualResults, expec
}
}

// generateTemplateFile Creates a YAML template file summarizing test case outcomes
//
// This function takes a map of test cases to result strings and builds a
// structured template containing lists for passed, skipped, and failed tests.
// It encodes the structure into YAML with two-space indentation and writes it
// to a predefined file path with specific permissions. If an unknown result
// value is encountered or any I/O operation fails, it returns an error
// detailing the issue.
func generateTemplateFile(resultsDB map[string]string) error {
var resultsTemplate TestResults
for testCase, result := range resultsDB {
Expand Down Expand Up @@ -201,6 +252,12 @@ func generateTemplateFile(resultsDB map[string]string) error {
return nil
}

// NewCommand Creates a command for checking test results against expected templates
//
// It defines persistent flags for specifying the template file, log file, and
// an option to generate a new template from logs. The flags are mutually
// exclusive to avoid conflicting inputs. Finally, it returns the configured
// command instance.
func NewCommand() *cobra.Command {
checkResultsCmd.PersistentFlags().String("template", "expected_results.yaml", "reference YAML template with the expected results")
checkResultsCmd.PersistentFlags().String("log-file", "certsuite.log", "log file of the Certsuite execution")
Expand Down
6 changes: 6 additions & 0 deletions cmd/certsuite/claim/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ var (
}
)

// NewCommand Creates a subcommand for claim operations
//
// It initializes the claim command by attaching its compare and show
// subcommands, each of which provides functionality for comparing claim files
// or displaying claim information. The function returns the configured
// cobra.Command ready to be added to the main application root command.
func NewCommand() *cobra.Command {
claimCommand.AddCommand(compare.NewCommand())
claimCommand.AddCommand(show.NewCommand())
Expand Down
26 changes: 26 additions & 0 deletions cmd/certsuite/claim/compare/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ var (
}
)

// NewCommand Creates a command for comparing two claim files
//
// It defines flags for the paths of two existing claim files, marks both as
// required, and handles errors by logging them before returning nil if marking
// fails. The function then returns the configured command object for use in the
// CLI.
func NewCommand() *cobra.Command {
claimCompareFiles.Flags().StringVarP(
&Claim1FilePathFlag, "claim1", "1", "",
Expand All @@ -109,6 +115,13 @@ func NewCommand() *cobra.Command {
return claimCompareFiles
}

// claimCompare compares two claim files for differences
//
// This function reads the paths provided by global flags, loads each file,
// unmarshals them into claim structures, and then generates diff reports for
// versions, test cases, configurations, and nodes. The resulting diffs are
// printed to standard output. If any step fails, it logs a fatal error and
// exits.
func claimCompare(_ *cobra.Command, _ []string) error {
err := claimCompareFilesfunc(Claim1FilePathFlag, Claim2FilePathFlag)
if err != nil {
Expand All @@ -117,6 +130,13 @@ func claimCompare(_ *cobra.Command, _ []string) error {
return nil
}

// claimCompareFilesfunc Reads two claim files, unmarshals them, and outputs structured comparison reports
//
// The function loads the contents of two specified claim files and parses each
// JSON document into a claim schema structure. It then generates separate diff
// reports for the claim versions, test case results, configuration differences,
// and node details, printing each report to standard output. Errors during file
// reading or unmarshalling are wrapped with context and returned for handling.
func claimCompareFilesfunc(claim1, claim2 string) error {
// readfiles
claimdata1, err := os.ReadFile(claim1)
Expand Down Expand Up @@ -161,6 +181,12 @@ func claimCompareFilesfunc(claim1, claim2 string) error {
return nil
}

// unmarshalClaimFile Parses raw claim data into a structured schema
//
// This function receives raw JSON bytes representing a claim file, attempts to
// unmarshal them into the claim.Schema type, and returns either the populated
// struct or an error if parsing fails. It uses standard library JSON decoding
// and propagates any unmarshaling errors back to the caller.
func unmarshalClaimFile(claimdata []byte) (claim.Schema, error) {
var claimDataResult claim.Schema
err := json.Unmarshal(claimdata, &claimDataResult)
Expand Down
34 changes: 34 additions & 0 deletions cmd/certsuite/claim/compare/configurations/configurations.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,24 @@ import (
"github.com/redhat-best-practices-for-k8s/certsuite/cmd/certsuite/pkg/claim"
)

// AbnormalEventsCount Displays counts of abnormal events for two claims
//
// This struct holds integer counts of abnormal events for two distinct claims,
// named Claim1 and Claim2. The String method formats these values into a
// readable table with headers, producing a string that summarizes the event
// counts for comparison purposes.
type AbnormalEventsCount struct {
Claim1 int `json:"claim1"`
Claim2 int `json:"claim2"`
}

// AbnormalEventsCount.String Formats abnormal event counts for two claims
//
// This method builds a multi-line string that displays the number of abnormal
// events detected in two separate claims. It starts with a header line, then
// adds a formatted table row showing the claim identifiers and their
// corresponding counts using printf-style formatting. The resulting string is
// returned for display or logging.
func (c *AbnormalEventsCount) String() string {
const (
rowHeaderFmt = "%-12s%-s\n"
Expand All @@ -25,11 +38,25 @@ func (c *AbnormalEventsCount) String() string {
return str
}

// DiffReport captures configuration differences and abnormal event counts
//
// This structure contains a diff of Cert Suite configuration objects and a
// count of abnormal events for two claims. The Config field holds the result
// from a diff comparison, while AbnormalEvents stores how many abnormal events
// each claim reported. It is used to report and display discrepancies between
// claims.
type DiffReport struct {
Config *diff.Diffs `json:"CertSuiteConfig"`
AbnormalEvents AbnormalEventsCount `json:"abnormalEventsCount"`
}

// DiffReport.String Formats the diff report into a readable string
//
// This method builds a formatted representation of a configuration comparison,
// beginning with header lines and then appending the configuration details
// followed by any abnormal events. It concatenates strings from the embedded
// Config and AbnormalEvents fields and returns the final result as a single
// string.
func (d *DiffReport) String() string {
str := "CONFIGURATIONS\n"
str += "--------------\n\n"
Expand All @@ -42,6 +69,13 @@ func (d *DiffReport) String() string {
return str
}

// GetDiffReport Creates a report of configuration differences
//
// The function compares two configuration objects from claim files, generating
// a DiffReport that includes field-by-field differences in the main
// configuration map and counts of abnormal events present in each file. It uses
// an external diff utility to compute the detailed comparison and returns the
// assembled report for further processing or display.
func GetDiffReport(claim1Configurations, claim2Configurations *claim.Configurations) *DiffReport {
return &DiffReport{
Config: diff.Compare("Cert Suite Configuration", claim1Configurations.Config, claim2Configurations.Config, nil),
Expand Down
68 changes: 38 additions & 30 deletions cmd/certsuite/claim/compare/diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import (
"strings"
)

// Diffs holds the differences between two interface{} objects that have
// been obtained by unmarshalling JSON strings.
// Diffs Captures differences between two JSON objects
//
// This structure records fields that differ, as well as those present only in
// one of the compared claims. It stores the object name for contextual output
// and provides a method to format the differences into a readable table. The
// fields are populated by comparing flattened representations of each claim.
type Diffs struct {
// Name of the json object whose diffs are stored here.
// It will be used when serializing the data in table format.
Expand All @@ -21,32 +25,25 @@ type Diffs struct {
FieldsInClaim2Only []string
}

// FieldDIff holds the field path and the values from both claim files
// that have been found to be different.
// FieldDiff Represents a mismatch between two claim files
//
// This structure records the location of a differing field along with its value
// from each claim file. It is used during comparison to track which fields
// differ, enabling further processing or reporting. The field path indicates
// where in the document the discrepancy occurs.
type FieldDiff struct {
FieldPath string `json:"field"`
Claim1Value interface{} `json:"claim1Value"`
Claim2Value interface{} `json:"claim2Value"`
}

// Stringer method. The output string is a table like this:
// <name>: Differences
// FIELD CLAIM 1 CLAIM 2
// /jsonpath/to/field1 value1 value2
// /jsonpath/to/another/field2 value3 value4
// ...
//
// <name>: Only in CLAIM 1
// /jsonpath/to/field/in/claim1/only
// ...
//
// <name>: Only in CLAIM 2
// /jsonpath/to/field/in/claim2/only
// ...
// Diffs.String Formats a readable report of claim differences
//
// Where <name> is replaced by the value of d.Name.
// The columns "FIELD" and "CLAIM 1" have a dynamic width that depends
// on the longest field path and longest value.
// The method builds a string that lists fields with differing values between
// two claims, as well as fields present only in one claim or the other. It
// calculates column widths based on longest field paths and values to align the
// table neatly. If no differences exist it displays a placeholder indicating
// none were found.
func (d *Diffs) String() string {
const (
noDiffs = "<none>"
Expand Down Expand Up @@ -110,13 +107,13 @@ func (d *Diffs) String() string {
return str
}

// Compares to interface{} objects obtained through json.Unmarshal() and returns
// a pointer to a Diffs object.
// A simple filtering of json subtrees can be achieved using the filters slice parameter.
// This might be helpful with big json trees that could have too many potential differences,
// but we want to get just the differences for some custom nodes/subtrees.
// E.g.: filters = []string{"labels"} : only the nodes/subtrees under all the
// labels nodes/branches will be traversed and compared.
// Compare Compares two JSON structures for differences
//
// This function takes two interface values that were previously unmarshaled
// from JSON, walks each tree to collect paths and values, then compares
// corresponding entries. It records mismatched values, fields present only in
// the first object, and fields present only in the second. Optional filters
// allow limiting comparison to specified subtrees.
func Compare(objectName string, claim1Object, claim2Object interface{}, filters []string) *Diffs {
objectsDiffs := Diffs{Name: objectName}

Expand Down Expand Up @@ -163,13 +160,24 @@ func Compare(objectName string, claim1Object, claim2Object interface{}, filters
return &objectsDiffs
}

// field represents a node in the traversal result
//
// This structure holds the full path to a value and the value itself as
// encountered during tree walking. The Path string records the hierarchical
// location using delimiters, while Value captures any type of data found at
// that point. It is used by the traversal routine to aggregate matching fields
// for comparison.
type field struct {
Path string
Value interface{}
}

// Helper function that traverses recursively a node to return a list
// of each field (leaf) path and its value.
// traverse recursively collects leaf paths and values from a nested data structure
//
// The function walks through maps, slices, or simple values, building a path
// string for each leaf node separated by slashes. It optionally filters the
// collected fields based on provided substrings in the path. The result is a
// slice of field structs containing the full path and the corresponding value.
func traverse(node interface{}, path string, filters []string) []field {
if node == nil {
return nil
Expand Down
Loading
Loading