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

feat(linting): Add support for linting decK files #981

Merged
merged 3 commits into from
Oct 18, 2023
Merged
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
199 changes: 199 additions & 0 deletions cmd/file_lint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package cmd

import (
"errors"
"fmt"
"log"
"strings"

"github.com/daveshanley/vacuum/motor"
"github.com/daveshanley/vacuum/rulesets"
"github.com/kong/go-apiops/filebasics"
"github.com/kong/go-apiops/logbasics"
"github.com/spf13/cobra"
)

var (
cmdLintInputFilename string
cmdLintFormat string
cmdLintFailSeverity string
cmdLintOutputFilename string
cmdLintOnlyFailures bool
)

const plainTextFormat = "plain"

type Severity int

const (
SeverityHint Severity = iota
SeverityInfo
SeverityWarn
SeverityError
)

var severityStrings = [...]string{
"hint",
"info",
"warn",
"error",
}

type LintResult struct {
Message string
Severity string
Line int
Column int
Character int
Path string
}

func ParseSeverity(s string) Severity {
for i, str := range severityStrings {
if s == str {
return Severity(i)
}
}
return SeverityWarn
}

// getRuleSet reads the ruleset file by the provided name and returns a RuleSet object.
func getRuleSet(ruleSetFile string) (*rulesets.RuleSet, error) {
ruleSetBytes, err := filebasics.ReadFile(ruleSetFile)
if err != nil {
return nil, fmt.Errorf("error reading ruleset file: %w", err)
}
customRuleSet, err := rulesets.CreateRuleSetFromData(ruleSetBytes)
if err != nil {
return nil, fmt.Errorf("error creating ruleset: %w", err)
}
return customRuleSet, nil
}

// Executes the CLI command "lint"
func executeLint(cmd *cobra.Command, args []string) error {
verbosity, _ := cmd.Flags().GetInt("verbose")
logbasics.Initialize(log.LstdFlags, verbosity)

customRuleSet, err := getRuleSet(args[0])
if err != nil {
return err
}

stateFileBytes, err := filebasics.ReadFile(cmdLintInputFilename)
if err != nil {
return fmt.Errorf("failed to read input file '%s'; %w", cmdLintInputFilename, err)
}

ruleSetResults := motor.ApplyRulesToRuleSet(&motor.RuleSetExecution{
RuleSet: customRuleSet,
Spec: stateFileBytes,
SkipDocumentCheck: true,
})

var (
failingCount int
totalCount int
lintResults = make([]LintResult, 0)
)
for _, x := range ruleSetResults.Results {
if cmdLintOnlyFailures && ParseSeverity(x.Rule.Severity) < ParseSeverity(cmdLintFailSeverity) {
continue
}
if ParseSeverity(x.Rule.Severity) >= ParseSeverity(cmdLintFailSeverity) {
failingCount++
}
totalCount++
lintResults = append(lintResults, LintResult{
Message: x.Message,
Path: func() string {
if path, ok := x.Rule.Given.(string); ok {
return path
}
return ""
}(),
Line: x.StartNode.Line,
Column: x.StartNode.Column,
Severity: x.Rule.Severity,
})
}

lintErrs := map[string]interface{}{
"total_count": totalCount,
"fail_count": failingCount,
"results": lintResults,
}

outputFormat := strings.ToUpper(cmdLintFormat)
switch outputFormat {
case strings.ToUpper(string(filebasics.OutputFormatJSON)):
fallthrough
case strings.ToUpper(string(filebasics.OutputFormatYaml)):
if err = filebasics.WriteSerializedFile(
cmdLintOutputFilename, lintErrs, filebasics.OutputFormat(outputFormat),
); err != nil {
return fmt.Errorf("error writing lint results: %w", err)
}
case strings.ToUpper(plainTextFormat):
if totalCount > 0 {
fmt.Printf("Linting Violations: %d\n", totalCount)
fmt.Printf("Failures: %d\n\n", failingCount)
for _, violation := range lintErrs["results"].([]LintResult) {
fmt.Printf("[%s][%d:%d] %s\n",
violation.Severity, violation.Line, violation.Column, violation.Message,
)
}
}
default:
return fmt.Errorf("invalid output format: %s", cmdLintFormat)
}
if failingCount > 0 {
GGabriele marked this conversation as resolved.
Show resolved Hide resolved
// We don't want to print the error here as they're already output above
// But we _do_ want to set an exit code of failure.
//
// We could simply use os.Exit(1) here, but that would make e2e tests harder.
cmd.SilenceErrors = true
return errors.New("linting errors detected")
}
return nil
}

