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

Better linting and error handling #81

Merged
merged 8 commits into from
Sep 11, 2024
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
/schema/ignore
/scm-engine
/scm-engine.exe
/scm-engine.schema.json
scm-engine.schema.json
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ tasks:
- go run . gitlab -h > docs/gitlab/_partials/cmd-gitlab.md
- go run . gitlab evaluate -h > docs/gitlab/_partials/cmd-gitlab-evaluate.md
- go run . gitlab server -h > docs/gitlab/_partials/cmd-gitlab-server.md
- go run . jsonschema > docs/scm-engine.schema.json
- cp pkg/generated/resources/scm-engine.schema.json docs/scm-engine.schema.json

docs:server:
desc: Run Docs dev server with live preview
Expand Down
13 changes: 13 additions & 0 deletions cmd/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ var GitLab = &cli.Command{
},
},
Subcommands: []*cli.Command{
{
Name: "lint",
Usage: "lint a configuration file",
Args: false,
Action: Lint,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "schema",
Usage: "Where to find the JSON Schema file. Can load the file from either the embedded version (default), http://, https://, or a file:// URI",
Value: "embed://",
},
},
},
{
Name: "evaluate",
Usage: "Evaluate a Merge Request",
Expand Down
106 changes: 106 additions & 0 deletions cmd/gitlab_lint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package cmd

import (
"fmt"
"net/http"
"os"
"strings"
"time"

"github.com/jippi/scm-engine/pkg/config"
"github.com/jippi/scm-engine/pkg/generated/resources"
"github.com/jippi/scm-engine/pkg/scm/gitlab"
"github.com/jippi/scm-engine/pkg/state"
"github.com/santhosh-tekuri/jsonschema/v6"
"github.com/urfave/cli/v2"
slogctx "github.com/veqryn/slog-context"
"gopkg.in/yaml.v3"
)

func Lint(cCtx *cli.Context) error {
ctx := cCtx.Context
ctx = state.WithConfigFilePath(ctx, cCtx.String(FlagConfigFile))

// Read raw YAML file
raw, err := os.ReadFile(state.ConfigFilePath(ctx))
if err != nil {
return err
}

// Parse the YAML file into lose Go shape
var yamlOutput any
if err := yaml.Unmarshal(raw, &yamlOutput); err != nil {
return err
}

// Setup file loaders for reading the JSON schema file
loader := jsonschema.SchemeURLLoader{
"file": jsonschema.FileLoader{},
"http": newHTTPURLLoader(),
"https": newHTTPURLLoader(),
"embed": &EmbedLoader{},
}

// Create json schema compiler
compiler := jsonschema.NewCompiler()
compiler.UseLoader(loader)

// Compile the schema into validator format
sch, err := compiler.Compile(cCtx.String("schema"))
if err != nil {
return err
}

// Validate the json output
if err := sch.Validate(yamlOutput); err != nil {
return err
}

// Load the configuration file via our Go struct
cfg, err := config.LoadFile(state.ConfigFilePath(ctx))
if err != nil {
return err
}

if len(cfg.Includes) != 0 {
slogctx.Warn(ctx, "Configuration file contains 'include' settings, those are currently unsupported by 'lint' command and will be ignored")
}

// To scm-engine specific linting last
return cfg.Lint(ctx, &gitlab.Context{})
}

type EmbedLoader struct{}

func (l *EmbedLoader) Load(url string) (any, error) {
return jsonschema.UnmarshalJSON(strings.NewReader(resources.JSONSchema))
}

type HTTPURLLoader http.Client

func (l *HTTPURLLoader) Load(url string) (any, error) {
client := (*http.Client)(l)

resp, err := client.Get(url) //nolint
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
resp.Body.Close()

return nil, fmt.Errorf("%s returned status code %d", url, resp.StatusCode)
}

defer resp.Body.Close()

return jsonschema.UnmarshalJSON(resp.Body)
}

func newHTTPURLLoader() *HTTPURLLoader {
httpLoader := HTTPURLLoader(http.Client{
Timeout: 15 * time.Second,
})

return &httpLoader
}
5 changes: 5 additions & 0 deletions cmd/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ func ProcessMR(ctx context.Context, client scm.Client, cfg *config.Config, event
ctx = state.WithDryRun(ctx, *cfg.DryRun)
}

// Lint the configuration file to catch any misconfigurations
if err := cfg.Lint(ctx, evalContext); err != nil {
return fmt.Errorf("Configuration failed validation: %w", err)
}

// Write the config to context so we can pull it out later
ctx = config.WithConfig(ctx, cfg)

Expand Down
3 changes: 3 additions & 0 deletions generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package main

