1
0
Fork 0
mirror of https://github.com/kyverno/policy-reporter.git synced 2024-12-14 11:57:32 +00:00

Policy report crd update (#24)

* Add Support for Properties and Timestamp
* Prevent resending violations after Kyverno cleanup
This commit is contained in:
Frank Jogeleit 2021-04-16 11:06:06 +02:00 committed by GitHub
parent 15ad03d552
commit 4a436eb2a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 527 additions and 180 deletions

View file

@ -1,5 +1,11 @@
# Changelog
## 1.2.0
* Support for (Cluster)PolicyReport CRD Properties in Target Output
* Support for (Cluster)PolicyReport CRD Timestamp in Target Output
* Fix resend violations after Kyverno Cleanup with ResultHashes
## 1.1.0
* Added PolicyReport Category to Metrics

View file

@ -5,8 +5,8 @@ description: |
It creates Prometheus Metrics and can send rule validation events to different targets like Loki, Elasticsearch, Slack or Discord
type: application
version: 1.1.0
appVersion: 1.1.0
version: 1.2.0
appVersion: 1.2.0
dependencies:
- name: monitoring

View file

@ -1,7 +1,7 @@
image:
repository: fjogeleit/policy-reporter
pullPolicy: IfNotPresent
tag: 1.1.0
tag: 1.2.0
imagePullSecrets: []

1
go.mod
View file

@ -7,6 +7,7 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/magiconair/properties v1.8.4 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.1 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/prometheus/client_golang v1.9.0

2
go.sum
View file

@ -292,6 +292,8 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/hashstructure/v2 v2.0.1 h1:L60q1+q7cXE4JeEJJKMnh2brFIe3rZxCihYAB61ypAY=
github.com/mitchellh/hashstructure/v2 v2.0.1/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=

View file

@ -20,6 +20,7 @@ type clusterPolicyReportClient struct {
startUp time.Time
skipExisting bool
started bool
modifyHash map[string]uint64
}
func (c *clusterPolicyReportClient) RegisterCallback(cb report.ClusterPolicyReportCallback) {
@ -125,6 +126,12 @@ func (c *clusterPolicyReportClient) RegisterPolicyResultWatcher(skipExisting boo
c.RegisterCallback(func(s watch.EventType, cpr report.ClusterPolicyReport, opr report.ClusterPolicyReport) {
switch s {
case watch.Added:
if len(cpr.Results) == 0 {
break
}
c.modifyHash[cpr.GetIdentifier()] = cpr.ResultHash()
preExisted := cpr.CreationTimestamp.Before(c.startUp)
if c.skipExisting && preExisted {
@ -145,6 +152,19 @@ func (c *clusterPolicyReportClient) RegisterPolicyResultWatcher(skipExisting boo
wg.Wait()
case watch.Modified:
if len(cpr.Results) == 0 {
break
}
newHash := cpr.ResultHash()
if hash, ok := c.modifyHash[cpr.GetIdentifier()]; ok {
if newHash == hash {
break
}
}
c.modifyHash[cpr.GetIdentifier()] = newHash
diff := cpr.GetNewResults(opr)
wg := sync.WaitGroup{}
@ -160,6 +180,10 @@ func (c *clusterPolicyReportClient) RegisterPolicyResultWatcher(skipExisting boo
}
wg.Wait()
case watch.Deleted:
if _, ok := c.modifyHash[cpr.GetIdentifier()]; ok {
delete(c.modifyHash, cpr.GetIdentifier())
}
}
})
}
@ -167,9 +191,10 @@ func (c *clusterPolicyReportClient) RegisterPolicyResultWatcher(skipExisting boo
// NewPolicyReportClient creates a new PolicyReportClient based on the kubernetes go-client
func NewClusterPolicyReportClient(client PolicyReportAdapter, store *report.ClusterPolicyReportStore, mapper Mapper, startUp time.Time) report.ClusterPolicyClient {
return &clusterPolicyReportClient{
policyAPI: client,
store: store,
mapper: mapper,
startUp: startUp,
policyAPI: client,
store: store,
mapper: mapper,
startUp: startUp,
modifyHash: make(map[string]uint64),
}
}

View file

@ -140,12 +140,13 @@ func (m *mapper) mapResult(result map[string]interface{}) report.Result {
status := result["status"].(report.Status)
r := report.Result{
Message: result["message"].(string),
Policy: result["policy"].(string),
Status: status,
Scored: result["scored"].(bool),
Priority: report.PriorityFromStatus(status),
Resources: resources,
Message: result["message"].(string),
Policy: result["policy"].(string),
Status: status,
Scored: result["scored"].(bool),
Priority: report.PriorityFromStatus(status),
Resources: resources,
Properties: make(map[string]string, 0),
}
if severity, ok := result["severity"]; ok {
@ -164,6 +165,21 @@ func (m *mapper) mapResult(result map[string]interface{}) report.Result {
r.Category = category.(string)
}
if created, ok := result["timestamp"]; ok {
time, err := time.Parse("2006-01-02T15:04:05Z", created.(string))
if err == nil {
r.Timestamp = time
}
}
if props, ok := result["properties"]; ok {
if properties, ok := props.(map[string]interface{}); ok {
for property, value := range properties {
r.Properties[property] = value.(string)
}
}
}
return r
}

View file

@ -28,13 +28,14 @@ var policyMap = map[string]interface{}{
},
"results": []interface{}{
map[string]interface{}{
"message": "message",
"status": "fail",
"scored": true,
"policy": "required-label",
"rule": "app-label-required",
"category": "test",
"severity": "high",
"message": "message",
"status": "fail",
"scored": true,
"policy": "required-label",
"rule": "app-label-required",
"timestamp": "2021-02-23T15:10:00Z",
"category": "test",
"severity": "high",
"resources": []interface{}{
map[string]interface{}{
"apiVersion": "v1",
@ -44,6 +45,9 @@ var policyMap = map[string]interface{}{
"uid": "dfd57c50-f30c-4729-b63f-b1954d8988d1",
},
},
"properties": map[string]interface{}{
"version": "1.2.0",
},
},
map[string]interface{}{
"message": "message 2",
@ -163,6 +167,12 @@ func Test_MapPolicyReport(t *testing.T) {
if result1.Severity != report.High {
t.Errorf("Expected Severity '%s' (acutal %s)", report.High, result1.Severity)
}
if result1.Timestamp.Format("2006-01-02T15:04:05Z") != "2021-02-23T15:10:00Z" {
t.Errorf("Expected Timestamp '2021-02-23T15:10:00Z' (acutal %s)", result1.Timestamp.Format("2006-01-02T15:04:05Z"))
}
if result1.Properties["version"] != "1.2.0" {
t.Errorf("Expected Property '1.2.0' (acutal %s)", result1.Properties["version"])
}
resource := result1.Resources[0]
if resource.APIVersion != "v1" {

View file

@ -20,6 +20,7 @@ type policyReportClient struct {
startUp time.Time
skipExisting bool
started bool
modifyHash map[string]uint64
}
func (c *policyReportClient) RegisterCallback(cb report.PolicyReportCallback) {
@ -126,24 +127,64 @@ func (c *policyReportClient) RegisterPolicyResultWatcher(skipExisting bool) {
func(e watch.EventType, pr report.PolicyReport, or report.PolicyReport) {
switch e {
case watch.Added:
if len(pr.Results) == 0 {
break
}
c.modifyHash[pr.GetIdentifier()] = pr.ResultHash()
preExisted := pr.CreationTimestamp.Before(c.startUp)
if c.skipExisting && preExisted {
break
}
for _, result := range pr.Results {
wg := sync.WaitGroup{}
wg.Add(len(pr.Results) * len(c.resultCallbacks))
for _, r := range pr.Results {
for _, cb := range c.resultCallbacks {
cb(result, preExisted)
go func(callback report.PolicyResultCallback, result report.Result) {
callback(result, preExisted)
wg.Done()
}(cb, r)
}
}
wg.Wait()
case watch.Modified:
diff := pr.GetNewResults(or)
for _, result := range diff {
for _, cb := range c.resultCallbacks {
cb(result, false)
if len(pr.Results) == 0 {
break
}
newHash := pr.ResultHash()
if hash, ok := c.modifyHash[pr.GetIdentifier()]; ok {
if newHash == hash {
break
}
}
c.modifyHash[pr.GetIdentifier()] = newHash
diff := pr.GetNewResults(or)
wg := sync.WaitGroup{}
wg.Add(len(diff) * len(c.resultCallbacks))
for _, r := range diff {
for _, cb := range c.resultCallbacks {
go func(callback report.PolicyResultCallback, result report.Result) {
callback(result, false)
wg.Done()
}(cb, r)
}
}
wg.Wait()
case watch.Deleted:
if _, ok := c.modifyHash[pr.GetIdentifier()]; ok {
delete(c.modifyHash, pr.GetIdentifier())
}
}
})
}
@ -151,9 +192,10 @@ func (c *policyReportClient) RegisterPolicyResultWatcher(skipExisting bool) {
// NewPolicyReportClient creates a new PolicyReportClient based on the kubernetes go-client
func NewPolicyReportClient(client PolicyReportAdapter, store *report.PolicyReportStore, mapper Mapper, startUp time.Time) report.PolicyClient {
return &policyReportClient{
policyAPI: client,
store: store,
mapper: mapper,
startUp: startUp,
policyAPI: client,
store: store,
mapper: mapper,
startUp: startUp,
modifyHash: make(map[string]uint64),
}
}

View file

@ -158,3 +158,149 @@ func Test_ResultClient_RegisterPolicyResultWatcher(t *testing.T) {
t.Error("Should receive 3 Result from all PolicyReports")
}
}
func Test_ResultClient_SkipReportsWithoutResults(t *testing.T) {
_, k8sCMClient := newFakeAPI()
k8sCMClient.Create(context.Background(), configMap, metav1.CreateOptions{})
fakeAdapter := NewPolicyReportAdapter()
mapper := NewMapper(k8sCMClient)
pClient := kubernetes.NewPolicyReportClient(fakeAdapter, report.NewPolicyReportStore(), mapper, time.Now())
cpClient := kubernetes.NewClusterPolicyReportClient(fakeAdapter, report.NewClusterPolicyReportStore(), mapper, time.Now())
client := kubernetes.NewPolicyResultClient(pClient, cpClient)
client.RegisterPolicyResultWatcher(false)
wg := sync.WaitGroup{}
wg.Add(3)
results := make([]report.Result, 0, 3)
client.RegisterPolicyResultCallback(func(r report.Result, b bool) {
results = append(results, r)
wg.Done()
})
go pClient.StartWatching()
go cpClient.StartWatching()
var policyMap2 = map[string]interface{}{
"metadata": map[string]interface{}{
"name": "policy-report",
"namespace": "test",
"creationTimestamp": "2021-02-23T15:00:00Z",
},
"summary": map[string]interface{}{
"pass": int64(1),
"skip": int64(2),
"warn": int64(3),
"fail": int64(4),
"error": int64(5),
},
"results": []interface{}{},
}
var clusterPolicyMap2 = map[string]interface{}{
"metadata": map[string]interface{}{
"name": "clusterpolicy-report",
"creationTimestamp": "2021-02-23T15:00:00Z",
},
"summary": map[string]interface{}{
"pass": int64(0),
"skip": int64(0),
"warn": int64(0),
"fail": int64(0),
"error": int64(0),
},
"results": []interface{}{},
}
fakeAdapter.clusterPolicyWatcher.Add(&unstructured.Unstructured{Object: clusterPolicyMap2})
fakeAdapter.clusterPolicyWatcher.Modify(&unstructured.Unstructured{Object: clusterPolicyMap2})
fakeAdapter.clusterPolicyWatcher.Modify(&unstructured.Unstructured{Object: clusterPolicyMap})
fakeAdapter.policyWatcher.Add(&unstructured.Unstructured{Object: policyMap2})
fakeAdapter.policyWatcher.Modify(&unstructured.Unstructured{Object: policyMap2})
fakeAdapter.policyWatcher.Modify(&unstructured.Unstructured{Object: policyMap})
wg.Wait()
if len(results) != 3 {
t.Error("Should receive 3 Result from none empty PolicyReport and ClusterPolicyReport Modify")
}
}
func Test_ResultClient_SkipReportsCleanUpEvents(t *testing.T) {
_, k8sCMClient := newFakeAPI()
k8sCMClient.Create(context.Background(), configMap, metav1.CreateOptions{})
fakeAdapter := NewPolicyReportAdapter()
mapper := NewMapper(k8sCMClient)
pClient := kubernetes.NewPolicyReportClient(fakeAdapter, report.NewPolicyReportStore(), mapper, time.Now())
cpClient := kubernetes.NewClusterPolicyReportClient(fakeAdapter, report.NewClusterPolicyReportStore(), mapper, time.Now())
client := kubernetes.NewPolicyResultClient(pClient, cpClient)
client.RegisterPolicyResultWatcher(false)
wg := sync.WaitGroup{}
wg.Add(3)
results := make([]report.Result, 0, 3)
client.RegisterPolicyResultCallback(func(r report.Result, b bool) {
results = append(results, r)
wg.Done()
})
go pClient.StartWatching()
go cpClient.StartWatching()
var policyMap2 = map[string]interface{}{
"metadata": map[string]interface{}{
"name": "policy-report",
"namespace": "test",
"creationTimestamp": "2021-02-23T15:00:00Z",
},
"summary": map[string]interface{}{
"pass": int64(0),
"skip": int64(0),
"warn": int64(0),
"fail": int64(0),
"error": int64(0),
},
"results": []interface{}{},
}
var clusterPolicyMap2 = map[string]interface{}{
"metadata": map[string]interface{}{
"name": "clusterpolicy-report",
"creationTimestamp": "2021-02-23T15:00:00Z",
},
"summary": map[string]interface{}{
"pass": int64(0),
"skip": int64(0),
"warn": int64(0),
"fail": int64(0),
"error": int64(0),
},
"results": []interface{}{},
}
fakeAdapter.clusterPolicyWatcher.Add(&unstructured.Unstructured{Object: clusterPolicyMap})
fakeAdapter.clusterPolicyWatcher.Modify(&unstructured.Unstructured{Object: clusterPolicyMap2})
fakeAdapter.clusterPolicyWatcher.Modify(&unstructured.Unstructured{Object: clusterPolicyMap})
fakeAdapter.policyWatcher.Add(&unstructured.Unstructured{Object: policyMap})
fakeAdapter.policyWatcher.Modify(&unstructured.Unstructured{Object: policyMap2})
fakeAdapter.policyWatcher.Modify(&unstructured.Unstructured{Object: policyMap})
wg.Wait()
if len(results) != 3 {
t.Error("Should receive 3 Results from the initial add events, not from the cleanup modify events")
}
}

View file

@ -28,49 +28,17 @@ func CreateClusterPolicyReportMetricsCallback() report.ClusterPolicyReportCallba
updateClusterPolicyGauge(policyGauge, report)
for _, rule := range report.Results {
res := rule.Resources[0]
ruleGauge.WithLabelValues(
rule.Rule,
rule.Policy,
report.Name,
res.Kind,
res.Name,
rule.Status,
rule.Severity,
rule.Category,
).Set(1)
ruleGauge.With(generateClusterResultLabels(report, rule)).Set(1)
}
case watch.Modified:
updateClusterPolicyGauge(policyGauge, report)
for _, rule := range oldReport.Results {
res := rule.Resources[0]
ruleGauge.DeleteLabelValues(
rule.Rule,
rule.Policy,
report.Name,
res.Kind,
res.Name,
rule.Status,
rule.Severity,
rule.Category,
)
ruleGauge.Delete(generateClusterResultLabels(oldReport, rule))
}
for _, rule := range report.Results {
res := rule.Resources[0]
ruleGauge.
WithLabelValues(
rule.Rule,
rule.Policy,
report.Name,
res.Kind,
res.Name,
rule.Status,
rule.Severity,
rule.Category,
).
Set(1)
ruleGauge.With(generateClusterResultLabels(report, rule)).Set(1)
}
case watch.Deleted:
policyGauge.DeleteLabelValues(report.Name, "Pass")
@ -80,22 +48,34 @@ func CreateClusterPolicyReportMetricsCallback() report.ClusterPolicyReportCallba
policyGauge.DeleteLabelValues(report.Name, "Skip")
for _, rule := range report.Results {
res := rule.Resources[0]
ruleGauge.DeleteLabelValues(
rule.Rule,
rule.Policy,
report.Name,
res.Kind,
res.Name,
rule.Status,
rule.Severity,
rule.Category,
)
ruleGauge.Delete(generateClusterResultLabels(report, rule))
}
}
}
}
func generateClusterResultLabels(report report.ClusterPolicyReport, result report.Result) prometheus.Labels {
labels := prometheus.Labels{
"rule": result.Rule,
"policy": result.Policy,
"report": report.Name,
"kind": "",
"name": "",
"status": result.Status,
"severity": result.Severity,
"category": result.Category,
}
if len(result.Resources) > 0 {
res := result.Resources[0]
labels["kind"] = res.Kind
labels["name"] = res.Name
}
return labels
}
func updateClusterPolicyGauge(policyGauge *prometheus.GaugeVec, report report.ClusterPolicyReport) {
policyGauge.
WithLabelValues(report.Name, "Pass").

View file

@ -28,54 +28,17 @@ func CreatePolicyReportMetricsCallback() report.PolicyReportCallback {
updatePolicyGauge(policyGauge, report)
for _, rule := range report.Results {
res := rule.Resources[0]
ruleGauge.
WithLabelValues(
report.Namespace,
rule.Rule,
rule.Policy,
report.Name,
res.Kind,
res.Name,
rule.Status,
rule.Severity,
rule.Category,
).
Set(1)
ruleGauge.With(generateResultLabels(report, rule)).Set(1)
}
case watch.Modified:
updatePolicyGauge(policyGauge, report)
for _, rule := range oldReport.Results {
res := rule.Resources[0]
ruleGauge.DeleteLabelValues(
report.Namespace,
rule.Rule,
rule.Policy,
report.Name,
res.Kind,
res.Name,
rule.Status,
rule.Severity,
rule.Category,
)
ruleGauge.Delete(generateResultLabels(oldReport, rule))
}
for _, rule := range report.Results {
res := rule.Resources[0]
ruleGauge.
WithLabelValues(
report.Namespace,
rule.Rule,
rule.Policy,
report.Name,
res.Kind,
res.Name,
rule.Status,
rule.Severity,
rule.Category,
).
Set(1)
ruleGauge.With(generateResultLabels(report, rule)).Set(1)
}
case watch.Deleted:
policyGauge.DeleteLabelValues(report.Namespace, report.Name, "Pass")
@ -85,24 +48,35 @@ func CreatePolicyReportMetricsCallback() report.PolicyReportCallback {
policyGauge.DeleteLabelValues(report.Namespace, report.Name, "Skip")
for _, rule := range report.Results {
res := rule.Resources[0]
ruleGauge.DeleteLabelValues(
report.Namespace,
rule.Rule,
rule.Policy,
report.Name,
res.Kind,
res.Name,
rule.Status,
rule.Severity,
rule.Category,
)
ruleGauge.Delete(generateResultLabels(report, rule))
}
}
}
}
func generateResultLabels(report report.PolicyReport, result report.Result) prometheus.Labels {
labels := prometheus.Labels{
"namespace": report.Namespace,
"rule": result.Rule,
"policy": result.Policy,
"report": report.Name,
"kind": "",
"name": "",
"status": result.Status,
"severity": result.Severity,
"category": result.Category,
}
if len(result.Resources) > 0 {
res := result.Resources[0]
labels["kind"] = res.Kind
labels["name"] = res.Name
}
return labels
}
func updatePolicyGauge(policyGauge *prometheus.GaugeVec, report report.PolicyReport) {
policyGauge.
WithLabelValues(report.Namespace, report.Name, "Pass").

View file

@ -3,7 +3,10 @@ package report
import (
"bytes"
"fmt"
"sort"
"time"
"github.com/mitchellh/hashstructure/v2"
)
// Status Enum defined for PolicyReport
@ -129,15 +132,17 @@ type Resource struct {
// Result from the PolicyReport spec wgpolicyk8s.io/v1alpha1.PolicyReportResult
type Result struct {
Message string
Policy string
Rule string
Priority Priority
Status Status
Severity Severity `json:",omitempty"`
Category string `json:",omitempty"`
Scored bool
Resources []Resource
Message string
Policy string
Rule string
Priority Priority
Status Status
Severity Severity `json:",omitempty"`
Category string `json:",omitempty"`
Scored bool
Timestamp time.Time
Resources []Resource
Properties map[string]string
}
// GetIdentifier returns a global unique Result identifier
@ -173,6 +178,24 @@ func (pr PolicyReport) GetIdentifier() string {
return fmt.Sprintf("%s__%s", pr.Namespace, pr.Name)
}
// ResultHash generates a has of the current result set
func (pr PolicyReport) ResultHash() uint64 {
list := make([]string, 0, len(pr.Results))
for id := range pr.Results {
list = append(list, id)
}
sort.Strings(list)
hash, err := hashstructure.Hash(list, hashstructure.FormatV2, nil)
if err != nil {
return 0
}
return hash
}
// GetNewResults filters already existing Results from the old PolicyReport and returns only the diff with new Results
func (pr PolicyReport) GetNewResults(or PolicyReport) []Result {
diff := make([]Result, 0)
@ -215,3 +238,21 @@ func (cr ClusterPolicyReport) GetNewResults(cor ClusterPolicyReport) []Result {
return diff
}
// ResultHash generates a has of the current result set
func (cr ClusterPolicyReport) ResultHash() uint64 {
list := make([]string, 0, len(cr.Results))
for id := range cr.Results {
list = append(list, id)
}
sort.Strings(list)
hash, err := hashstructure.Hash(list, hashstructure.FormatV2, nil)
if err != nil {
return 0
}
return hash
}

View file

@ -83,6 +83,28 @@ func Test_PolicyReport(t *testing.T) {
t.Error("Expected 1 new result in diff")
}
})
t.Run("Check PolicyReport.ResultHash", func(t *testing.T) {
preport := preport
preport.Results = map[string]report.Result{result1.GetIdentifier(): result1, result2.GetIdentifier(): result2}
hash := preport.ResultHash()
if hash != 5971778764232883205 {
t.Error("Expected '5971778764232883205' new result in diff")
}
})
t.Run("Check PolicyReport.ResultHash same with different order", func(t *testing.T) {
preport1 := preport
preport2 := preport
preport1.Results = map[string]report.Result{result2.GetIdentifier(): result2, result1.GetIdentifier(): result1}
preport2.Results = map[string]report.Result{result1.GetIdentifier(): result1, result2.GetIdentifier(): result2}
if preport2.ResultHash() != preport1.ResultHash() {
t.Error("Expected same hash with different order")
}
})
}
func Test_ClusterPolicyReport(t *testing.T) {
@ -104,6 +126,28 @@ func Test_ClusterPolicyReport(t *testing.T) {
t.Error("Expected 1 new result in diff")
}
})
t.Run("Check PolicyReport.ResultHash", func(t *testing.T) {
report1 := creport
report1.Results = map[string]report.Result{result1.GetIdentifier(): result1, result2.GetIdentifier(): result2}
hash := report1.ResultHash()
if hash != 5971778764232883205 {
t.Error("Expected '5971778764232883205' new result in diff")
}
})
t.Run("Check PolicyReport.ResultHash same with different order", func(t *testing.T) {
report1 := creport
report2 := creport
report1.Results = map[string]report.Result{result2.GetIdentifier(): result2, result1.GetIdentifier(): result1}
report2.Results = map[string]report.Result{result1.GetIdentifier(): result1, result2.GetIdentifier(): result2}
if report2.ResultHash() != report1.ResultHash() {
t.Error("Expected same hash with different order")
}
})
}
func Test_Result(t *testing.T) {

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"log"
"net/http"
"strings"
"github.com/fjogeleit/policy-reporter/pkg/report"
"github.com/fjogeleit/policy-reporter/pkg/target"
@ -74,6 +75,10 @@ func newPayload(result report.Result) payload {
embedFields = append(embedFields, embedField{"API Version", res.APIVersion, true})
}
for property, value := range result.Properties {
embedFields = append(embedFields, embedField{strings.Title(property), value, true})
}
embeds := make([]embed, 0, 1)
embeds = append(embeds, embed{
Title: "New Policy Report Result",

View file

@ -3,20 +3,22 @@ package discord_test
import (
"net/http"
"testing"
"time"
"github.com/fjogeleit/policy-reporter/pkg/report"
"github.com/fjogeleit/policy-reporter/pkg/target/discord"
)
var completeResult = report.Result{
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
Policy: "require-requests-and-limits-required",
Rule: "autogen-check-for-requests-and-limits",
Priority: report.WarningPriority,
Status: report.Fail,
Severity: report.High,
Category: "resources",
Scored: true,
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
Policy: "require-requests-and-limits-required",
Rule: "autogen-check-for-requests-and-limits",
Timestamp: time.Date(2021, time.February, 23, 15, 10, 0, 0, time.UTC),
Priority: report.WarningPriority,
Status: report.Fail,
Severity: report.High,
Category: "resources",
Scored: true,
Resources: []report.Resource{
{
APIVersion: "v1",
@ -26,6 +28,7 @@ var completeResult = report.Result{
UID: "536ab69f-1b3c-4bd9-9ba4-274a56188409",
},
},
Properties: map[string]string{"version": "1.2.0"},
}
var minimalResult = report.Result{

View file

@ -10,14 +10,15 @@ import (
)
var completeResult = report.Result{
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
Policy: "require-requests-and-limits-required",
Rule: "autogen-check-for-requests-and-limits",
Priority: report.WarningPriority,
Status: report.Fail,
Severity: report.High,
Category: "resources",
Scored: true,
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
Policy: "require-requests-and-limits-required",
Rule: "autogen-check-for-requests-and-limits",
Timestamp: time.Date(2021, time.February, 23, 15, 10, 0, 0, time.UTC),
Priority: report.WarningPriority,
Status: report.Fail,
Severity: report.High,
Category: "resources",
Scored: true,
Resources: []report.Resource{
{
APIVersion: "v1",
@ -27,6 +28,7 @@ var completeResult = report.Result{
UID: "536ab69f-1b3c-4bd9-9ba4-274a56188409",
},
},
Properties: map[string]string{"version": "1.2.0"},
}
type testClient struct {

View file

@ -32,7 +32,12 @@ type entry struct {
}
func newLokiPayload(result report.Result) payload {
le := entry{Ts: time.Now().Format(time.RFC3339), Line: "[" + strings.ToUpper(result.Priority.String()) + "] " + result.Message}
timestamp := time.Now()
if !result.Timestamp.IsZero() {
timestamp = result.Timestamp
}
le := entry{Ts: timestamp.Format(time.RFC3339), Line: "[" + strings.ToUpper(result.Priority.String()) + "] " + result.Message}
ls := stream{Entries: []entry{le}}
res := report.Resource{}
@ -65,6 +70,10 @@ func newLokiPayload(result report.Result) payload {
labels = append(labels, "namespace=\""+res.Namespace+"\"")
}
for property, value := range result.Properties {
labels = append(labels, property+"=\""+strings.ReplaceAll(value, "\"", "")+"\"")
}
ls.Labels = "{" + strings.Join(labels, ",") + "}"
return payload{Streams: []stream{ls}}

View file

@ -6,20 +6,22 @@ import (
"net/http"
"strings"
"testing"
"time"
"github.com/fjogeleit/policy-reporter/pkg/report"
"github.com/fjogeleit/policy-reporter/pkg/target/loki"
)
var completeResult = report.Result{
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
Policy: "require-requests-and-limits-required",
Rule: "autogen-check-for-requests-and-limits",
Priority: report.WarningPriority,
Status: report.Fail,
Severity: report.High,
Category: "resources",
Scored: true,
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
Policy: "require-requests-and-limits-required",
Rule: "autogen-check-for-requests-and-limits",
Timestamp: time.Date(2021, time.February, 23, 15, 10, 0, 0, time.UTC),
Priority: report.WarningPriority,
Status: report.Fail,
Severity: report.High,
Category: "resources",
Scored: true,
Resources: []report.Resource{
{
APIVersion: "v1",
@ -29,6 +31,7 @@ var completeResult = report.Result{
UID: "536ab69f-1b3c-4bd9-9ba4-274a56188409",
},
},
Properties: map[string]string{"version": "1.2.0"},
}
var minimalResult = report.Result{
@ -107,6 +110,9 @@ func Test_LokiTarget(t *testing.T) {
if !strings.Contains(labels, "namespace=\""+res.Namespace+"\"") {
t.Error("Missing Content for Label 'namespace'")
}
if !strings.Contains(labels, "version=\""+completeResult.Properties["version"]+"\"") {
t.Error("Missing Content for Label 'version'")
}
}
loki := loki.NewClient("http://localhost:3100", "", false, testClient{callback, 200})

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"log"
"net/http"
"strings"
"github.com/fjogeleit/policy-reporter/pkg/report"
"github.com/fjogeleit/policy-reporter/pkg/target"
@ -148,6 +149,24 @@ func (s *client) newPayload(result report.Result) payload {
att.Blocks = append(att.Blocks, block{Type: "section", Fields: []field{{Type: "mrkdwn", Text: "*Namespace*\n" + res.Namespace}}})
}
if len(result.Properties) > 0 {
att.Blocks = append(
att.Blocks,
block{Type: "section", Text: &text{Type: "mrkdwn", Text: "*Properties*"}},
)
propBlock := block{
Type: "section",
Fields: []field{},
}
for property, value := range result.Properties {
propBlock.Fields = append(propBlock.Fields, field{Type: "mrkdwn", Text: "*" + strings.Title(property) + "*\n" + value})
}
att.Blocks = append(att.Blocks, propBlock)
}
p.Attachments = append(p.Attachments, att)
return p

View file

@ -3,20 +3,22 @@ package slack_test
import (
"net/http"
"testing"
"time"
"github.com/fjogeleit/policy-reporter/pkg/report"
"github.com/fjogeleit/policy-reporter/pkg/target/slack"
)
var completeResult = report.Result{
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
Policy: "require-requests-and-limits-required",
Rule: "autogen-check-for-requests-and-limits",
Priority: report.WarningPriority,
Status: report.Fail,
Severity: report.High,
Category: "resources",
Scored: true,
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
Policy: "require-requests-and-limits-required",
Rule: "autogen-check-for-requests-and-limits",
Timestamp: time.Date(2021, time.February, 23, 15, 10, 0, 0, time.UTC),
Priority: report.WarningPriority,
Status: report.Fail,
Severity: report.High,
Category: "resources",
Scored: true,
Resources: []report.Resource{
{
APIVersion: "v1",
@ -26,6 +28,7 @@ var completeResult = report.Result{
UID: "536ab69f-1b3c-4bd9-9ba4-274a56188409",
},
},
Properties: map[string]string{"version": "1.2.0"},
}
var minimalResult = report.Result{

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"log"
"net/http"
"strings"
"time"
"github.com/fjogeleit/policy-reporter/pkg/report"
@ -85,10 +86,19 @@ func newPayload(result report.Result) payload {
facts = append(facts, fact{"API Version", res.APIVersion})
}
for property, value := range result.Properties {
facts = append(facts, fact{strings.Title(property), value})
}
timestamp := time.Now()
if !result.Timestamp.IsZero() {
timestamp = result.Timestamp
}
sections := make([]section, 0, 1)
sections = append(sections, section{
Title: "New Policy Report Result",
SubTitle: time.Now().Format(time.RFC3339),
SubTitle: timestamp.Format(time.RFC3339),
Text: result.Message,
Facts: facts,
})

View file

@ -4,20 +4,22 @@ import (
"encoding/json"
"net/http"
"testing"
"time"
"github.com/fjogeleit/policy-reporter/pkg/report"
"github.com/fjogeleit/policy-reporter/pkg/target/teams"
)
var completeResult = report.Result{
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
Policy: "require-requests-and-limits-required",
Rule: "autogen-check-for-requests-and-limits",
Priority: report.WarningPriority,
Status: report.Fail,
Severity: report.High,
Category: "resources",
Scored: true,
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
Policy: "require-requests-and-limits-required",
Rule: "autogen-check-for-requests-and-limits",
Priority: report.WarningPriority,
Status: report.Fail,
Severity: report.High,
Timestamp: time.Date(2021, time.February, 23, 15, 10, 0, 0, time.UTC),
Category: "resources",
Scored: true,
Resources: []report.Resource{
{
APIVersion: "v1",
@ -27,6 +29,7 @@ var completeResult = report.Result{
UID: "536ab69f-1b3c-4bd9-9ba4-274a56188409",
},
},
Properties: map[string]string{"version": "1.2.0"},
}
var minimalErrorResult = report.Result{