//
//
// Define the CLI data for the lint command
//
//

func newLintCmd() *cobra.Command {
lintCmd := &cobra.Command{
Use: "lint [flags] ruleset-file",
Short: "Lint a file against a ruleset",
Long: "Validate a decK state file against a linting ruleset, reporting any violations or failures.\n" +
"Report output can be returned in JSON, YAML, or human readable format (see --format).\n" +
"Ruleset Docs: https://quobix.com/vacuum/rulesets/",
RunE: executeLint,
Args: cobra.ExactArgs(1),
}

lintCmd.Flags().StringVarP(&cmdLintInputFilename, "state", "s", "-",
GGabriele marked this conversation as resolved.
Show resolved Hide resolved
"decK file to process. Use - to read from stdin.")
lintCmd.Flags().StringVarP(&cmdLintOutputFilename, "output-file", "o", "-",
"Output file to write to. Use - to write to stdout.")
lintCmd.Flags().StringVar(
&cmdLintFormat, "format", plainTextFormat,
fmt.Sprintf(`output format [choices: "%s", "%s", "%s"]`,
plainTextFormat,
string(filebasics.OutputFormatJSON),
string(filebasics.OutputFormatYaml),
),
)
lintCmd.Flags().StringVarP(
&cmdLintFailSeverity, "fail-severity", "F", "error",
"results of this level or above will trigger a failure exit code\n"+
"[choices: \"error\", \"warn\", \"info\", \"hint\"]")
lintCmd.Flags().BoolVarP(&cmdLintOnlyFailures,
"display-only-failures", "D", false,
"only output results equal to or greater than --fail-severity")

return lintCmd
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ It can be used to export, import, or sync entities to Kong.`,
fileCmd.AddCommand(newPatchCmd())
fileCmd.AddCommand(newOpenapi2KongCmd())
fileCmd.AddCommand(newFileRenderCmd())
fileCmd.AddCommand(newLintCmd())
}
return rootCmd
}
Expand Down
24 changes: 22 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/alecthomas/jsonschema v0.0.0-20191017121752-4bb6e3fae4f2
github.com/blang/semver/v4 v4.0.0
github.com/cenkalti/backoff/v4 v4.2.1
github.com/daveshanley/vacuum v0.2.7
github.com/fatih/color v1.15.0
github.com/google/go-cmp v0.5.9
github.com/google/go-querystring v1.1.0
Expand All @@ -28,18 +29,24 @@ require (
github.com/stretchr/testify v1.8.4
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/sync v0.3.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/code-generator v0.28.2
sigs.k8s.io/yaml v1.3.0
)

require (
atomicgo.dev/cursor v0.1.1 // indirect
atomicgo.dev/keyboard v0.2.9 // indirect
atomicgo.dev/schedule v0.0.2 // indirect
github.com/Kong/go-diff v1.2.2 // indirect
github.com/adrg/strutil v0.2.3 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/getkin/kin-openapi v0.108.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
Expand All @@ -49,6 +56,7 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/gookit/color v1.5.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
Expand All @@ -58,20 +66,27 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kong/semver/v4 v4.0.1 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/mozillazg/go-slugify v0.2.0 // indirect
github.com/mozillazg/go-unidecode v0.2.0 // indirect
github.com/pb33f/libopenapi v0.9.6 // indirect
github.com/pb33f/libopenapi-validator v0.0.10 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pterm/pterm v0.12.62 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.3.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
Expand All @@ -85,16 +100,21 @@ require (
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/net v0.13.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.8.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect
k8s.io/klog/v2 v2.100.1 // indirect
Expand Down
Loading
Loading