//go:generate go run ./pkg/generated/main.go
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ require (
github.com/golang-cz/devslog v0.0.9
github.com/google/go-github/v64 v64.0.0
github.com/guregu/null/v5 v5.0.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hasura/go-graphql-client v0.13.0
github.com/iancoleman/strcase v0.3.0
github.com/invopop/jsonschema v0.12.0
github.com/lmittmann/tint v1.0.5
github.com/muesli/termenv v0.15.2
github.com/samber/slog-multi v1.2.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
github.com/stretchr/testify v1.9.0
github.com/teacat/noire v1.1.0
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569
github.com/urfave/cli/v2 v2.27.4
github.com/vektah/gqlparser/v2 v2.5.16
github.com/veqryn/slog-context v0.7.0
github.com/veqryn/slog-dedup v0.5.0
github.com/wk8/go-ordered-map/v2 v2.1.8
github.com/xanzy/go-gitlab v0.109.0
github.com/xhit/go-str2duration/v2 v2.1.0
golang.org/x/oauth2 v0.23.0
Expand All @@ -37,11 +40,14 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/charmbracelet/x/ansi v0.1.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand All @@ -51,7 +57,6 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/mod v0.20.0 // indirect
Expand All @@ -60,6 +65,7 @@ require (
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.24.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
modernc.org/b/v2 v2.1.0 // indirect
nhooyr.io/websocket v1.8.11 // indirect
)
20 changes: 17 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI=
github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
Expand All @@ -46,10 +48,14 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/guregu/null/v5 v5.0.0 h1:PRxjqyOekS11W+w/7Vfz6jgJE/BCwELWtgvOJzddimw=
github.com/guregu/null/v5 v5.0.0/go.mod h1:SjupzNy+sCPtwQTKWhUCqjhVCO69hpsl2QsZrWHjlwU=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
Expand All @@ -61,6 +67,11 @@ github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw=
github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
Expand Down Expand Up @@ -90,6 +101,8 @@ github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/samber/slog-multi v1.2.1 h1:MRVc6JxvGiZ+ubyANneZkMREAFAykoW0CACJZagT7so=
github.com/samber/slog-multi v1.2.1/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
Expand Down Expand Up @@ -136,8 +149,9 @@ golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
25 changes: 3 additions & 22 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
//go:build !generate
// +build !generate

package main

import (
"encoding/json"
"fmt"
"io"
"log"
"os"

"github.com/davecgh/go-spew/spew"
"github.com/invopop/jsonschema"
"github.com/jippi/scm-engine/cmd"
"github.com/jippi/scm-engine/pkg/config"
"github.com/jippi/scm-engine/pkg/state"
"github.com/jippi/scm-engine/pkg/tui"
"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -72,26 +72,7 @@ func main() {
Commands: []*cli.Command{
cmd.GitLab,
cmd.GitHub,
{
Name: "jsonschema",
Action: func(ctx *cli.Context) error {
r := new(jsonschema.Reflector)
if err := r.AddGoComments("github.com/jippi/scm-engine", "./"); err != nil {
return err
}

schema := r.Reflect(&config.Config{})

data, err := json.MarshalIndent(schema, "", " ")
if err != nil {
panic(err.Error())
}

fmt.Println(string(data))

return os.WriteFile("scm-engine.schema.json", data, 0o600)
},
},
// DEPRECATED COMMANDS
{
Name: "evaluate",
Expand Down
33 changes: 28 additions & 5 deletions pkg/config/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,32 @@ import (
slogctx "github.com/veqryn/slog-context"
)

type Actions []Action
type (
Actions []Action

Action struct {
// The name of the action, this is purely for debugging and your convenience.
//
// See: https://jippi.github.io/scm-engine/configuration/#actions.name
Name string `json:"name" yaml:"name"`

// (Optional) Only one action per group (in order) will be executed per evaluation cycle.
// Use this to 'stop' other actions from running with the same group name
Group string `json:"group,omitempty" yaml:"group"`

// A key controlling if the action should executed or not.
//
// This script is in Expr-lang: https://expr-lang.org/docs/language-definition
//
// See: https://jippi.github.io/scm-engine/configuration/#actions.if
If string `json:"if" yaml:"if"`

// The list of operations to take if the action.if returned true.
//
// See: https://jippi.github.io/scm-engine/configuration/#actions.if.then
Then []ActionStep `json:"then" yaml:"then"`
}
)

func (actions Actions) Evaluate(ctx context.Context, evalContext scm.EvalContext) ([]Action, error) {
results := []Action{}
Expand Down Expand Up @@ -42,10 +67,8 @@ func (actions Actions) Evaluate(ctx context.Context, evalContext scm.EvalContext
return results, nil
}

type Action scm.EvaluationActionResult

func (p *Action) Evaluate(ctx context.Context, evalContext scm.EvalContext) (bool, error) {
program, err := p.initialize(evalContext)
program, err := p.Setup(evalContext)
if err != nil {
return false, err
}
Expand All @@ -54,7 +77,7 @@ func (p *Action) Evaluate(ctx context.Context, evalContext scm.EvalContext) (boo
return runAndCheckBool(ctx, program, evalContext)
}

func (p *Action) initialize(evalContext scm.EvalContext) (*vm.Program, error) {
func (p *Action) Setup(evalContext scm.EvalContext) (*vm.Program, error) {
opts := []expr.Option{}
opts = append(opts, expr.AsBool())
opts = append(opts, expr.Env(evalContext))
Expand Down
Loading
Loading