Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cli option to group reporter output. Closes #38 #92

Merged
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
fb451a1
Add GroupBy command
jcsmurph Nov 19, 2023
fdb4d5f
Add multiple groupby commands and validation
jcsmurph Nov 19, 2023
4e048fd
Add Tests
jcsmurph Nov 19, 2023
5683cb6
Reformat groupBy
jcsmurph Nov 20, 2023
a88b172
Add Test Files
jcsmurph Nov 20, 2023
7800a50
Update grouping order and update readme
jcsmurph Nov 20, 2023
853efbd
Fix groupby directory
jcsmurph Nov 20, 2023
bd74941
Update Tests
jcsmurph Nov 20, 2023
ca546e4
Fix Comment
jcsmurph Nov 20, 2023
e61be66
Update README.md
jcsmurph Nov 20, 2023
1140303
Update cli_test.go
jcsmurph Nov 20, 2023
b333ed2
Update validator.go
jcsmurph Nov 20, 2023
42e0c50
Update validator_test.go
jcsmurph Nov 20, 2023
1264e99
Update group_output.go
jcsmurph Nov 20, 2023
49de011
Update README.md
jcsmurph Nov 20, 2023
89718be
Update validator.go
jcsmurph Nov 20, 2023
367bd49
Update group_output.go
jcsmurph Nov 20, 2023
438275f
Update validator.go
jcsmurph Nov 20, 2023
741a1ca
Update cli_test.go
jcsmurph Nov 20, 2023
1f21c25
Update group_output.go
jcsmurph Nov 20, 2023
8dc47c6
Formatting fix
jcsmurph Nov 21, 2023
a90a888
Add Group output
jcsmurph Nov 21, 2023
a7b3335
Implement Group Output standard out
jcsmurph Nov 22, 2023
0aa37a3
Implement Single Groupby JSON
jcsmurph Nov 22, 2023
64acf60
Refactor GroupBy
jcsmurph Nov 22, 2023
2429073
Refactor GroupByTwo
jcsmurph Nov 22, 2023
3060971
Update to Stdout
jcsmurph Nov 22, 2023
16e341a
Implement JSON single group
jcsmurph Nov 23, 2023
01ddc54
Implement JSON double group and tests
jcsmurph Nov 23, 2023
d3391a1
Update go fmt
jcsmurph Nov 23, 2023
a190abc
Implement triple group output
jcsmurph Nov 23, 2023
f41a5e0
Add Output Tests
jcsmurph Nov 23, 2023
888f74a
Clean comments
jcsmurph Nov 23, 2023
6898103
Changed Summary Format
jcsmurph Nov 23, 2023
8452661
Update README.md
jcsmurph Nov 30, 2023
5bb439c
Update cmd/validator/validator.go
jcsmurph Nov 30, 2023
e893ee0
Add Header Comments
jcsmurph Dec 4, 2023
4fdb835
Fix Naming Conflict
jcsmurph Dec 4, 2023
a084cc7
Add Util function for Json Reporter
jcsmurph Dec 5, 2023
1a3db98
Rename Utility Function
jcsmurph Dec 5, 2023
eb2dfc1
Rename Utility Function Calls
jcsmurph Dec 5, 2023
bb5f191
Update JSON Print
jcsmurph Dec 5, 2023
e26c7cc
Update STD Reporter Test
jcsmurph Dec 11, 2023
fd2c8e8
Merge branch 'main' into Add-CLI-option-to-group-reporter-output-#38
jcsmurph Dec 11, 2023
3bf77db
Refactor Reporter
jcsmurph Dec 11, 2023
627b5c3
Update GroupBy Filetype
jcsmurph Dec 11, 2023
5262344
GoFmt update
jcsmurph Dec 12, 2023
df6ac8c
Update directory groupby
jcsmurph Dec 18, 2023
99fb5b8
Add GroupBy Directory test
jcsmurph Dec 18, 2023
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
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ There are several ways to install the config file validator tool

We offer alpine, ubuntu, and scratch containers

#### Apline
#### Alpine

