mirror of
https://github.com/kyverno/policy-reporter.git
synced 2024-12-14 11:57:32 +00:00
ID Generation (#428)
Signed-off-by: Frank Jogeleit <frank.jogeleit@lovoo.com>
This commit is contained in:
parent
1edff60f57
commit
dd150ee3b6
20 changed files with 716 additions and 130 deletions
|
@ -385,6 +385,12 @@ redis:
|
|||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.sourceConfig }}
|
||||
sourceConfig:
|
||||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
|
||||
|
||||
logging:
|
||||
encoding: {{ .Values.logging.encoding }}
|
||||
logLevel: {{ include "policyreporter.logLevel" . }}
|
||||
|
|
|
@ -163,6 +163,13 @@ reportFilter:
|
|||
# Disable the processing of ClusterPolicyReports
|
||||
disabled: false
|
||||
|
||||
# customize source specific logic like result ID generation
|
||||
sourceConfig: {}
|
||||
# sourcename:
|
||||
# customID:
|
||||
# enabled: true
|
||||
# fields: ["resource", "policy", "rule", "category", "result", "message"]
|
||||
|
||||
# enable policy-report-ui
|
||||
ui:
|
||||
enabled: false
|
||||
|
|
|
@ -347,34 +347,44 @@ type Database struct {
|
|||
MountedSecret string `mapstructure:"mountedSecret"`
|
||||
}
|
||||
|
||||
type CustomID struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Fields []string `mapstructure:"fields"`
|
||||
}
|
||||
|
||||
type SourceConfig struct {
|
||||
CustomID `mapstructure:"customID"`
|
||||
}
|
||||
|
||||
// Config of the PolicyReporter
|
||||
type Config struct {
|
||||
Version string
|
||||
Namespace string `mapstructure:"namespace"`
|
||||
Loki *Loki `mapstructure:"loki"`
|
||||
Elasticsearch *Elasticsearch `mapstructure:"elasticsearch"`
|
||||
Slack *Slack `mapstructure:"slack"`
|
||||
Discord *Discord `mapstructure:"discord"`
|
||||
Teams *Teams `mapstructure:"teams"`
|
||||
S3 *S3 `mapstructure:"s3"`
|
||||
Kinesis *Kinesis `mapstructure:"kinesis"`
|
||||
SecurityHub *SecurityHub `mapstructure:"securityHub"`
|
||||
GCS *GCS `mapstructure:"gcs"`
|
||||
UI *UI `mapstructure:"ui"`
|
||||
Webhook *Webhook `mapstructure:"webhook"`
|
||||
Telegram *Telegram `mapstructure:"telegram"`
|
||||
GoogleChat *GoogleChat `mapstructure:"googleChat"`
|
||||
API API `mapstructure:"api"`
|
||||
WorkerCount int `mapstructure:"worker"`
|
||||
DBFile string `mapstructure:"dbfile"`
|
||||
Metrics Metrics `mapstructure:"metrics"`
|
||||
REST REST `mapstructure:"rest"`
|
||||
ReportFilter ReportFilter `mapstructure:"reportFilter"`
|
||||
Redis Redis `mapstructure:"redis"`
|
||||
Profiling Profiling `mapstructure:"profiling"`
|
||||
EmailReports EmailReports `mapstructure:"emailReports"`
|
||||
LeaderElection LeaderElection `mapstructure:"leaderElection"`
|
||||
K8sClient K8sClient `mapstructure:"k8sClient"`
|
||||
Logging Logging `mapstructure:"logging"`
|
||||
Database Database `mapstructure:"database"`
|
||||
Namespace string `mapstructure:"namespace"`
|
||||
Loki *Loki `mapstructure:"loki"`
|
||||
Elasticsearch *Elasticsearch `mapstructure:"elasticsearch"`
|
||||
Slack *Slack `mapstructure:"slack"`
|
||||
Discord *Discord `mapstructure:"discord"`
|
||||
Teams *Teams `mapstructure:"teams"`
|
||||
S3 *S3 `mapstructure:"s3"`
|
||||
Kinesis *Kinesis `mapstructure:"kinesis"`
|
||||
SecurityHub *SecurityHub `mapstructure:"securityHub"`
|
||||
GCS *GCS `mapstructure:"gcs"`
|
||||
UI *UI `mapstructure:"ui"`
|
||||
Webhook *Webhook `mapstructure:"webhook"`
|
||||
Telegram *Telegram `mapstructure:"telegram"`
|
||||
GoogleChat *GoogleChat `mapstructure:"googleChat"`
|
||||
API API `mapstructure:"api"`
|
||||
WorkerCount int `mapstructure:"worker"`
|
||||
DBFile string `mapstructure:"dbfile"`
|
||||
Metrics Metrics `mapstructure:"metrics"`
|
||||
REST REST `mapstructure:"rest"`
|
||||
ReportFilter ReportFilter `mapstructure:"reportFilter"`
|
||||
Redis Redis `mapstructure:"redis"`
|
||||
Profiling Profiling `mapstructure:"profiling"`
|
||||
EmailReports EmailReports `mapstructure:"emailReports"`
|
||||
LeaderElection LeaderElection `mapstructure:"leaderElection"`
|
||||
K8sClient K8sClient `mapstructure:"k8sClient"`
|
||||
Logging Logging `mapstructure:"logging"`
|
||||
Database Database `mapstructure:"database"`
|
||||
SourceConfig map[string]SourceConfig `mapstructure:"sourceConfig"`
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
goredis "github.com/go-redis/redis/v8"
|
||||
|
@ -33,6 +34,7 @@ import (
|
|||
"github.com/kyverno/policy-reporter/pkg/listener"
|
||||
"github.com/kyverno/policy-reporter/pkg/listener/metrics"
|
||||
"github.com/kyverno/policy-reporter/pkg/report"
|
||||
"github.com/kyverno/policy-reporter/pkg/report/result"
|
||||
"github.com/kyverno/policy-reporter/pkg/target"
|
||||
"github.com/kyverno/policy-reporter/pkg/validate"
|
||||
)
|
||||
|
@ -173,6 +175,19 @@ func (r *Resolver) EventPublisher() report.EventPublisher {
|
|||
return r.publisher
|
||||
}
|
||||
|
||||
func (r *Resolver) CustomIDGenerators() map[string]result.IDGenerator {
|
||||
generators := make(map[string]result.IDGenerator)
|
||||
for s, c := range r.config.SourceConfig {
|
||||
if !c.Enabled || len(c.Fields) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
generators[strings.ToLower(s)] = result.NewIDGenerator(c.Fields)
|
||||
}
|
||||
|
||||
return generators
|
||||
}
|
||||
|
||||
// EventPublisher resolver method
|
||||
func (r *Resolver) Queue() (*kubernetes.Queue, error) {
|
||||
client, err := r.CRDClient()
|
||||
|
@ -184,6 +199,7 @@ func (r *Resolver) Queue() (*kubernetes.Queue, error) {
|
|||
kubernetes.NewDebouncer(1*time.Minute, r.EventPublisher()),
|
||||
workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "report-queue"),
|
||||
client,
|
||||
result.NewReconditioner(r.CustomIDGenerators()),
|
||||
), nil
|
||||
}
|
||||
|
||||
|
|
|
@ -190,6 +190,15 @@ var testConfig = &config.Config{
|
|||
Webhook: "http://localhost:900/webhook",
|
||||
Channels: []*config.GoogleChat{{}},
|
||||
},
|
||||
SourceConfig: map[string]config.SourceConfig{
|
||||
"test": {
|
||||
CustomID: config.CustomID{
|
||||
Enabled: true,
|
||||
Fields: []string{"resource"},
|
||||
},
|
||||
},
|
||||
"default": {},
|
||||
},
|
||||
}
|
||||
|
||||
func Test_ResolveTargets(t *testing.T) {
|
||||
|
@ -580,3 +589,12 @@ func Test_ResolveEnableLeaderElection(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ResolveCustomIDGenerators(t *testing.T) {
|
||||
resolver := config.NewResolver(testConfig, nil)
|
||||
|
||||
generators := resolver.CustomIDGenerators()
|
||||
if len(generators) != 1 {
|
||||
t.Error("only enabled custom id config should be mapped")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,8 @@ package v1alpha2
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/segmentio/fasthash/fnv1a"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
@ -243,32 +241,6 @@ func (r *PolicyReportResult) GetKind() string {
|
|||
}
|
||||
|
||||
func (r *PolicyReportResult) GetID() string {
|
||||
if r.ID != "" {
|
||||
return r.ID
|
||||
}
|
||||
|
||||
if id, ok := r.Properties[ResultIDKey]; ok {
|
||||
r.ID = id
|
||||
|
||||
return r.ID
|
||||
}
|
||||
|
||||
h1 := fnv1a.Init64
|
||||
|
||||
res := r.GetResource()
|
||||
if res != nil {
|
||||
h1 = fnv1a.AddString64(h1, res.Name)
|
||||
h1 = fnv1a.AddString64(h1, string(res.UID))
|
||||
}
|
||||
|
||||
h1 = fnv1a.AddString64(h1, r.Policy)
|
||||
h1 = fnv1a.AddString64(h1, r.Rule)
|
||||
h1 = fnv1a.AddString64(h1, string(r.Result))
|
||||
h1 = fnv1a.AddString64(h1, r.Category)
|
||||
h1 = fnv1a.AddString64(h1, r.Message)
|
||||
|
||||
r.ID = strconv.FormatUint(h1, 10)
|
||||
|
||||
return r.ID
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/report/result"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
|
@ -98,7 +99,7 @@ func TestCommon(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestPolicyReportResul(t *testing.T) {
|
||||
func TestPolicyReportResult(t *testing.T) {
|
||||
t.Run("GetResource Without Resources", func(t *testing.T) {
|
||||
r := &v1alpha2.PolicyReportResult{}
|
||||
|
||||
|
@ -128,32 +129,21 @@ func TestPolicyReportResul(t *testing.T) {
|
|||
}
|
||||
})
|
||||
t.Run("GetID from Result With Resource", func(t *testing.T) {
|
||||
r := &v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{{Name: "test", Kind: "Pod"}}}
|
||||
r := v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{{Name: "test", Kind: "Pod"}}}
|
||||
r.ID = result.NewIDGenerator(nil).Generate(&v1alpha2.PolicyReport{}, r)
|
||||
|
||||
if r.GetID() != "18007334074686647077" {
|
||||
t.Errorf("expected result kind to be '18007334074686647077', got :%s", r.GetID())
|
||||
}
|
||||
})
|
||||
t.Run("GetID from Result With ID Property", func(t *testing.T) {
|
||||
r := &v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{{Name: "test", Kind: "Pod"}}, Properties: map[string]string{"resultID": "result-id"}}
|
||||
r := v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{{Name: "test", Kind: "Pod"}}, Properties: map[string]string{"resultID": "result-id"}}
|
||||
r.ID = result.NewIDGenerator(nil).Generate(&v1alpha2.PolicyReport{}, r)
|
||||
|
||||
if r.GetID() != "result-id" {
|
||||
t.Errorf("expected result kind to be 'result-id', got :%s", r.GetID())
|
||||
}
|
||||
})
|
||||
t.Run("GetID cached", func(t *testing.T) {
|
||||
r := &v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{{Name: "test", Kind: "Pod"}}, Properties: map[string]string{"resultID": "result-id"}}
|
||||
|
||||
if r.GetID() != "result-id" {
|
||||
t.Errorf("expected result kind to be 'result-id', got :%s", r.GetID())
|
||||
}
|
||||
|
||||
r.Properties["resultID"] = "test"
|
||||
|
||||
if r.GetID() != "result-id" {
|
||||
t.Errorf("expected result ID doesn't change, got :%s", r.GetID())
|
||||
}
|
||||
})
|
||||
t.Run("ToResourceString with Namespace and Kind", func(t *testing.T) {
|
||||
r := &v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{{Name: "test", Namespace: "default", Kind: "Pod"}}}
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ import (
|
|||
|
||||
"github.com/segmentio/fasthash/fnv1a"
|
||||
"github.com/uptrace/bun"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
api "github.com/kyverno/policy-reporter/pkg/api/v1"
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/report"
|
||||
"github.com/kyverno/policy-reporter/pkg/report/result"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -109,13 +109,8 @@ func MapPolicyReport(r v1alpha2.ReportInterface) *PolicyReport {
|
|||
|
||||
func MapPolicyReportResults(polr v1alpha2.ReportInterface) []*PolicyReportResult {
|
||||
list := make([]*PolicyReportResult, 0, len(polr.GetResults()))
|
||||
for _, result := range polr.GetResults() {
|
||||
res := result.GetResource()
|
||||
if res == nil && polr.GetScope() != nil {
|
||||
res = polr.GetScope()
|
||||
} else if res == nil {
|
||||
res = &corev1.ObjectReference{}
|
||||
}
|
||||
for _, r := range polr.GetResults() {
|
||||
res := result.Resource(polr, r)
|
||||
|
||||
ns := res.Namespace
|
||||
if ns == "" {
|
||||
|
@ -123,7 +118,7 @@ func MapPolicyReportResults(polr v1alpha2.ReportInterface) []*PolicyReportResult
|
|||
}
|
||||
|
||||
list = append(list, &PolicyReportResult{
|
||||
ID: result.GetID(),
|
||||
ID: r.GetID(),
|
||||
PolicyReportID: polr.GetID(),
|
||||
Resource: Resource{
|
||||
APIVersion: res.APIVersion,
|
||||
|
@ -132,16 +127,16 @@ func MapPolicyReportResults(polr v1alpha2.ReportInterface) []*PolicyReportResult
|
|||
Namespace: ns,
|
||||
UID: string(res.UID),
|
||||
},
|
||||
Policy: result.Policy,
|
||||
Rule: result.Rule,
|
||||
Source: result.Source,
|
||||
Scored: result.Scored,
|
||||
Message: result.Message,
|
||||
Result: string(result.Result),
|
||||
Severity: string(result.Severity),
|
||||
Category: result.Category,
|
||||
Properties: result.Properties,
|
||||
Created: result.Timestamp.Seconds,
|
||||
Policy: r.Policy,
|
||||
Rule: r.Rule,
|
||||
Source: r.Source,
|
||||
Scored: r.Scored,
|
||||
Message: r.Message,
|
||||
Result: string(r.Result),
|
||||
Severity: string(r.Severity),
|
||||
Category: r.Category,
|
||||
Properties: r.Properties,
|
||||
Created: r.Timestamp.Seconds,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ var DefaultPolicyReport = &v1alpha2.PolicyReport{
|
|||
},
|
||||
Results: []v1alpha2.PolicyReportResult{
|
||||
{
|
||||
ID: "12348",
|
||||
Message: "message",
|
||||
Result: v1alpha2.StatusFail,
|
||||
Scored: true,
|
||||
|
@ -53,6 +54,7 @@ var DefaultPolicyReport = &v1alpha2.PolicyReport{
|
|||
Properties: map[string]string{"version": "1.2.0"},
|
||||
},
|
||||
{
|
||||
ID: "12346",
|
||||
Message: "message 2",
|
||||
Result: v1alpha2.StatusFail,
|
||||
Scored: true,
|
||||
|
@ -60,6 +62,7 @@ var DefaultPolicyReport = &v1alpha2.PolicyReport{
|
|||
Timestamp: v1.Timestamp{Seconds: 1614093000},
|
||||
},
|
||||
{
|
||||
ID: "12347",
|
||||
Message: "message 3",
|
||||
Result: v1alpha2.StatusFail,
|
||||
Scored: true,
|
||||
|
|
|
@ -11,3 +11,11 @@ func Contains(source string, sources []string) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
func Defaults(s, f string) string {
|
||||
if s != "" {
|
||||
return s
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/kyverno/policy-reporter/pkg/fixtures"
|
||||
"github.com/kyverno/policy-reporter/pkg/kubernetes"
|
||||
"github.com/kyverno/policy-reporter/pkg/report"
|
||||
"github.com/kyverno/policy-reporter/pkg/report/result"
|
||||
"github.com/kyverno/policy-reporter/pkg/validate"
|
||||
)
|
||||
|
||||
|
@ -38,6 +39,7 @@ func Test_PolicyReportWatcher(t *testing.T) {
|
|||
kubernetes.NewDebouncer(0, publisher),
|
||||
workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "test-queue"),
|
||||
restClient.Wgpolicyk8sV1alpha2(),
|
||||
result.NewReconditioner(nil),
|
||||
)
|
||||
|
||||
kclient, rclient, _ := NewFakeMetaClient()
|
||||
|
@ -88,6 +90,7 @@ func Test_ClusterPolicyReportWatcher(t *testing.T) {
|
|||
kubernetes.NewDebouncer(0, publisher),
|
||||
workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "test-queue"),
|
||||
restClient.Wgpolicyk8sV1alpha2(),
|
||||
result.NewReconditioner(nil),
|
||||
)
|
||||
|
||||
kclient, _, rclient := NewFakeMetaClient()
|
||||
|
@ -128,6 +131,7 @@ func Test_HasSynced(t *testing.T) {
|
|||
kubernetes.NewDebouncer(0, report.NewEventPublisher()),
|
||||
workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "test-queue"),
|
||||
restClient.Wgpolicyk8sV1alpha2(),
|
||||
result.NewReconditioner(nil),
|
||||
)
|
||||
|
||||
kclient, _, _ := NewFakeMetaClient()
|
||||
|
|
|
@ -17,14 +17,16 @@ import (
|
|||
pr "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/client/clientset/versioned/typed/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/report"
|
||||
"github.com/kyverno/policy-reporter/pkg/report/result"
|
||||
)
|
||||
|
||||
type Queue struct {
|
||||
queue workqueue.RateLimitingInterface
|
||||
client v1alpha2.Wgpolicyk8sV1alpha2Interface
|
||||
debouncer Debouncer
|
||||
lock *sync.Mutex
|
||||
cache sets.Set[string]
|
||||
queue workqueue.RateLimitingInterface
|
||||
client v1alpha2.Wgpolicyk8sV1alpha2Interface
|
||||
reconditioner *result.Reconditioner
|
||||
debouncer Debouncer
|
||||
lock *sync.Mutex
|
||||
cache sets.Set[string]
|
||||
}
|
||||
|
||||
func (q *Queue) Add(obj *v1.PartialObjectMetadata) error {
|
||||
|
@ -96,7 +98,7 @@ func (q *Queue) processNextItem() bool {
|
|||
defer q.lock.Unlock()
|
||||
q.cache.Delete(key)
|
||||
}()
|
||||
q.debouncer.Add(report.LifecycleEvent{Type: report.Deleted, PolicyReport: updateResults(polr)})
|
||||
q.debouncer.Add(report.LifecycleEvent{Type: report.Deleted, PolicyReport: q.reconditioner.Prepare(polr)})
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -115,29 +117,11 @@ func (q *Queue) processNextItem() bool {
|
|||
|
||||
q.handleErr(err, key)
|
||||
|
||||
q.debouncer.Add(report.LifecycleEvent{Type: event, PolicyReport: updateResults(polr)})
|
||||
q.debouncer.Add(report.LifecycleEvent{Type: event, PolicyReport: q.reconditioner.Prepare(polr)})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// each result needs to know its resource it belongs to, to generate internal unique IDs
|
||||
func updateResults(polr pr.ReportInterface) pr.ReportInterface {
|
||||
results := polr.GetResults()
|
||||
for i, r := range results {
|
||||
scope := polr.GetScope()
|
||||
|
||||
if len(r.Resources) == 0 && scope != nil {
|
||||
r.Resources = append(r.Resources, *scope)
|
||||
}
|
||||
|
||||
r.Priority = report.ResolvePriority(r)
|
||||
|
||||
results[i] = r
|
||||
}
|
||||
|
||||
return polr
|
||||
}
|
||||
|
||||
func (q *Queue) handleErr(err error, key interface{}) {
|
||||
if err == nil {
|
||||
q.queue.Forget(key)
|
||||
|
@ -157,12 +141,18 @@ func (q *Queue) handleErr(err error, key interface{}) {
|
|||
zap.L().Warn("dropping report out of queue", zap.Any("key", key), zap.Error(err))
|
||||
}
|
||||
|
||||
func NewQueue(debouncer Debouncer, queue workqueue.RateLimitingInterface, client v1alpha2.Wgpolicyk8sV1alpha2Interface) *Queue {
|
||||
func NewQueue(
|
||||
debouncer Debouncer,
|
||||
queue workqueue.RateLimitingInterface,
|
||||
client v1alpha2.Wgpolicyk8sV1alpha2Interface,
|
||||
reconditioner *result.Reconditioner,
|
||||
) *Queue {
|
||||
return &Queue{
|
||||
debouncer: debouncer,
|
||||
queue: queue,
|
||||
client: client,
|
||||
cache: sets.New[string](),
|
||||
lock: &sync.Mutex{},
|
||||
debouncer: debouncer,
|
||||
queue: queue,
|
||||
client: client,
|
||||
cache: sets.New[string](),
|
||||
lock: &sync.Mutex{},
|
||||
reconditioner: reconditioner,
|
||||
}
|
||||
}
|
||||
|
|
165
pkg/report/result/id_generator.go
Normal file
165
pkg/report/result/id_generator.go
Normal file
|
@ -0,0 +1,165 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/segmentio/fasthash/fnv1a"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
type FieldMapperFunc = func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64
|
||||
|
||||
type IDGenerator interface {
|
||||
Generate(polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) string
|
||||
}
|
||||
|
||||
var fieldMapper = map[string]FieldMapperFunc{
|
||||
"resource": func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
var resource *corev1.ObjectReference
|
||||
|
||||
if res.HasResource() {
|
||||
resource = res.GetResource()
|
||||
} else if polr.GetScope() != nil {
|
||||
resource = polr.GetScope()
|
||||
}
|
||||
|
||||
if resource != nil {
|
||||
h1 = fnv1a.AddString64(h1, string(resource.UID))
|
||||
h1 = fnv1a.AddString64(h1, string(resource.Name))
|
||||
}
|
||||
|
||||
return h1
|
||||
},
|
||||
"namespace": func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
return fnv1a.AddString64(h1, polr.GetNamespace())
|
||||
},
|
||||
"policy": func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
return fnv1a.AddString64(h1, res.Policy)
|
||||
},
|
||||
"rule": func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
return fnv1a.AddString64(h1, res.Rule)
|
||||
},
|
||||
"result": func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
return fnv1a.AddString64(h1, string(res.Result))
|
||||
},
|
||||
"category": func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
return fnv1a.AddString64(h1, res.Category)
|
||||
},
|
||||
"message": func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
return fnv1a.AddString64(h1, res.Message)
|
||||
},
|
||||
"created": func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
return fnv1a.AddString64(h1, res.Timestamp.String())
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
propertyResolver = func(field string) FieldMapperFunc {
|
||||
name := strings.TrimPrefix(field, "property:")
|
||||
|
||||
return func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
if prop, ok := res.Properties[name]; ok {
|
||||
h1 = fnv1a.AddString64(h1, prop)
|
||||
}
|
||||
|
||||
return h1
|
||||
}
|
||||
}
|
||||
|
||||
labelResolver = func(field string) FieldMapperFunc {
|
||||
name := strings.TrimPrefix(field, "label:")
|
||||
|
||||
return func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
if prop, ok := polr.GetLabels()[name]; ok {
|
||||
h1 = fnv1a.AddString64(h1, prop)
|
||||
}
|
||||
|
||||
return h1
|
||||
}
|
||||
}
|
||||
|
||||
annotationResolver = func(field string) FieldMapperFunc {
|
||||
name := strings.TrimPrefix(field, "annotation:")
|
||||
|
||||
return func(h1 uint64, polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) uint64 {
|
||||
if prop, ok := polr.GetAnnotations()[name]; ok {
|
||||
h1 = fnv1a.AddString64(h1, prop)
|
||||
}
|
||||
|
||||
return h1
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
type customIDGenerator struct {
|
||||
mappings []FieldMapperFunc
|
||||
}
|
||||
|
||||
func (g *customIDGenerator) Generate(polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) string {
|
||||
if id, ok := res.Properties[v1alpha2.ResultIDKey]; ok {
|
||||
return id
|
||||
}
|
||||
|
||||
h1 := fnv1a.Init64
|
||||
for _, mapping := range g.mappings {
|
||||
h1 = mapping(h1, polr, res)
|
||||
}
|
||||
|
||||
return strconv.FormatUint(h1, 10)
|
||||
}
|
||||
|
||||
type defaultIDGenerator struct{}
|
||||
|
||||
func (g *defaultIDGenerator) Generate(polr v1alpha2.ReportInterface, res v1alpha2.PolicyReportResult) string {
|
||||
if id, ok := res.Properties[v1alpha2.ResultIDKey]; ok {
|
||||
return id
|
||||
}
|
||||
|
||||
h1 := fnv1a.Init64
|
||||
|
||||
resource := polr.GetScope()
|
||||
if resource == nil {
|
||||
resource = res.GetResource()
|
||||
}
|
||||
|
||||
if resource != nil {
|
||||
h1 = fnv1a.AddString64(h1, resource.Name)
|
||||
h1 = fnv1a.AddString64(h1, string(resource.UID))
|
||||
}
|
||||
|
||||
h1 = fnv1a.AddString64(h1, res.Policy)
|
||||
h1 = fnv1a.AddString64(h1, res.Rule)
|
||||
h1 = fnv1a.AddString64(h1, string(res.Result))
|
||||
h1 = fnv1a.AddString64(h1, res.Category)
|
||||
h1 = fnv1a.AddString64(h1, res.Message)
|
||||
|
||||
return strconv.FormatUint(h1, 10)
|
||||
}
|
||||
|
||||
func NewIDGenerator(config []string) IDGenerator {
|
||||
if len(config) == 0 {
|
||||
return &defaultIDGenerator{}
|
||||
}
|
||||
|
||||
mappings := make([]FieldMapperFunc, 0, len(config))
|
||||
for _, field := range config {
|
||||
if strings.HasPrefix(field, "property:") {
|
||||
mappings = append(mappings, propertyResolver(field))
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(field, "label:") {
|
||||
mappings = append(mappings, labelResolver(field))
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(field, "annotation:") {
|
||||
mappings = append(mappings, annotationResolver(field))
|
||||
continue
|
||||
}
|
||||
|
||||
mappings = append(mappings, fieldMapper[field])
|
||||
}
|
||||
|
||||
return &customIDGenerator{mappings}
|
||||
}
|
180
pkg/report/result/id_generator_test.go
Normal file
180
pkg/report/result/id_generator_test.go
Normal file
|
@ -0,0 +1,180 @@
|
|||
package result_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/report/result"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestDefaultGenerator(t *testing.T) {
|
||||
generator := result.NewIDGenerator(nil)
|
||||
|
||||
t.Run("ID From Property", func(t *testing.T) {
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Properties: map[string]string{"resultID": "12345"}})
|
||||
|
||||
if id != "12345" {
|
||||
t.Errorf("expected result id to be '12345', got :%s", id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Resource", func(t *testing.T) {
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{{Name: "test", Kind: "Pod"}}})
|
||||
|
||||
if id != "18007334074686647077" {
|
||||
t.Errorf("expected result id to be '18007334074686647077', got :%s", id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Scope", func(t *testing.T) {
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{Scope: &corev1.ObjectReference{Name: "test", Kind: "Pod"}}, v1alpha2.PolicyReportResult{})
|
||||
|
||||
if id != "18007334074686647077" {
|
||||
t.Errorf("expected result id to be '18007334074686647077', got :%s", id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCustomGenerator(t *testing.T) {
|
||||
t.Run("ID From Property", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"resource"})
|
||||
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Properties: map[string]string{"resultID": "12345"}})
|
||||
|
||||
if id != "12345" {
|
||||
t.Errorf("expected result id to be '12345', got :%s", id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Resource", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"resource"})
|
||||
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{{Name: "test", Kind: "Pod"}}})
|
||||
|
||||
if id != "18007334074686647077" {
|
||||
t.Errorf("expected result id to be '18007334074686647077', got :%s", id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Scope", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"resource"})
|
||||
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{Scope: &corev1.ObjectReference{Name: "test", Kind: "Pod"}}, v1alpha2.PolicyReportResult{})
|
||||
|
||||
if id != "18007334074686647077" {
|
||||
t.Errorf("expected result id to be '18007334074686647077', got :%s", id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Namespace", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"namespace"})
|
||||
|
||||
empty := generator.Generate(&v1alpha2.PolicyReport{ObjectMeta: v1.ObjectMeta{Namespace: ""}}, v1alpha2.PolicyReportResult{Message: ""})
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{ObjectMeta: v1.ObjectMeta{Namespace: "test"}}, v1alpha2.PolicyReportResult{Message: ""})
|
||||
|
||||
if id == empty {
|
||||
t.Errorf("expected result id different from empty %s, got :%s", empty, id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Policy", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"policy"})
|
||||
|
||||
empty := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Policy: ""})
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Policy: "test"})
|
||||
|
||||
if id == empty {
|
||||
t.Errorf("expected result id different from empty %s, got :%s", empty, id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Rule", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"rule"})
|
||||
|
||||
empty := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Rule: ""})
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Rule: "test"})
|
||||
|
||||
if id == empty {
|
||||
t.Errorf("expected result id different from empty %s, got :%s", empty, id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Result", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"result"})
|
||||
|
||||
empty := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Result: ""})
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Result: "fail"})
|
||||
|
||||
if id == empty {
|
||||
t.Errorf("expected result id different from empty %s, got :%s", empty, id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Category", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"category"})
|
||||
|
||||
empty := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Category: ""})
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Category: "test"})
|
||||
|
||||
if id == empty {
|
||||
t.Errorf("expected result id different from empty %s, got :%s", empty, id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Message", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"message"})
|
||||
|
||||
empty := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Message: ""})
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Message: "test"})
|
||||
|
||||
if id == empty {
|
||||
t.Errorf("expected result id different from empty %s, got :%s", empty, id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Created", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"created"})
|
||||
|
||||
empty := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Timestamp: v1.Timestamp{Seconds: 0}})
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Timestamp: v1.Timestamp{Seconds: 1714641964}})
|
||||
|
||||
if id == empty {
|
||||
t.Errorf("expected result id different from empty %s, got :%s", empty, id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Property", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"property:id"})
|
||||
|
||||
empty := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{})
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Properties: map[string]string{"id": "1234"}})
|
||||
|
||||
if id == empty {
|
||||
t.Errorf("expected result id different from empty %s, got :%s", empty, id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Label", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"label:id"})
|
||||
|
||||
empty := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{})
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{ObjectMeta: v1.ObjectMeta{Labels: map[string]string{"id": "1234"}}}, v1alpha2.PolicyReportResult{})
|
||||
|
||||
if id == empty {
|
||||
t.Errorf("expected result id different from empty %s, got :%s", empty, id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID From Annotation", func(t *testing.T) {
|
||||
generator := result.NewIDGenerator([]string{"annotation:id"})
|
||||
|
||||
empty := generator.Generate(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{})
|
||||
id := generator.Generate(&v1alpha2.PolicyReport{ObjectMeta: v1.ObjectMeta{Annotations: map[string]string{"id": "1234"}}}, v1alpha2.PolicyReportResult{})
|
||||
|
||||
if id == empty {
|
||||
t.Errorf("expected result id different from empty %s, got :%s", empty, id)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package report
|
||||
package result
|
||||
|
||||
import (
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
|
@ -1,15 +1,15 @@
|
|||
package report_test
|
||||
package result_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/report"
|
||||
"github.com/kyverno/policy-reporter/pkg/report/result"
|
||||
)
|
||||
|
||||
func Test_ResolvePriority(t *testing.T) {
|
||||
t.Run("Status Skip", func(t *testing.T) {
|
||||
priority := report.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
priority := result.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
Result: v1alpha2.StatusSkip,
|
||||
Severity: v1alpha2.SeverityHigh,
|
||||
})
|
||||
|
@ -20,7 +20,7 @@ func Test_ResolvePriority(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Status Pass", func(t *testing.T) {
|
||||
priority := report.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
priority := result.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
Result: v1alpha2.StatusPass,
|
||||
Severity: v1alpha2.SeverityHigh,
|
||||
})
|
||||
|
@ -31,7 +31,7 @@ func Test_ResolvePriority(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Status Warning", func(t *testing.T) {
|
||||
priority := report.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
priority := result.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
Result: v1alpha2.StatusWarn,
|
||||
Severity: v1alpha2.SeverityHigh,
|
||||
})
|
||||
|
@ -42,7 +42,7 @@ func Test_ResolvePriority(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Status Error", func(t *testing.T) {
|
||||
priority := report.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
priority := result.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
Result: v1alpha2.StatusError,
|
||||
Severity: v1alpha2.SeverityHigh,
|
||||
})
|
||||
|
@ -53,7 +53,7 @@ func Test_ResolvePriority(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Status Fail Fallback", func(t *testing.T) {
|
||||
priority := report.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
priority := result.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
Result: v1alpha2.StatusFail,
|
||||
})
|
||||
|
||||
|
@ -63,7 +63,7 @@ func Test_ResolvePriority(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Status Severity", func(t *testing.T) {
|
||||
priority := report.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
priority := result.ResolvePriority(v1alpha2.PolicyReportResult{
|
||||
Result: v1alpha2.StatusFail,
|
||||
Severity: v1alpha2.SeverityCritical,
|
||||
})
|
43
pkg/report/result/reconditioner.go
Normal file
43
pkg/report/result/reconditioner.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/helper"
|
||||
)
|
||||
|
||||
type Reconditioner struct {
|
||||
defaultIDGenerator IDGenerator
|
||||
customIDGenerators map[string]IDGenerator
|
||||
}
|
||||
|
||||
func (r *Reconditioner) Prepare(polr v1alpha2.ReportInterface) v1alpha2.ReportInterface {
|
||||
generator := r.defaultIDGenerator
|
||||
if g, ok := r.customIDGenerators[strings.ToLower(polr.GetSource())]; ok {
|
||||
generator = g
|
||||
}
|
||||
|
||||
results := polr.GetResults()
|
||||
for i, r := range results {
|
||||
r.ID = generator.Generate(polr, r)
|
||||
r.Priority = ResolvePriority(r)
|
||||
r.Category = helper.Defaults(r.Category, "Other")
|
||||
|
||||
scope := polr.GetScope()
|
||||
if len(r.Resources) == 0 && scope != nil {
|
||||
r.Resources = append(r.Resources, *scope)
|
||||
}
|
||||
|
||||
results[i] = r
|
||||
}
|
||||
|
||||
return polr
|
||||
}
|
||||
|
||||
func NewReconditioner(generators map[string]IDGenerator) *Reconditioner {
|
||||
return &Reconditioner{
|
||||
defaultIDGenerator: NewIDGenerator(nil),
|
||||
customIDGenerators: generators,
|
||||
}
|
||||
}
|
126
pkg/report/result/reconditioner_test.go
Normal file
126
pkg/report/result/reconditioner_test.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
package result_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/report/result"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestReconditioner(t *testing.T) {
|
||||
t.Run("prepare with default generator", func(t *testing.T) {
|
||||
var report v1alpha2.ReportInterface = &v1alpha2.PolicyReport{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "policy-report",
|
||||
Namespace: "test",
|
||||
},
|
||||
Summary: v1alpha2.PolicyReportSummary{
|
||||
Pass: 0,
|
||||
Skip: 0,
|
||||
Warn: 0,
|
||||
Fail: 1,
|
||||
Error: 0,
|
||||
},
|
||||
Scope: &corev1.ObjectReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "Deployment",
|
||||
Name: "nginx",
|
||||
Namespace: "test",
|
||||
UID: "dfd57c50-f30c-4729-b63f-b1954d8988d1",
|
||||
},
|
||||
Results: []v1alpha2.PolicyReportResult{
|
||||
{
|
||||
ID: "12348",
|
||||
Message: "message",
|
||||
Result: v1alpha2.StatusFail,
|
||||
Scored: true,
|
||||
Policy: "required-label",
|
||||
Rule: "app-label-required",
|
||||
Timestamp: v1.Timestamp{Seconds: 1614093000},
|
||||
Source: "test",
|
||||
Category: "",
|
||||
Severity: v1alpha2.SeverityHigh,
|
||||
Properties: map[string]string{"version": "1.2.0"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rec := result.NewReconditioner(nil)
|
||||
|
||||
report = rec.Prepare(report)
|
||||
res := report.GetResults()[0]
|
||||
|
||||
if res.ID != "1412073110812056002" {
|
||||
t.Errorf("result id should be generated from default generator: %s", res.ID)
|
||||
}
|
||||
if res.Category != "Other" {
|
||||
t.Error("result category should default to Other")
|
||||
}
|
||||
if res.Priority != v1alpha2.ErrorPriority {
|
||||
t.Error("result prioriry should be mapped")
|
||||
}
|
||||
if len(res.Resources) == 0 || res.Resources[0] != *report.GetScope() {
|
||||
t.Error("result resource should be mapped to scope")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("prepare with custom generator", func(t *testing.T) {
|
||||
var report v1alpha2.ReportInterface = &v1alpha2.PolicyReport{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "policy-report",
|
||||
Namespace: "test",
|
||||
},
|
||||
Summary: v1alpha2.PolicyReportSummary{
|
||||
Pass: 0,
|
||||
Skip: 0,
|
||||
Warn: 0,
|
||||
Fail: 1,
|
||||
Error: 0,
|
||||
},
|
||||
Scope: &corev1.ObjectReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "Deployment",
|
||||
Name: "nginx",
|
||||
Namespace: "test",
|
||||
UID: "dfd57c50-f30c-4729-b63f-b1954d8988d1",
|
||||
},
|
||||
Results: []v1alpha2.PolicyReportResult{
|
||||
{
|
||||
ID: "12348",
|
||||
Message: "message",
|
||||
Result: v1alpha2.StatusFail,
|
||||
Scored: true,
|
||||
Policy: "required-label",
|
||||
Rule: "app-label-required",
|
||||
Timestamp: v1.Timestamp{Seconds: 1614093000},
|
||||
Source: "test",
|
||||
Category: "",
|
||||
Severity: v1alpha2.SeverityHigh,
|
||||
Properties: map[string]string{"version": "1.2.0"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rec := result.NewReconditioner(map[string]result.IDGenerator{
|
||||
"test": result.NewIDGenerator([]string{"resource", "policy", "rule", "result"}),
|
||||
})
|
||||
|
||||
report = rec.Prepare(report)
|
||||
res := report.GetResults()[0]
|
||||
|
||||
if res.ID != "12714703365089292087" {
|
||||
t.Errorf("result id should be generated from custom generator: %s", res.ID)
|
||||
}
|
||||
if res.Category != "Other" {
|
||||
t.Error("result category should default to Other")
|
||||
}
|
||||
if res.Priority != v1alpha2.ErrorPriority {
|
||||
t.Error("result prioriry should be mapped")
|
||||
}
|
||||
if len(res.Resources) == 0 || res.Resources[0] != *report.GetScope() {
|
||||
t.Error("result resource should be mapped to scope")
|
||||
}
|
||||
})
|
||||
}
|
16
pkg/report/result/resource.go
Normal file
16
pkg/report/result/resource.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func Resource(p v1alpha2.ReportInterface, r v1alpha2.PolicyReportResult) *corev1.ObjectReference {
|
||||
if r.HasResource() {
|
||||
return r.GetResource()
|
||||
} else if p.GetScope() != nil {
|
||||
return p.GetScope()
|
||||
}
|
||||
|
||||
return &corev1.ObjectReference{}
|
||||
}
|
37
pkg/report/result/resource_test.go
Normal file
37
pkg/report/result/resource_test.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package result_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/report/result"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestResource(t *testing.T) {
|
||||
t.Run("resource from scope", func(t *testing.T) {
|
||||
resource := &corev1.ObjectReference{Name: "test", Kind: "Pod"}
|
||||
|
||||
res := result.Resource(&v1alpha2.PolicyReport{Scope: resource}, v1alpha2.PolicyReportResult{})
|
||||
|
||||
if res != resource {
|
||||
t.Error("expected function to return scope resource")
|
||||
}
|
||||
})
|
||||
t.Run("resource from result", func(t *testing.T) {
|
||||
resource := &corev1.ObjectReference{Name: "test", Kind: "Pod"}
|
||||
|
||||
res := result.Resource(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{*resource}})
|
||||
|
||||
if res.Name != resource.Name {
|
||||
t.Error("expected function to return result resource")
|
||||
}
|
||||
})
|
||||
t.Run("empty fallback resource", func(t *testing.T) {
|
||||
res := result.Resource(&v1alpha2.PolicyReport{}, v1alpha2.PolicyReportResult{Resources: []corev1.ObjectReference{}})
|
||||
|
||||
if res == nil {
|
||||
t.Error("expected function to return empty fallback resource")
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue