Skip to content
Open
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 cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func rootRunE(cmd *cobra.Command, args []string) error {
}
p := parser.NewParser(cfg.Rules, ignorer)

print, err := printer.NewPrinter(outputName, output.Stdout)
print, err := printer.NewPrinter(outputName, output.Stdout, cfg)
if err != nil {
return err
}
Expand Down
45 changes: 45 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,51 @@ Format used to populate results into the popular [SonarQube](https://www.sonarqu
!!! note
`<sonarqubeseverity>` is mapped from severity, such that an error in `woke` is translated to a `MAJOR`, warning to a `MINOR`, and info to `INFO`

### Prometheus

!!! example ""
`woke -o prometheus`

Outputs the results as a [`prometheus metrics`](https://prometheus.io/docs/practices/naming/) one per line.

#### Structure

!!! info inline end
Actual output from woke will be consolidated Prometheus metrics. Having this output as a file could be used after through [textfile-collector / node exporter](https://github.com/prometheus/node_exporter#textfile-collector)

```text
woke_result{file="CHANGELOG.md:62:24-30", term="master", repo="sample", something="else"} 1
woke_result{file="README.md:1:241-247", term="master", repo="sample", something="else"} 1
```

If pushgateway parameter is provided, it will push woke result as [`prometheus metrics`](https://prometheus.io/docs/practices/naming/) to a [`pushgateway instance`](https://prometheus.io/docs/practices/pushing/).

#### Configuration

As per the sample output above, this will put labels in the prometheus metric with:

- file: same as previous outputs format, file name with line and start/end position
- term: this is currently using the rule name
- repo & something: this is based on a list of labels

Here is an example of the configuration file:

```yaml
rules:
- name: master
terms:
- master
alternatives:
- primary
- main
outputs:
prometheus:
labels:
repo: sample
something: else
pushgateway: "http://pushgateway.example.org:9091"
```

### Checkstyle

!!! example ""
Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,28 @@ require (

require (
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-git/v5 v5.4.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
Expand All @@ -42,6 +50,7 @@ require (
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
125 changes: 124 additions & 1 deletion go.sum

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,21 @@ import (
"gopkg.in/yaml.v2"
)

type Prometheus struct {
Labels map[string]string `yaml:"labels,omitempty"`
PushgatewayURL string `yaml:"pushgateway"`
}

// Config contains a list of rules
type Config struct {
Rules []*rule.Rule `yaml:"rules"`
IgnoreFiles []string `yaml:"ignore_files"`
SuccessExitMessage *string `yaml:"success_exit_message"`
IncludeNote bool `yaml:"include_note"`
ExcludeCategories []string `yaml:"exclude_categories"`
Outputs struct {
Prometheus *Prometheus `yaml:"prometheus"`
} `yaml:"outputs"`
}

// NewConfig returns a new Config
Expand Down
10 changes: 9 additions & 1 deletion pkg/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/get-woke/woke/pkg/result"

env "github.com/caitlinelfring/go-env-default"
config "github.com/get-woke/woke/pkg/config"
"github.com/rs/zerolog/log"
)

Expand Down Expand Up @@ -35,6 +36,10 @@ const (
// https://docs.sonarqube.org/latest/analysis/generic-issue/
OutFormatSonarQube = "sonarqube"

// OutFormatPrometheus outputs in prometheus format
// https://prometheus.io/docs/instrumenting/writing_exporters/
OutFormatPrometheus = "prometheus"

// OutFormatCheckstyle outputs in checkstyle format.
// https://github.com/checkstyle/checkstyle
OutFormatCheckstyle = "checkstyle"
Expand All @@ -47,14 +52,15 @@ var OutFormats = []string{
OutFormatGitHubActions,
OutFormatJSON,
OutFormatSonarQube,
OutFormatPrometheus,
OutFormatCheckstyle,
}

// OutFormatsString is all OutFormats, as a comma-separated string
var OutFormatsString = strings.Join(OutFormats, ",")

// NewPrinter returns a valid new Printer from a string, or an error if the printer is invalid
func NewPrinter(f string, w io.Writer) (Printer, error) {
func NewPrinter(f string, w io.Writer, c *config.Config) (Printer, error) {
var p Printer
switch f {
case OutFormatText:
Expand All @@ -67,6 +73,8 @@ func NewPrinter(f string, w io.Writer) (Printer, error) {
p = NewJSON(w)
case OutFormatSonarQube:
p = NewSonarQube(w)
case OutFormatPrometheus:
p = NewPrometheus(w, c.Outputs.Prometheus)
case OutFormatCheckstyle:
p = NewCheckstyle(w)
default:
Expand Down
5 changes: 3 additions & 2 deletions pkg/printer/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"io"
"testing"

config "github.com/get-woke/woke/pkg/config"
"github.com/stretchr/testify/assert"
)

Expand All @@ -21,11 +22,11 @@ func TestCreatePrinter(t *testing.T) {
}

for _, test := range tests {
p, err := NewPrinter(test.OutFormat, io.Discard)
p, err := NewPrinter(test.OutFormat, io.Discard, new(config.Config))
assert.NoError(t, err)
assert.IsType(t, test.Type, p)
}

_, err := NewPrinter("invalid-printer", io.Discard)
_, err := NewPrinter("invalid-printer", io.Discard, new(config.Config))
assert.Errorf(t, err, "invalid-printer is not a valid printer type")
}
93 changes: 93 additions & 0 deletions pkg/printer/prometheus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Copyright 2022 Cisco and its affiliates
* All rights reserved.
**/

package printer

import (
"fmt"
"io"

config "github.com/get-woke/woke/pkg/config"
"github.com/get-woke/woke/pkg/result"
"github.com/prometheus/client_golang/prometheus"
push "github.com/prometheus/client_golang/prometheus/push"
"github.com/rs/zerolog/log"
)

// Prometheus is a output format with prometheus metrics
type Prometheus struct {
writer io.Writer
labels map[string]string
pushgatewayURL string
}

// NewPrometheus returns a Prometheus Printer with color optionally disabled
func NewPrometheus(w io.Writer, c *config.Prometheus) *Prometheus {
return &Prometheus{
writer: w,
labels: c.Labels,
pushgatewayURL: c.PushgatewayURL,
}
}

func (t *Prometheus) PrintSuccessExitMessage() bool {
return true
}

// Print prints the file results or send it to pushgateway if URL provided
func (t *Prometheus) Print(fs *result.FileResults) error {

for _, r := range fs.Results {
pos := fmt.Sprintf("%d:%d-%d",
r.GetStartPosition().Line,
r.GetStartPosition().Column,
r.GetEndPosition().Column)

labelString := ""

if len(t.labels) > 0 {
first := true
for k, v := range t.labels {
if first {
labelString += ", "
first = false
}
labelString += k + "=" + v + ", "
}
labelString = labelString[:len(labelString)-2]
}
Comment on lines +50 to +60
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is duplicated across prometheus.go and pushgateway.go. Can they be consolidated? I would also recommend creating a test for this functionality specifically

fmt.Fprintf(t.writer, "woke_result{file=\"%s:%s\", term=\"%s\"%s} 1 \n",
fs.Filename, pos, r.GetRuleName(), labelString)

if t.pushgatewayURL != "" {
pusher := push.New(t.pushgatewayURL, "woke")
woke_result := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "woke_result",
Help: "Inclusive woke result",
})
for k, v := range t.labels {
pusher = pusher.Grouping(k, v)
}
pusher = pusher.Grouping("term", r.GetRuleName()).
Grouping("instance", fs.Filename+":"+pos).
Collector(woke_result)

if err := pusher.Push(); err != nil {
log.Error().Err(err).Msg("Could not push woke_result to Pushgateway")
} else {
log.Debug().Msg("push woke_result to Pushgateway done")
}
}

}

return nil
}

func (t *Prometheus) Start() {
}

func (t *Prometheus) End() {
}
48 changes: 48 additions & 0 deletions pkg/printer/prometheus_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright 2022 Cisco and its affiliates
* All rights reserved.
**/

package printer

import (
"bytes"
"fmt"
config "github.com/get-woke/woke/pkg/config"
"github.com/stretchr/testify/assert"
"testing"
)

func TestPrometheus_Print(t *testing.T) {
buf := new(bytes.Buffer)
p := NewPrometheus(buf, new(config.Config))
res := generateFileResult()
assert.NoError(t, p.Print(res))
got := buf.String()
fmt.Printf("buf: %s", buf)
fmt.Printf("res: %s", res.Results[0].String())
expected := fmt.Sprintf("woke_result{file=\"foo.txt:1:6-15\", term=\"%s\"} 1 \n", res.Results[0].GetRuleName())
assert.Equal(t, expected, got)
}

func TestPrometheus_Start(t *testing.T) {
buf := new(bytes.Buffer)
p := NewPrometheus(buf, new(config.Config))
p.Start()
got := buf.String()
assert.Equal(t, ``, got)
}

func TestPrometheus_End(t *testing.T) {
buf := new(bytes.Buffer)
p := NewPrometheus(buf, new(config.Config))
p.End()
got := buf.String()
assert.Equal(t, ``, got)
}

func TestPrometheus_PrintSuccessExitMessage(t *testing.T) {
buf := new(bytes.Buffer)
p := NewPrometheus(buf, new(config.Config))
assert.Equal(t, true, p.PrintSuccessExitMessage())
}
Loading