```
docker pull ghcr.io/boeing/config-file-validator:v1.5.0
Expand Down Expand Up @@ -103,6 +103,8 @@ optional flags:
Subdirectories to exclude when searching for configuration files
-exclude-file-types string
A comma separated list of file types to ignore
-groupby string
Group the output by filetype, pass-fail, or directory
-reporter string
Format of the printed report. Options are standard and json (default "standard")
-version
Expand Down Expand Up @@ -130,7 +132,7 @@ validator /path/to/search /another/path/to/search
Exclude subdirectories in the search path

```
validator --exclude-dirs=/path/to/search/tests /path/to/search
validator --exclude-dirs=/path/to/search/tests /path/to/search
```

![Exclude Dirs Run](./img/exclude_dirs.png)
Expand All @@ -145,7 +147,7 @@ validator --exclude-file-types=json /path/to/search
![Exclude File Types Run](./img/exclude_file_types.png)

#### Customize recursion depth
By default there is no recursion limit. If desired, the recursion depth can be set to an integer value. If depth is set to `0` recursion will be disabled and only the files in the search path will be validated.
By default there is no recursion limit. If desired, the recursion depth can be set to an integer value. If depth is set to `0` recursion will be disabled and only the files in the search path will be validated.

```
validator --depth=0 /path/to/search
Expand All @@ -162,6 +164,14 @@ validator --reporter=json /path/to/search

![Exclude File Types Run](./img/custom_reporter.png)

### Group report output
Group the report output by file type, directory, or pass-fail. Supports one or more groupings.

```
validator -groupby filetype
validator -groupby directory,pass-fail
```


