Skip to content

Commit

Permalink
feat(linting): Add support for linting decK files
Browse files Browse the repository at this point in the history
  • Loading branch information
mheap authored and Tieske committed Oct 17, 2023
1 parent 1b8317a commit f4d1a8c
Show file tree
Hide file tree
Showing 12 changed files with 607 additions and 11 deletions.
209 changes: 209 additions & 0 deletions cmd/file_lint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package cmd

import (
"errors"
"fmt"
"io"
"log"
"os"
"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
cmdLintInputRuleset string
cmdLintFormat string
cmdLintFailSeverity 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
}

func getRuleSet(ruleSetFile string) (*rulesets.RuleSet, error) {
ruleSetBytes, err := os.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
}

func executeLint(cmd *cobra.Command, _ []string) error {
verbosity, _ := cmd.Flags().GetInt("verbose")
logbasics.Initialize(log.LstdFlags, verbosity)

if cmdLintInputRuleset == "" {
return errors.New("missing required option: --ruleset")
}
customRuleSet, err := getRuleSet(cmdLintInputRuleset)
if err != nil {
return err
}

var stateFileBytes []byte
if cmdLintInputFilename == "-" {
stateFileBytes, err = io.ReadAll(os.Stdin)
if err != nil {
return fmt.Errorf("error reading state from STDIN: %w", err)
}
} else {
stateFileBytes, err = os.ReadFile(cmdLintInputFilename)
if err != nil {
return fmt.Errorf("error reading state file: %w", 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(
"-", 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 {
// 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",
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,
}

lintCmd.Flags().StringVarP(&cmdLintInputFilename, "state", "s", "-",
"decK file to process. Use - to read from stdin.")
lintCmd.Flags().StringVarP(&cmdLintInputRuleset, "ruleset", "r", "",
"Ruleset to apply to the state file.")
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 "+
"[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

0 comments on commit f4d1a8c

Please sign in to comment.