Skip to content

Commit

Permalink
Add lint command and timeout flag
Browse files Browse the repository at this point in the history
And add Cobra library to deal with flags/args

Signed-off-by: Anders Eknert <anders@styra.com>
  • Loading branch information
anderseknert committed Mar 20, 2023
1 parent 6f868f9 commit 2d92757
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 51 deletions.
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ linters:
- varnamelen
- nonamedreturns
- testpackage
- gochecknoinits
- gomnd
- godox
- exhaustruct
Expand Down
107 changes: 107 additions & 0 deletions cmd/lint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package cmd

import (
"context"
"embed"
"errors"
"fmt"
"io/fs"
"log"
"os"
"time"

"github.com/open-policy-agent/opa/loader"
"github.com/spf13/cobra"
rio "github.com/styrainc/regal/internal/io"
"github.com/styrainc/regal/pkg/config"
"github.com/styrainc/regal/pkg/linter"
)

type lintCommandParams struct {
timeout time.Duration
}

//nolint:gochecknoglobals
var EmbedBundleFS embed.FS

var errNoFileProvided = errors.New("at least one file or directory must be provided for linting")

func init() {
params := lintCommandParams{}

lintCommand := &cobra.Command{
Use: "lint <path> [path [...]]",
Short: "Lint Rego source files",
Long: `Lint Rego source files for linter rule violations.`,

PreRunE: func(_ *cobra.Command, args []string) error {
if len(args) == 0 {
return errNoFileProvided
}

return nil
},

Run: func(_ *cobra.Command, args []string) {
if err := lint(args, params); err != nil {
log.SetOutput(os.Stderr)
log.Println(err)
os.Exit(1)
}
},
}

lintCommand.Flags().DurationVar(&params.timeout, "timeout", 0, "set timeout for linting (default unlimited)")

RootCommand.AddCommand(lintCommand)
}

func lint(args []string, params lintCommandParams) error {
ctx := context.Background()

if params.timeout != 0 {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, params.timeout)

defer cancel()
}

// Create new fs from root of bundle, to avoid having to deal with
// "bundle" in paths (i.e. `data.bundle.regal`)
bfs, err := fs.Sub(EmbedBundleFS, "bundle")
if err != nil {
return fmt.Errorf("failed reading embedded bundle %w", err)
}

regalRules := rio.MustLoadRegalBundleFS(bfs)

policies, err := loader.AllRegos(args)
if err != nil {
return fmt.Errorf("failed to load policy from provided args %w", err)
}

// TODO: Allow user-provided path to config
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get cwd %w", err)
}

regal := linter.NewLinter().WithAddedBundle(regalRules)

userConfig, err := config.FindConfig(cwd)
if err == nil {
defer rio.CloseFileIgnore(userConfig)

regal = regal.WithUserConfig(rio.MustYAMLToMap(userConfig))
}

rep, err := regal.Lint(ctx, policies)
if err != nil {
return fmt.Errorf("error(s) ecountered while linting %w", err)
}

// TODO: Create reporter interface and implementations
log.Println(rep)

return nil
}
17 changes: 17 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cmd

import (
"os"
"path"

"github.com/spf13/cobra"
)

// RootCommand is the base CLI command that all subcommands are added to.
//
//nolint:gochecknoglobals
var RootCommand = &cobra.Command{
Use: path.Base(os.Args[0]),
Short: "Regal",
Long: "Regal is a linter for Rego, with the goal of making your Rego magnificent!",
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ require (
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
Expand All @@ -26,6 +27,9 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand All @@ -41,7 +45,12 @@ github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
Expand Down
58 changes: 7 additions & 51 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
package main

import (
"context"
"embed"
"io/fs"
"log"
"os"
"time"

"github.com/open-policy-agent/opa/loader"
rio "github.com/styrainc/regal/internal/io"
"github.com/styrainc/regal/pkg/config"
"github.com/styrainc/regal/pkg/linter"
"github.com/styrainc/regal/cmd"
)

// Note: this will bundle the tests as well, but since that has negligible impact on the size of the binary,
Expand All @@ -22,52 +16,14 @@ var bundle embed.FS

func main() {
// Remove date and time from any `log.*` calls, as that doesn't add much of value here
// Evaluate options for logging later..
log.SetFlags(0)

if len(os.Args) < 2 {
log.Fatal("At least one file or directory must be provided for linting")
}

// Create new fs from root of bundle, do avoid having to deal with
// "bundle" in paths (i.e. `data.bundle.regal`)
bfs, err := fs.Sub(bundle, "bundle")
if err != nil {
log.Fatal(err)
}

regalRules := rio.MustLoadRegalBundleFS(bfs)

policies, err := loader.AllRegos(os.Args[1:])
if err != nil {
log.Fatal(err)
}

// TODO: Allow user-provided path to config
cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}

// TODO: Make timeout configurable via command line flag
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5)*time.Second)
defer cancel()

regal := linter.NewLinter().WithAddedBundle(regalRules)

userConfig, err := config.FindConfig(cwd)
if err == nil {
defer rio.CloseFileIgnore(userConfig)

regal = regal.WithUserConfig(rio.MustYAMLToMap(userConfig))
}
// The embedded FS can't point to parent directories, so while not pretty, we'll
// need to pass it from here to the next command
cmd.EmbedBundleFS = bundle

rep, err := regal.Lint(ctx, policies)
if err != nil {
defer func() {
log.Fatal(err)
}()
} else {
// TODO: Create reporter interface and implementations
log.Println(rep)
if err := cmd.RootCommand.Execute(); err != nil {
os.Exit(1)
}
}

0 comments on commit 2d92757

Please sign in to comment.