#### Container Run
```
Expand Down Expand Up @@ -242,4 +252,4 @@ docker build . -t config-file-validator:v1.5.0
We welcome contributions! Please refer to our [contributing guide](/CONTRIBUTING.md)

## License
The Config File Validator is released under the [Apache 2.0](/LICENSE) License
The Config File Validator is released under the [Apache 2.0](/LICENSE) License
41 changes: 41 additions & 0 deletions cmd/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"fmt"
"log"
"os"
"slices"
"strings"

configfilevalidator "github.com/Boeing/config-file-validator"
Expand All @@ -46,6 +47,7 @@ type validatorConfig struct {
reportType *string
depth *int
versionQuery *bool
groupOutput *string
}

// Custom Usage function to cover
Expand All @@ -71,6 +73,7 @@ func getFlags() (validatorConfig, error) {
excludeFileTypesPtr := flag.String("exclude-file-types", "", "A comma separated list of file types to ignore")
depthPtr := flag.Int("depth", 0, "Depth of recursion for the provided search paths. Set depth to 0 to disable recursive path traversal")
versionPtr := flag.Bool("version", false, "Version prints the release version of validator")
groupOutputPtr := flag.String("groupby", "", "Group output by filetype, directory, pass-fail")
flag.Parse()

searchPaths := make([]string, 0)
Expand All @@ -96,13 +99,36 @@ func getFlags() (validatorConfig, error) {
return validatorConfig{}, errors.New("Wrong parameter value for depth, value cannot be negative")
}

groupByCleanString := cleanString("groupby")
groupByUserInput := strings.Split(groupByCleanString, ",")
groupByAllowedValues := []string{"filetype", "directory", "pass-fail"}
seenValues := make(map[string]bool)

// Check that the groupby values are valid and not duplicates
if groupOutputPtr != nil && isFlagSet("groupby") {
for _, groupBy := range groupByUserInput {
if !slices.Contains(groupByAllowedValues, groupBy) {
fmt.Println("Wrong parameter value for groupby, only supports filetype, directory, pass-fail")
flag.Usage()
return validatorConfig{}, errors.New("Wrong parameter value for groupby, only supports filetype, directory, pass-fail")
}
if _, ok := seenValues[groupBy]; ok {
fmt.Println("Wrong parameter value for groupby, duplicate values are not allowed")
flag.Usage()
return validatorConfig{}, errors.New("Wrong parameter value for groupby, duplicate values are not allowed")
}
seenValues[groupBy] = true
}
}

config := validatorConfig{
searchPaths,
excludeDirsPtr,
excludeFileTypesPtr,
reportTypePtr,
depthPtr,
versionPtr,
groupOutputPtr,
}

return config, nil
Expand Down Expand Up @@ -132,6 +158,16 @@ func getReporter(reportType *string) reporter.Reporter {
}
}

// cleanString takes a command string and a split string
// and returns a cleaned string
func cleanString(command string) string {
cleanedString := flag.Lookup(command).Value.String()
cleanedString = strings.ToLower(cleanedString)
cleanedString = strings.TrimSpace(cleanedString)

return cleanedString
}

func mainInit() int {
validatorConfig, err := getFlags()
if err != nil {
Expand All @@ -149,6 +185,10 @@ func mainInit() int {
reporter := getReporter(validatorConfig.reportType)
excludeFileTypes := strings.Split(*validatorConfig.excludeFileTypes, ",")

// since the group output is a comma separated string
// it needs to be split into a slice of strings
groupOutput := strings.Split(*validatorConfig.groupOutput, ",")

fsOpts := []finder.FSFinderOptions{finder.WithPathRoots(validatorConfig.searchPaths...),
finder.WithExcludeDirs(excludeDirs),
finder.WithExcludeFileTypes(excludeFileTypes)}
Expand All @@ -164,6 +204,7 @@ func mainInit() int {
cli := cli.Init(
cli.WithReporter(reporter),
cli.WithFinder(fileSystemFinder),
cli.WithGroupOutput(groupOutput),
)

// Run the config file validation
Expand Down
2 changes: 2 additions & 0 deletions cmd/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func Test_flags(t *testing.T) {
{"exclude file types set", []string{"--exclude-file-types=json", "."}, 0},
{"multiple paths", []string{"../../test/fixtures/subdir/good.json", "../../test/fixtures/good.json"}, 0},
{"version", []string{"--version"}, 0},
{"incorrect group", []string{"-groupby=badgroup", "."}, 1},
{"correct group", []string{"-groupby=directory", "."}, 0},
}
for _, tc := range cases {
// this call is required because otherwise flags panics,
Expand Down
34 changes: 33 additions & 1 deletion pkg/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (
"github.com/Boeing/config-file-validator/pkg/reporter"
)

// GroupOutput is a global variable that is used to
// store the group by options that the user specifies
var GroupOutput []string

type CLI struct {
// FileFinder interface to search for the files
// in the SearchPath
Expand Down Expand Up @@ -36,6 +40,12 @@ func WithReporter(reporter reporter.Reporter) CLIOption {
}
}

func WithGroupOutput(groupOutput []string) CLIOption {
return func(c *CLI) {
GroupOutput = groupOutput
}
}

// Initialize the CLI object
func Init(opts ...CLIOption) *CLI {
defaultFsFinder := finder.FileSystemFinderInit()
Expand Down Expand Up @@ -88,7 +98,29 @@ func (c CLI) Run() (int, error) {
reports = append(reports, report)
}

c.Reporter.Print(reports)
// Group the output if the user specified a group by option
// Length is equal to one when empty as it contains an empty string
if len(GroupOutput) == 1 && GroupOutput[0] != "" {
reportGroup, err := GroupBySingle(reports, GroupOutput[0])
if err != nil {
return 1, fmt.Errorf("unable to group by single value: %v", err)
}
c.Reporter.PrintSingleGroup(reportGroup, GroupOutput[0])
} else if len(GroupOutput) == 2 {
reportGroup, err := GroupByDouble(reports, GroupOutput)
if err != nil {
return 1, fmt.Errorf("unable to group by double value: %v", err)
}
c.Reporter.PrintDoubleGroup(reportGroup, GroupOutput)
} else if len(GroupOutput) == 3 {
reportGroup, err := GroupByTriple(reports, GroupOutput)
if err != nil {
return 1, fmt.Errorf("unable to group by triple value: %v", err)
}
c.Reporter.PrintTripleGroup(reportGroup, GroupOutput)
} else {
c.Reporter.Print(reports)
}

if errorFound {
return 1, nil
Expand Down
28 changes: 28 additions & 0 deletions pkg/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
func Test_CLI(t *testing.T) {
searchPath := "../../test"
excludeDirs := []string{"subdir", "subdir2"}
groupOutput := []string{""}
stdoutReporter := reporter.StdoutReporter{}

fsFinder := finder.FileSystemFinderInit(
Expand All @@ -19,6 +20,7 @@ func Test_CLI(t *testing.T) {
cli := Init(
WithFinder(fsFinder),
WithReporter(stdoutReporter),
WithGroupOutput(groupOutput),
)
exitStatus, err := cli.Run()

Expand Down Expand Up @@ -70,3 +72,29 @@ func Test_CLIBadPath(t *testing.T) {
t.Errorf("Exit status was not 1")
}
}

func Test_CLIWithGroup(t *testing.T) {
searchPath := "../../test"
excludeDirs := []string{"subdir", "subdir2"}
groupOutput := []string{"pass-fail", "directory"}
stdoutReporter := reporter.StdoutReporter{}

fsFinder := finder.FileSystemFinderInit(
finder.WithPathRoots(searchPath),
finder.WithExcludeDirs(excludeDirs),
)
cli := Init(
WithFinder(fsFinder),
WithReporter(stdoutReporter),
WithGroupOutput(groupOutput),
)
exitStatus, err := cli.Run()

if err != nil {
t.Errorf("An error was returned: %v", err)
}

if exitStatus != 0 {
t.Errorf("Exit status was not 0")
}
}
126 changes: 126 additions & 0 deletions pkg/cli/group_output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package cli

import (
"fmt"
"strings"

"github.com/Boeing/config-file-validator/pkg/reporter"
)

// Group Reports by File Type
func GroupByFile(reports []reporter.Report) map[string][]reporter.Report {
reportByFile := make(map[string][]reporter.Report)

for _, report := range reports {
fileType := strings.Split(report.FileName, ".")[1]
if fileType == "yml" {
fileType = "yaml"
}
if reportByFile[fileType] == nil {
reportByFile[fileType] = []reporter.Report{report}
} else {
reportByFile[fileType] = append(reportByFile[fileType], report)
}
}

return reportByFile
}

// Group Reports by Pass-Fail
func GroupByPassFail(reports []reporter.Report) map[string][]reporter.Report {
reportByPassOrFail := make(map[string][]reporter.Report)

for _, report := range reports {
if report.IsValid {
if reportByPassOrFail["Passed"] == nil {
reportByPassOrFail["Passed"] = []reporter.Report{report}
} else {
reportByPassOrFail["Passed"] = append(reportByPassOrFail["Passed"], report)
}
} else if reportByPassOrFail["Failed"] == nil {
reportByPassOrFail["Failed"] = []reporter.Report{report}
} else {
reportByPassOrFail["Failed"] = append(reportByPassOrFail["Failed"], report)
}

}

return reportByPassOrFail
}

// Group Reports by Directory
func GroupByDirectory(reports []reporter.Report) map[string][]reporter.Report {
reportByDirectory := make(map[string][]reporter.Report)
for _, report := range reports {
directoryPath := strings.Split(report.FilePath, "/")
directory := strings.Join(directoryPath[:len(directoryPath)-1], "/")
directory = directory + "/"

if reportByDirectory[directory] == nil {
reportByDirectory[directory] = []reporter.Report{report}
} else {
reportByDirectory[directory] = append(reportByDirectory[directory], report)
}
}

return reportByDirectory
}

// Group Reports by single grouping
func GroupBySingle(reports []reporter.Report, groupBy string) (map[string][]reporter.Report, error) {
var groupReport map[string][]reporter.Report

// Group by the groupings in reverse order
// This allows for the first grouping to be the outermost grouping
for i := len(groupBy) - 1; i >= 0; i-- {
switch groupBy {
case "pass-fail":
groupReport = GroupByPassFail(reports)
case "filetype":
groupReport = GroupByFile(reports)
case "directory":
groupReport = GroupByDirectory(reports)
default:
return nil, fmt.Errorf("unable to group by %s", groupBy)
}
}
return groupReport, nil
}

// Group Reports for two groupings
func GroupByDouble(reports []reporter.Report, groupBy []string) (map[string]map[string][]reporter.Report, error) {
groupReport := make(map[string]map[string][]reporter.Report)

firstGroup, err := GroupBySingle(reports, groupBy[0])
if err != nil {
return nil, err
}
for key := range firstGroup {
groupReport[key] = make(map[string][]reporter.Report)
groupReport[key], err = GroupBySingle(firstGroup[key], groupBy[1])
if err != nil {
return nil, err
}
}

return groupReport, nil
}

// Group Reports for three groupings
func GroupByTriple(reports []reporter.Report, groupBy []string) (map[string]map[string]map[string][]reporter.Report, error) {
groupReport := make(map[string]map[string]map[string][]reporter.Report)

firstGroup, err := GroupBySingle(reports, groupBy[0])
if err != nil {
return nil, err
}
for key := range firstGroup {
groupReport[key] = make(map[string]map[string][]reporter.Report)
groupReport[key], err = GroupByDouble(firstGroup[key], groupBy[1:])
if err != nil {
return nil, err
}
}

return groupReport, nil
}
Loading