mirror of
https://github.com/kyverno/policy-reporter.git
synced 2024-12-14 11:57:32 +00:00
External SQL DB support (#304)
* Support external Databases Signed-off-by: Frank Jogeleit <frank.jogeleit@web.de>
This commit is contained in:
parent
49cc1a50cc
commit
72abc63ce0
48 changed files with 2537 additions and 2208 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -1,5 +1,18 @@
|
|||
# Changelog
|
||||
|
||||
# 2.19.0
|
||||
* Policy Reporter
|
||||
* New AWS SecurityHub push target - See [values.yaml](https://github.com/kyverno/policy-reporter/blob/main/charts/policy-reporter/values.yaml#L550) for available configurations
|
||||
* External DB support (PostgreSQL, MySQL, MariaDB) - See [values.yaml](https://github.com/kyverno/policy-reporter/blob/main/charts/policy-reporter/values.yaml#L172) for available configurations
|
||||
* HA Mode support - only leader write into the DB
|
||||
* Versioned Schema, autoupdated when another version is detected
|
||||
* Configurable over values and secrets
|
||||
* Cache improvements to reduce duplicated pushes
|
||||
* Split Category API into namespaced scoped and cluster scoped API
|
||||
* Support search for contained words in the results API
|
||||
* Policy Reporter UI
|
||||
* Update API requests
|
||||
|
||||
# 2.18.3
|
||||
* Policy Reporter
|
||||
* new value to add `priorityClassName` to pods [[#283](https://github.com/kyverno/policy-reporter/pull/283) by [boniek83](https://github.com/boniek83)]
|
||||
|
|
12
Makefile
12
Makefile
|
@ -1,8 +1,8 @@
|
|||
GO ?= go
|
||||
BUILD ?= build
|
||||
REPO ?= ghcr.io/kyverno/policy-reporter
|
||||
IMAGE_TAG ?= 2.8.1
|
||||
LD_FLAGS='-s -w -linkmode external -extldflags "-static"'
|
||||
IMAGE_TAG ?= 2.15.0
|
||||
LD_FLAGS=-s -w -linkmode external -extldflags "-static"
|
||||
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x
|
||||
|
||||
all: build
|
||||
|
@ -29,16 +29,16 @@ build: prepare
|
|||
|
||||
.PHONY: docker-build
|
||||
docker-build:
|
||||
@docker buildx build --progress plane --platform $(PLATFORMS) --tag $(REPO):$(IMAGE_TAG) . --build-arg LD_FLAGS=$(LD_FLAGS)
|
||||
@docker buildx build --progress plane --platform $(PLATFORMS) --tag $(REPO):$(IMAGE_TAG) . --build-arg LD_FLAGS='$(LD_FLAGS) -X main.Version=$(IMAGE_TAG)'
|
||||
|
||||
.PHONY: docker-push
|
||||
docker-push:
|
||||
@docker buildx build --progress plane --platform $(PLATFORMS) --tag $(REPO):$(IMAGE_TAG) . --build-arg LD_FLAGS=$(LD_FLAGS) --push
|
||||
@docker buildx build --progress plane --platform $(PLATFORMS) --tag $(REPO):latest . --build-arg LD_FLAGS=$(LD_FLAGS) --push
|
||||
@docker buildx build --progress plane --platform $(PLATFORMS) --tag $(REPO):$(IMAGE_TAG) . --build-arg LD_FLAGS='$(LD_FLAGS) -X main.Version=$(IMAGE_TAG)' --push
|
||||
@docker buildx build --progress plane --platform $(PLATFORMS) --tag $(REPO):latest . --build-arg LD_FLAGS='$(LD_FLAGS) -X main.Version=$(IMAGE_TAG)' --push
|
||||
|
||||
.PHONY: docker-push-dev
|
||||
docker-push-dev:
|
||||
@docker buildx build --progress plane --platform $(PLATFORMS) --tag $(REPO):dev . --build-arg LD_FLAGS=$(LD_FLAGS) --push
|
||||
@docker buildx build --progress plane --platform $(PLATFORMS) --tag $(REPO):dev . --build-arg LD_FLAGS='$(LD_FLAGS) -X main.Version=$(IMAGE_TAG)-dev' --push
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
|
|
|
@ -4,9 +4,9 @@ dependencies:
|
|||
version: 2.7.1
|
||||
- name: ui
|
||||
repository: ""
|
||||
version: 2.9.3
|
||||
version: 2.9.4
|
||||
- name: kyvernoPlugin
|
||||
repository: ""
|
||||
version: 1.5.4
|
||||
digest: sha256:274143bd81946a2e65f7349e88222d31b2c4c2f9153671a502f5ea254d24954c
|
||||
generated: "2023-04-05T14:57:22.183437589+02:00"
|
||||
digest: sha256:8c60bb2f27f40a297021c1ed8ac5d52d0053e36f65f1d71ad296a7beff95dd70
|
||||
generated: "2023-05-02T09:19:51.799892+02:00"
|
||||
|
|
|
@ -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: 2.18.3
|
||||
appVersion: 2.14.2
|
||||
version: 2.19.0
|
||||
appVersion: 2.15.0
|
||||
|
||||
icon: https://github.com/kyverno/kyverno/raw/main/img/logo.png
|
||||
home: https://kyverno.github.io/policy-reporter
|
||||
|
@ -21,7 +21,7 @@ dependencies:
|
|||
version: "2.7.1"
|
||||
- name: ui
|
||||
condition: ui.enabled
|
||||
version: "2.9.3"
|
||||
version: "2.9.4"
|
||||
- name: kyvernoPlugin
|
||||
condition: kyvernoPlugin.enabled
|
||||
version: "1.5.4"
|
||||
|
|
|
@ -3,5 +3,5 @@ name: ui
|
|||
description: Policy Reporter UI
|
||||
|
||||
type: application
|
||||
version: 2.9.3
|
||||
appVersion: 1.8.3
|
||||
version: 2.9.4
|
||||
appVersion: 1.8.4
|
||||
|
|
|
@ -9,7 +9,7 @@ image:
|
|||
registry: ghcr.io
|
||||
repository: kyverno/policy-reporter-ui
|
||||
pullPolicy: IfNotPresent
|
||||
tag: 1.8.3
|
||||
tag: 1.8.4
|
||||
|
||||
# possible default displayModes: light/dark
|
||||
displayMode: ""
|
||||
|
|
|
@ -314,3 +314,14 @@ logging:
|
|||
|
||||
api:
|
||||
logging: {{ .Values.api.logging }}
|
||||
|
||||
database:
|
||||
type: {{ .Values.database.type }}
|
||||
database: {{ .Values.database.database }}
|
||||
username: {{ .Values.database.username }}
|
||||
password: {{ .Values.database.password }}
|
||||
host: {{ .Values.database.host }}
|
||||
enableSSL: {{ .Values.database.enableSSL }}
|
||||
dsn: {{ .Values.database.dsn }}
|
||||
secretRef: {{ .Values.database.secretRef }}
|
||||
mountedSecret: {{ .Values.database.mountedSecret }}
|
||||
|
|
|
@ -5,7 +5,7 @@ image:
|
|||
registry: ghcr.io
|
||||
repository: kyverno/policy-reporter
|
||||
pullPolicy: IfNotPresent
|
||||
tag: 2.14.2
|
||||
tag: 2.15.0
|
||||
|
||||
imagePullSecrets: []
|
||||
|
||||
|
@ -169,6 +169,25 @@ kyvernoPlugin:
|
|||
monitoring:
|
||||
enabled: false
|
||||
|
||||
database:
|
||||
# Database Type, supported: mysql, postgres, mariadb
|
||||
type: ""
|
||||
database: "" # Database Name
|
||||
username: ""
|
||||
password: ""
|
||||
host: ""
|
||||
enableSSL: false
|
||||
# instead of configure the individual values you can also provide an DSN string
|
||||
# example postgres: postgres://postgres:password@localhost:5432/postgres?sslmode=disable
|
||||
# example mysql: root:password@tcp(localhost:3306)/test?tls=false
|
||||
dsn: ""
|
||||
# configure an existing secret as source for your values
|
||||
# supported fields: username, password, host, dsn, database
|
||||
secretRef: ""
|
||||
# use an mounted secret as source for your values, required the information in JSON format
|
||||
# supported fields: username, password, host, dsn, database
|
||||
mountedSecret: ""
|
||||
|
||||
global:
|
||||
# available plugins
|
||||
plugins:
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
)
|
||||
|
||||
// NewCLI creates a new instance of the root CLI
|
||||
func NewCLI() *cobra.Command {
|
||||
func NewCLI(version string) *cobra.Command {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "policyreporter",
|
||||
Short: "Generates PolicyReport Metrics and Send Results to different targets",
|
||||
|
@ -13,7 +13,8 @@ func NewCLI() *cobra.Command {
|
|||
Sends notifications to different targets like Grafana's Loki.`,
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(newRunCMD())
|
||||
rootCmd.AddCommand(newVersionCMD(version))
|
||||
rootCmd.AddCommand(newRunCMD(version))
|
||||
rootCmd.AddCommand(newSendCMD())
|
||||
|
||||
return rootCmd
|
||||
|
|
71
cmd/run.go
71
cmd/run.go
|
@ -2,6 +2,7 @@ package cmd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -11,10 +12,11 @@ import (
|
|||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/config"
|
||||
"github.com/kyverno/policy-reporter/pkg/database"
|
||||
"github.com/kyverno/policy-reporter/pkg/listener"
|
||||
)
|
||||
|
||||
func newRunCMD() *cobra.Command {
|
||||
func newRunCMD(version string) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Run PolicyReporter Watcher & HTTP Metrics Server",
|
||||
|
@ -23,6 +25,7 @@ func newRunCMD() *cobra.Command {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Version = version
|
||||
|
||||
var k8sConfig *rest.Config
|
||||
if c.K8sClient.Kubeconfig != "" {
|
||||
|
@ -37,6 +40,7 @@ func newRunCMD() *cobra.Command {
|
|||
k8sConfig.QPS = c.K8sClient.QPS
|
||||
k8sConfig.Burst = c.K8sClient.Burst
|
||||
|
||||
readinessProbe := config.NewReadinessProbe(c)
|
||||
resolver := config.NewResolver(c, k8sConfig)
|
||||
logger, err := resolver.Logger()
|
||||
if err != nil {
|
||||
|
@ -52,20 +56,26 @@ func newRunCMD() *cobra.Command {
|
|||
|
||||
g := &errgroup.Group{}
|
||||
|
||||
var store *database.Store
|
||||
|
||||
if c.REST.Enabled {
|
||||
db, err := resolver.Database()
|
||||
if err != nil {
|
||||
return err
|
||||
db := resolver.Database()
|
||||
if db == nil {
|
||||
return errors.New("unable to create database connection")
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
store, err := resolver.PolicyReportStore(db)
|
||||
store, err = resolver.PolicyReportStore(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.LeaderElection.Enabled || store.IsSQLite() {
|
||||
store.PrepareDatabase(cmd.Context())
|
||||
resolver.RegisterStoreListener(cmd.Context(), store)
|
||||
}
|
||||
|
||||
logger.Info("REST api enabled")
|
||||
resolver.RegisterStoreListener(store)
|
||||
server.RegisterV1Handler(store)
|
||||
}
|
||||
|
||||
|
@ -80,41 +90,75 @@ func newRunCMD() *cobra.Command {
|
|||
server.RegisterProfilingHandler()
|
||||
}
|
||||
|
||||
if resolver.HasTargets() && c.LeaderElection.Enabled {
|
||||
if !resolver.ResultCache().Shared() {
|
||||
logger.Debug("register new result listener")
|
||||
resolver.RegisterNewResultsListener()
|
||||
}
|
||||
|
||||
if resolver.EnableLeaderElection() {
|
||||
elector, err := resolver.LeaderElectionClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elector.RegisterOnStart(func(c context.Context) {
|
||||
elector.RegisterOnStart(func(ctx context.Context) {
|
||||
logger.Info("started leadership")
|
||||
|
||||
if c.REST.Enabled && !store.IsSQLite() {
|
||||
store.PrepareDatabase(cmd.Context())
|
||||
|
||||
logger.Debug("register database persistence")
|
||||
resolver.RegisterStoreListener(ctx, store)
|
||||
|
||||
if readinessProbe.Running() {
|
||||
logger.Debug("trigger informer restart")
|
||||
client.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
resolver.RegisterSendResultListener()
|
||||
|
||||
readinessProbe.Ready()
|
||||
}).RegisterOnNew(func(currentID, lockID string) {
|
||||
if currentID != lockID {
|
||||
logger.Info("leadership", zap.String("leader", currentID))
|
||||
readinessProbe.Ready()
|
||||
return
|
||||
}
|
||||
}).RegisterOnStop(func() {
|
||||
logger.Info("stopped leadership")
|
||||
|
||||
resolver.EventPublisher().UnregisterListener(listener.NewResults)
|
||||
if !store.IsSQLite() {
|
||||
resolver.EventPublisher().UnregisterListener(listener.Store)
|
||||
}
|
||||
|
||||
if resolver.HasTargets() {
|
||||
resolver.UnregisterSendResultListener()
|
||||
}
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return elector.Run(cmd.Context())
|
||||
})
|
||||
} else if resolver.HasTargets() {
|
||||
} else {
|
||||
resolver.RegisterSendResultListener()
|
||||
}
|
||||
|
||||
g.Go(server.Start)
|
||||
|
||||
g.Go(func() error {
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
readinessProbe.Wait()
|
||||
|
||||
logger.Info("start client", zap.Int("worker", c.WorkerCount))
|
||||
|
||||
return client.Run(c.WorkerCount, stop)
|
||||
for {
|
||||
stop := make(chan struct{})
|
||||
if err := client.Run(c.WorkerCount, stop); err != nil {
|
||||
zap.L().Error("informer client error", zap.Error(err))
|
||||
}
|
||||
|
||||
zap.L().Debug("informer restarts")
|
||||
}
|
||||
})
|
||||
|
||||
return g.Wait()
|
||||
|
@ -130,6 +174,7 @@ func newRunCMD() *cobra.Command {
|
|||
cmd.PersistentFlags().BoolP("rest-enabled", "r", false, "Enable Policy Reporter's REST API")
|
||||
cmd.PersistentFlags().Bool("profile", false, "Enable application profiling with pprof")
|
||||
cmd.PersistentFlags().String("lease-name", "policy-reporter", "name of the LeaseLock")
|
||||
cmd.PersistentFlags().String("pod-name", "policy-reporter", "name of the pod, used for leaderelection")
|
||||
cmd.PersistentFlags().Int("worker", 5, "amount of queue worker")
|
||||
cmd.PersistentFlags().Float32("qps", 20, "K8s RESTClient QPS")
|
||||
cmd.PersistentFlags().Int("burst", 50, "K8s RESTClient burst")
|
||||
|
|
17
cmd/version.go
Normal file
17
cmd/version.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newVersionCMD(version string) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Policy Reporter AppVersion",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("AppVersion: " + version)
|
||||
},
|
||||
}
|
||||
}
|
21
go.mod
21
go.mod
|
@ -6,6 +6,7 @@ require (
|
|||
cloud.google.com/go/storage v1.30.1
|
||||
github.com/aws/aws-sdk-go v1.44.229
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/kyverno/go-wildcard v1.0.5
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
|
@ -14,6 +15,11 @@ require (
|
|||
github.com/segmentio/fasthash v1.0.3
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/uptrace/bun v1.1.12
|
||||
github.com/uptrace/bun/dialect/mysqldialect v1.1.12
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.12
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.12
|
||||
github.com/uptrace/bun/driver/pgdriver v1.1.12
|
||||
github.com/xhit/go-simple-mail/v2 v2.13.0
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/sync v0.1.0
|
||||
|
@ -47,6 +53,7 @@ require (
|
|||
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
|
@ -65,12 +72,17 @@ require (
|
|||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 // indirect
|
||||
|
@ -81,6 +93,7 @@ require (
|
|||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.90.1 // indirect
|
||||
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // indirect
|
||||
mellium.im/sasl v0.3.1 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
@ -92,9 +105,9 @@ require (
|
|||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/oauth2 v0.6.0
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
k8s.io/api v0.26.3
|
||||
|
|
43
go.sum
43
go.sum
|
@ -107,6 +107,8 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/
|
|||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
|
@ -200,6 +202,8 @@ github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+h
|
|||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
|
@ -291,6 +295,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
|
@ -298,8 +303,24 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
|
|||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
|
||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
|
||||
github.com/uptrace/bun v1.1.12 h1:sOjDVHxNTuM6dNGaba0wUuz7KvDE1BmNu9Gqs2gJSXQ=
|
||||
github.com/uptrace/bun v1.1.12/go.mod h1:NPG6JGULBeQ9IU6yHp7YGELRa5Agmd7ATZdz4tGZ6z0=
|
||||
github.com/uptrace/bun/dialect/mysqldialect v1.1.12 h1:Rpp0N7E9wmpWm8oTXuQ7tG9Ekdp5hLO/lSQCbiAQvYY=
|
||||
github.com/uptrace/bun/dialect/mysqldialect v1.1.12/go.mod h1:Zz+fRspfRjkRYUQLGFfkq5s5ilEsPW5KFmORgy64dy8=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.12 h1:m/CM1UfOkoBTglGO5CUTKnIKKOApOYxkcP2qn0F9tJk=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.12/go.mod h1:Ij6WIxQILxLlL2frUBxUBOZJtLElD2QQNDcu/PWDHTc=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.12 h1:Ud31nqZmebcQpl151nb108+vtcpxJ7kfXmbPYbALBiI=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.12/go.mod h1:Pwg7s31BdF3PMBlWTnYkEn2I9ASsvatt1Ln/AERCTV4=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.1.12 h1:3rRWB1GK0psTJrHwxzNfEij2MLibggiLdTqjTtfHc1w=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.1.12/go.mod h1:ssYUP+qwSEgeDDS1xm2XBip9el1y9Mi5mTAvLoiADLM=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
|
@ -334,6 +355,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -368,6 +391,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -405,8 +430,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -472,13 +497,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -489,8 +514,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -696,6 +721,8 @@ k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a h1:gmovKNur38vgoWfGtP5QOG
|
|||
k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY=
|
||||
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 h1:xMMXJlJbsU8w3V5N2FLDQ8YgU8s1EoULdbQBcAeNJkY=
|
||||
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
|
||||
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
4
main.go
4
main.go
|
@ -7,8 +7,10 @@ import (
|
|||
"github.com/kyverno/policy-reporter/cmd"
|
||||
)
|
||||
|
||||
var Version = "development"
|
||||
|
||||
func main() {
|
||||
if err := cmd.NewCLI().Execute(); err != nil {
|
||||
if err := cmd.NewCLI(Version).Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
@ -47,13 +47,13 @@ func (s *httpServer) RegisterV1Handler(finder v1.PolicyReportFinder) {
|
|||
handler := v1.NewHandler(finder)
|
||||
|
||||
s.mux.HandleFunc("/v1/targets", Gzip(handler.TargetsHandler(s.targets)))
|
||||
s.mux.HandleFunc("/v1/categories", Gzip(handler.CategoryListHandler()))
|
||||
s.mux.HandleFunc("/v1/namespaces", Gzip(handler.NamespaceListHandler()))
|
||||
s.mux.HandleFunc("/v1/rule-status-count", Gzip(handler.RuleStatusCountHandler()))
|
||||
|
||||
s.mux.HandleFunc("/v1/policy-reports", Gzip(handler.PolicyReportListHandler()))
|
||||
s.mux.HandleFunc("/v1/cluster-policy-reports", Gzip(handler.ClusterPolicyReportListHandler()))
|
||||
|
||||
s.mux.HandleFunc("/v1/namespaced-resources/categories", Gzip(handler.NamespacedCategoryListHandler()))
|
||||
s.mux.HandleFunc("/v1/namespaced-resources/policies", Gzip(handler.NamespacedResourcesPolicyListHandler()))
|
||||
s.mux.HandleFunc("/v1/namespaced-resources/rules", Gzip(handler.NamespacedResourcesRuleListHandler()))
|
||||
s.mux.HandleFunc("/v1/namespaced-resources/kinds", Gzip(handler.NamespacedResourcesKindListHandler()))
|
||||
|
@ -71,6 +71,7 @@ func (s *httpServer) RegisterV1Handler(finder v1.PolicyReportFinder) {
|
|||
s.mux.HandleFunc("/v1/cluster-resources/report-labels", Gzip(handler.ClusterReportLabelListHandler()))
|
||||
s.mux.HandleFunc("/v1/cluster-resources/status-counts", Gzip(handler.ClusterResourcesStatusCountHandler()))
|
||||
s.mux.HandleFunc("/v1/cluster-resources/results", Gzip(handler.ClusterResourcesResultHandler()))
|
||||
s.mux.HandleFunc("/v1/cluster-resources/categories", Gzip(handler.ClusterCategoryListHandler()))
|
||||
}
|
||||
|
||||
func (s *httpServer) RegisterMetricsHandler() {
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/segmentio/fasthash/fnv1a"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"context"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
|
@ -29,115 +25,57 @@ type Pagination struct {
|
|||
Direction string
|
||||
}
|
||||
|
||||
type ResultFilterValues struct {
|
||||
ReportID string
|
||||
Namespace string
|
||||
Source string
|
||||
Kind string
|
||||
Category string
|
||||
Policy string
|
||||
Severity string
|
||||
Result string
|
||||
Count int
|
||||
}
|
||||
|
||||
func (r ResultFilterValues) Hash() string {
|
||||
h1 := fnv1a.Init64
|
||||
h1 = fnv1a.AddString64(h1, r.ReportID)
|
||||
h1 = fnv1a.AddString64(h1, r.Namespace)
|
||||
h1 = fnv1a.AddString64(h1, r.Source)
|
||||
h1 = fnv1a.AddString64(h1, r.Kind)
|
||||
h1 = fnv1a.AddString64(h1, r.Category)
|
||||
h1 = fnv1a.AddString64(h1, r.Policy)
|
||||
h1 = fnv1a.AddString64(h1, r.Severity)
|
||||
h1 = fnv1a.AddString64(h1, r.Result)
|
||||
|
||||
return strconv.FormatUint(h1, 10)
|
||||
}
|
||||
|
||||
func ExtractFilterValues(polr v1alpha2.ReportInterface) []*ResultFilterValues {
|
||||
mapping := make(map[string]*ResultFilterValues)
|
||||
for _, res := range polr.GetResults() {
|
||||
kind := res.GetKind()
|
||||
if kind == "" && polr.GetScope() != nil {
|
||||
kind = polr.GetScope().Namespace
|
||||
}
|
||||
|
||||
value := &ResultFilterValues{
|
||||
ReportID: polr.GetID(),
|
||||
Namespace: polr.GetNamespace(),
|
||||
Source: res.Source,
|
||||
Kind: kind,
|
||||
Category: res.Category,
|
||||
Policy: res.Policy,
|
||||
Severity: string(res.Severity),
|
||||
Result: string(res.Result),
|
||||
Count: 1,
|
||||
}
|
||||
|
||||
if item, ok := mapping[value.Hash()]; ok {
|
||||
item.Count = item.Count + 1
|
||||
} else {
|
||||
mapping[value.Hash()] = value
|
||||
}
|
||||
}
|
||||
list := make([]*ResultFilterValues, 0, len(mapping))
|
||||
for _, v := range mapping {
|
||||
list = append(list, v)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
type PolicyReportFinder interface {
|
||||
// FetchClusterPolicyReports by filter and pagination
|
||||
FetchClusterPolicyReports(Filter, Pagination) ([]*PolicyReport, error)
|
||||
FetchClusterPolicyReports(context.Context, Filter, Pagination) ([]*PolicyReport, error)
|
||||
// FetchPolicyReports by filter and pagination
|
||||
FetchPolicyReports(Filter, Pagination) ([]*PolicyReport, error)
|
||||
FetchPolicyReports(context.Context, Filter, Pagination) ([]*PolicyReport, error)
|
||||
// CountClusterPolicyReports by filter
|
||||
CountClusterPolicyReports(Filter) (int, error)
|
||||
CountClusterPolicyReports(context.Context, Filter) (int, error)
|
||||
// CountPolicyReports by filter
|
||||
CountPolicyReports(Filter) (int, error)
|
||||
CountPolicyReports(context.Context, Filter) (int, error)
|
||||
// FetchClusterPolicies from current PolicyReportResults
|
||||
FetchClusterPolicies(Filter) ([]string, error)
|
||||
FetchClusterPolicies(context.Context, Filter) ([]string, error)
|
||||
// FetchClusterRules from current PolicyReportResults
|
||||
FetchClusterRules(Filter) ([]string, error)
|
||||
FetchClusterRules(context.Context, Filter) ([]string, error)
|
||||
// FetchNamespacedPolicies from current PolicyReportResults with a Namespace
|
||||
FetchNamespacedPolicies(Filter) ([]string, error)
|
||||
FetchNamespacedPolicies(context.Context, Filter) ([]string, error)
|
||||
// FetchNamespacedRules from current PolicyReportResults with a Namespace
|
||||
FetchNamespacedRules(Filter) ([]string, error)
|
||||
// FetchCategories from current PolicyReportResults
|
||||
FetchCategories(Filter) ([]string, error)
|
||||
FetchNamespacedRules(context.Context, Filter) ([]string, error)
|
||||
// FetchClusterCategories from current PolicyReportResults
|
||||
FetchClusterCategories(context.Context, Filter) ([]string, error)
|
||||
// FetchNamespacedCategories from current PolicyReportResults
|
||||
FetchNamespacedCategories(context.Context, Filter) ([]string, error)
|
||||
// FetchClusterSources from current PolicyReportResults
|
||||
FetchClusterSources() ([]string, error)
|
||||
FetchClusterSources(context.Context) ([]string, error)
|
||||
// FetchNamespacedSources from current PolicyReportResults with a Namespace
|
||||
FetchNamespacedSources() ([]string, error)
|
||||
FetchNamespacedSources(context.Context) ([]string, error)
|
||||
// FetchNamespacedKinds from current PolicyReportResults with a Namespace
|
||||
FetchNamespacedKinds(Filter) ([]string, error)
|
||||
FetchNamespacedKinds(context.Context, Filter) ([]string, error)
|
||||
// FetchNamespacedResources from current PolicyReportResults with a Namespace
|
||||
FetchNamespacedResources(Filter) ([]*Resource, error)
|
||||
FetchNamespacedResources(context.Context, Filter) ([]*Resource, error)
|
||||
// FetchClusterResources from current PolicyReportResults
|
||||
FetchClusterResources(Filter) ([]*Resource, error)
|
||||
FetchClusterResources(context.Context, Filter) ([]*Resource, error)
|
||||
// FetchClusterKinds from current PolicyReportResults
|
||||
FetchClusterKinds(Filter) ([]string, error)
|
||||
FetchClusterKinds(context.Context, Filter) ([]string, error)
|
||||
// FetchNamespaces from current PolicyReports
|
||||
FetchNamespaces(Filter) ([]string, error)
|
||||
FetchNamespaces(context.Context, Filter) ([]string, error)
|
||||
// FetchNamespacedStatusCounts from current PolicyReportResults with a Namespace
|
||||
FetchNamespacedStatusCounts(Filter) ([]NamespacedStatusCount, error)
|
||||
// FetchStatusCounts from current PolicyReportResults
|
||||
FetchStatusCounts(Filter) ([]StatusCount, error)
|
||||
FetchNamespacedStatusCounts(context.Context, Filter) ([]NamespacedStatusCount, error)
|
||||
// FetchClusterStatusCounts from current PolicyReportResults
|
||||
FetchClusterStatusCounts(context.Context, Filter) ([]StatusCount, error)
|
||||
// FetchNamespacedResults from current PolicyReportResults with a Namespace
|
||||
FetchNamespacedResults(Filter, Pagination) ([]*ListResult, error)
|
||||
FetchNamespacedResults(context.Context, Filter, Pagination) ([]*ListResult, error)
|
||||
// FetchClusterResults from current PolicyReportResults
|
||||
FetchClusterResults(Filter, Pagination) ([]*ListResult, error)
|
||||
FetchClusterResults(context.Context, Filter, Pagination) ([]*ListResult, error)
|
||||
// CountNamespacedResults from current PolicyReportResults with a Namespace
|
||||
CountNamespacedResults(Filter) (int, error)
|
||||
CountNamespacedResults(context.Context, Filter) (int, error)
|
||||
// CountClusterResults from current PolicyReportResults
|
||||
CountClusterResults(Filter) (int, error)
|
||||
CountClusterResults(context.Context, Filter) (int, error)
|
||||
// FetchRuleStatusCounts from current PolicyReportResults
|
||||
FetchRuleStatusCounts(policy, rule string) ([]StatusCount, error)
|
||||
FetchRuleStatusCounts(context.Context, string, string) ([]StatusCount, error)
|
||||
// FetchClusterReportLabels from ClusterPolicyReports
|
||||
FetchClusterReportLabels(Filter) (map[string][]string, error)
|
||||
FetchClusterReportLabels(context.Context, Filter) (map[string][]string, error)
|
||||
// FetchNamespacedReportLabels from PolicyReports
|
||||
FetchNamespacedReportLabels(Filter) (map[string][]string, error)
|
||||
FetchNamespacedReportLabels(context.Context, Filter) (map[string][]string, error)
|
||||
}
|
||||
|
|
|
@ -39,8 +39,8 @@ func (h *Handler) TargetsHandler(targets []target.Client) http.HandlerFunc {
|
|||
func (h *Handler) PolicyReportListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
filter := buildFilter(req)
|
||||
count, _ := h.finder.CountPolicyReports(filter)
|
||||
list, err := h.finder.FetchPolicyReports(filter, buildPaginatiomn(req, []string{"namespace", "name"}))
|
||||
count, _ := h.finder.CountPolicyReports(req.Context(), filter)
|
||||
list, err := h.finder.FetchPolicyReports(req.Context(), filter, buildPaginatiomn(req, []string{"namespace", "name"}))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, PolicyReportList{Items: list, Count: count}, err)
|
||||
}
|
||||
|
@ -50,8 +50,8 @@ func (h *Handler) PolicyReportListHandler() http.HandlerFunc {
|
|||
func (h *Handler) ClusterPolicyReportListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
filter := buildFilter(req)
|
||||
count, _ := h.finder.CountClusterPolicyReports(filter)
|
||||
list, err := h.finder.FetchClusterPolicyReports(filter, buildPaginatiomn(req, []string{"namespace", "name"}))
|
||||
count, _ := h.finder.CountClusterPolicyReports(req.Context(), filter)
|
||||
list, err := h.finder.FetchClusterPolicyReports(req.Context(), filter, buildPaginatiomn(req, []string{"namespace", "name"}))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, PolicyReportList{Items: list, Count: count}, err)
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ func (h *Handler) ClusterPolicyReportListHandler() http.HandlerFunc {
|
|||
// ClusterResourcesPolicyListHandler REST API
|
||||
func (h *Handler) ClusterResourcesPolicyListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchClusterPolicies(buildFilter(req))
|
||||
list, err := h.finder.FetchClusterPolicies(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ func (h *Handler) ClusterResourcesPolicyListHandler() http.HandlerFunc {
|
|||
// ClusterResourcesRuleListHandler REST API
|
||||
func (h *Handler) ClusterResourcesRuleListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchClusterRules(buildFilter(req))
|
||||
list, err := h.finder.FetchClusterRules(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ func (h *Handler) ClusterResourcesRuleListHandler() http.HandlerFunc {
|
|||
// NamespacedResourcesPolicyListHandler REST API
|
||||
func (h *Handler) NamespacedResourcesPolicyListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchNamespacedPolicies(buildFilter(req))
|
||||
list, err := h.finder.FetchNamespacedPolicies(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -87,16 +87,25 @@ func (h *Handler) NamespacedResourcesPolicyListHandler() http.HandlerFunc {
|
|||
// NamespacedResourcesRuleListHandler REST API
|
||||
func (h *Handler) NamespacedResourcesRuleListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchNamespacedRules(buildFilter(req))
|
||||
list, err := h.finder.FetchNamespacedRules(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
}
|
||||
|
||||
// CategoryListHandler REST API
|
||||
func (h *Handler) CategoryListHandler() http.HandlerFunc {
|
||||
func (h *Handler) ClusterCategoryListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchCategories(buildFilter(req))
|
||||
list, err := h.finder.FetchClusterCategories(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
}
|
||||
|
||||
// CategoryListHandler REST API
|
||||
func (h *Handler) NamespacedCategoryListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchNamespacedCategories(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -105,7 +114,7 @@ func (h *Handler) CategoryListHandler() http.HandlerFunc {
|
|||
// ClusterResourcesKindListHandler REST API
|
||||
func (h *Handler) ClusterResourcesKindListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchClusterKinds(buildFilter(req))
|
||||
list, err := h.finder.FetchClusterKinds(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -114,7 +123,7 @@ func (h *Handler) ClusterResourcesKindListHandler() http.HandlerFunc {
|
|||
// NamespacedResourcesKindListHandler REST API
|
||||
func (h *Handler) NamespacedResourcesKindListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchNamespacedKinds(buildFilter(req))
|
||||
list, err := h.finder.FetchNamespacedKinds(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -123,7 +132,7 @@ func (h *Handler) NamespacedResourcesKindListHandler() http.HandlerFunc {
|
|||
// ClusterResourcesListHandler REST API
|
||||
func (h *Handler) ClusterResourcesListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchClusterResources(buildFilter(req))
|
||||
list, err := h.finder.FetchClusterResources(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -132,7 +141,7 @@ func (h *Handler) ClusterResourcesListHandler() http.HandlerFunc {
|
|||
// NamespacedResourcesListHandler REST API
|
||||
func (h *Handler) NamespacedResourcesListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchNamespacedResources(buildFilter(req))
|
||||
list, err := h.finder.FetchNamespacedResources(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -141,7 +150,7 @@ func (h *Handler) NamespacedResourcesListHandler() http.HandlerFunc {
|
|||
// ClusterResourcesSourceListHandler REST API
|
||||
func (h *Handler) ClusterResourcesSourceListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchClusterSources()
|
||||
list, err := h.finder.FetchClusterSources(req.Context())
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -150,7 +159,7 @@ func (h *Handler) ClusterResourcesSourceListHandler() http.HandlerFunc {
|
|||
// NamespacedSourceListHandler REST API
|
||||
func (h *Handler) NamespacedSourceListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchNamespacedSources()
|
||||
list, err := h.finder.FetchNamespacedSources(req.Context())
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -159,7 +168,7 @@ func (h *Handler) NamespacedSourceListHandler() http.HandlerFunc {
|
|||
// NamespacedReportLabelListHandler REST API
|
||||
func (h *Handler) NamespacedReportLabelListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchNamespacedReportLabels(buildFilter(req))
|
||||
list, err := h.finder.FetchNamespacedReportLabels(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -168,7 +177,7 @@ func (h *Handler) NamespacedReportLabelListHandler() http.HandlerFunc {
|
|||
// ClusterReportLabelListHandler REST API
|
||||
func (h *Handler) ClusterReportLabelListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchClusterReportLabels(buildFilter(req))
|
||||
list, err := h.finder.FetchClusterReportLabels(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -177,7 +186,7 @@ func (h *Handler) ClusterReportLabelListHandler() http.HandlerFunc {
|
|||
// ClusterResourcesStatusCountHandler REST API
|
||||
func (h *Handler) ClusterResourcesStatusCountHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchStatusCounts(buildFilter(req))
|
||||
list, err := h.finder.FetchClusterStatusCounts(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -186,7 +195,7 @@ func (h *Handler) ClusterResourcesStatusCountHandler() http.HandlerFunc {
|
|||
// NamespacedResourcesStatusCountsHandler REST API
|
||||
func (h *Handler) NamespacedResourcesStatusCountsHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchNamespacedStatusCounts(buildFilter(req))
|
||||
list, err := h.finder.FetchNamespacedStatusCounts(req.Context(), buildFilter(req))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, list, err)
|
||||
}
|
||||
|
@ -196,6 +205,7 @@ func (h *Handler) NamespacedResourcesStatusCountsHandler() http.HandlerFunc {
|
|||
func (h *Handler) RuleStatusCountHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchRuleStatusCounts(
|
||||
req.Context(),
|
||||
req.URL.Query().Get("policy"),
|
||||
req.URL.Query().Get("rule"),
|
||||
)
|
||||
|
@ -208,8 +218,8 @@ func (h *Handler) RuleStatusCountHandler() http.HandlerFunc {
|
|||
func (h *Handler) NamespacedResourcesResultHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
filter := buildFilter(req)
|
||||
count, _ := h.finder.CountNamespacedResults(filter)
|
||||
list, err := h.finder.FetchNamespacedResults(filter, buildPaginatiomn(req, defaultOrder))
|
||||
count, _ := h.finder.CountNamespacedResults(req.Context(), filter)
|
||||
list, err := h.finder.FetchNamespacedResults(req.Context(), filter, buildPaginatiomn(req, defaultOrder))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, ResultList{Items: list, Count: count}, err)
|
||||
}
|
||||
|
@ -219,8 +229,8 @@ func (h *Handler) NamespacedResourcesResultHandler() http.HandlerFunc {
|
|||
func (h *Handler) ClusterResourcesResultHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
filter := buildFilter(req)
|
||||
count, _ := h.finder.CountClusterResults(filter)
|
||||
list, err := h.finder.FetchClusterResults(filter, buildPaginatiomn(req, defaultOrder))
|
||||
count, _ := h.finder.CountClusterResults(req.Context(), filter)
|
||||
list, err := h.finder.FetchClusterResults(req.Context(), filter, buildPaginatiomn(req, defaultOrder))
|
||||
h.logError(err)
|
||||
helper.SendJSONResponse(w, ResultList{Items: list, Count: count}, err)
|
||||
}
|
||||
|
@ -229,7 +239,7 @@ func (h *Handler) ClusterResourcesResultHandler() http.HandlerFunc {
|
|||
// NamespaceListHandler REST API
|
||||
func (h *Handler) NamespaceListHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
list, err := h.finder.FetchNamespaces(Filter{
|
||||
list, err := h.finder.FetchNamespaces(req.Context(), Filter{
|
||||
Sources: req.URL.Query()["sources"],
|
||||
Categories: req.URL.Query()["categories"],
|
||||
Policies: req.URL.Query()["policies"],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package v1_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
@ -12,7 +13,7 @@ import (
|
|||
|
||||
v1 "github.com/kyverno/policy-reporter/pkg/api/v1"
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/sqlite3"
|
||||
"github.com/kyverno/policy-reporter/pkg/database"
|
||||
"github.com/kyverno/policy-reporter/pkg/target"
|
||||
"github.com/kyverno/policy-reporter/pkg/target/loki"
|
||||
)
|
||||
|
@ -120,8 +121,10 @@ var creport = &v1alpha2.ClusterPolicyReport{
|
|||
Summary: v1alpha2.PolicyReportSummary{},
|
||||
}
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func Test_V1_API(t *testing.T) {
|
||||
db, err := sqlite3.NewDatabase("test.db")
|
||||
db, err := database.NewSQLiteDB("test.db")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -129,14 +132,15 @@ func Test_V1_API(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
store, err := sqlite3.NewPolicyReportStore(db)
|
||||
store, err := database.NewStore(db, "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer store.CleanUp()
|
||||
store.PrepareDatabase(ctx)
|
||||
defer store.CleanUp(ctx)
|
||||
|
||||
store.Add(preport)
|
||||
store.Add(creport)
|
||||
store.Add(ctx, preport)
|
||||
store.Add(ctx, creport)
|
||||
|
||||
handl := v1.NewHandler(store)
|
||||
|
||||
|
@ -220,21 +224,41 @@ func Test_V1_API(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("CategoryListHandler", func(t *testing.T) {
|
||||
t.Run("NamespacedCategoryListHandler", func(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/v1/categories", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := handl.CategoryListHandler()
|
||||
handler := handl.NamespacedCategoryListHandler()
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
expected := `["Best Practices","Convention"]`
|
||||
expected := `["Best Practices"]`
|
||||
if !strings.Contains(rr.Body.String(), expected) {
|
||||
t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ClusterCategoryListHandler", func(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/v1/categories", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := handl.ClusterCategoryListHandler()
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
expected := `["Convention"]`
|
||||
if !strings.Contains(rr.Body.String(), expected) {
|
||||
t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected)
|
||||
}
|
||||
|
@ -491,7 +515,7 @@ func Test_V1_API(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("ClusterReportLabelListHandler", func(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/v1/cluster-resources/report-labels?sources=kyverno", nil)
|
||||
req, err := http.NewRequest("GET", "/v1/cluster-resources/report-labels?sources=Kyverno", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -511,7 +535,7 @@ func Test_V1_API(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("ClusterReportLabelListHandler", func(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/v1/namespaced-resources/report-labels?sources=kyverno", nil)
|
||||
req, err := http.NewRequest("GET", "/v1/namespaced-resources/report-labels?sources=Kyverno", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ type PolicyReport struct {
|
|||
Warn int `json:"warn"`
|
||||
Error int `json:"error"`
|
||||
Fail int `json:"fail"`
|
||||
Type string `json:"-"`
|
||||
Created int64 `json:"-"`
|
||||
}
|
||||
|
||||
type PolicyReportList struct {
|
||||
|
@ -41,6 +43,7 @@ type NamespacedStatusCount struct {
|
|||
type NamespaceCount struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Count int `json:"count"`
|
||||
Status string `json:"-"`
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
|
@ -60,7 +63,7 @@ type ListResult struct {
|
|||
Rule string `json:"rule"`
|
||||
Status string `json:"status"`
|
||||
Severity string `json:"severity,omitempty"`
|
||||
Timestamp int `json:"timestamp,omitempty"`
|
||||
Timestamp int64 `json:"timestamp,omitempty"`
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
|
|
2
pkg/cache/cache.go
vendored
2
pkg/cache/cache.go
vendored
|
@ -6,4 +6,6 @@ type Cache interface {
|
|||
RemoveReport(id string)
|
||||
AddReport(report v1alpha2.ReportInterface)
|
||||
GetResults(id string) []string
|
||||
Shared() bool
|
||||
Clear()
|
||||
}
|
||||
|
|
74
pkg/cache/memory.go
vendored
74
pkg/cache/memory.go
vendored
|
@ -9,32 +9,86 @@ import (
|
|||
)
|
||||
|
||||
type inMemoryCache struct {
|
||||
cache *gocache.Cache
|
||||
caches *gocache.Cache
|
||||
}
|
||||
|
||||
func (c *inMemoryCache) AddReport(report v1alpha2.ReportInterface) {
|
||||
c.cache.Set(report.GetID(), reportResultsIds(report), gocache.NoExpiration)
|
||||
cache, ok := c.getCache(report.GetID())
|
||||
|
||||
if !ok {
|
||||
cache = gocache.New(gocache.NoExpiration, 5*time.Minute)
|
||||
c.caches.Set(report.GetID(), cache, gocache.NoExpiration)
|
||||
}
|
||||
|
||||
next := make(map[string]bool)
|
||||
for _, result := range report.GetResults() {
|
||||
cache.Set(result.GetID(), nil, gocache.NoExpiration)
|
||||
next[result.GetID()] = true
|
||||
}
|
||||
|
||||
for id, item := range cache.Items() {
|
||||
if !next[id] && item.Expiration == 0 {
|
||||
cache.Set(id, nil, 6*time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
c.caches.Set(report.GetID(), cache, gocache.NoExpiration)
|
||||
}
|
||||
|
||||
func (c *inMemoryCache) RemoveReport(id string) {
|
||||
val, ok := c.cache.Get(id)
|
||||
if ok {
|
||||
// don't remove it directly to prevent sending results from instantly recreated reports
|
||||
c.cache.Set(id, val, 5*time.Minute)
|
||||
cache, ok := c.getCache(id)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
c.caches.Set(id, cache, 10*time.Minute)
|
||||
}
|
||||
|
||||
func (c *inMemoryCache) getCache(id string) (*gocache.Cache, bool) {
|
||||
cache, ok := c.caches.Get(id)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return cache.(*gocache.Cache), ok
|
||||
}
|
||||
|
||||
func (c *inMemoryCache) GetResults(id string) []string {
|
||||
list, ok := c.cache.Get(id)
|
||||
list := make([]string, 0)
|
||||
|
||||
cache, ok := c.getCache(id)
|
||||
if !ok {
|
||||
return make([]string, 0)
|
||||
return list
|
||||
}
|
||||
|
||||
return list.([]string)
|
||||
for id := range cache.Items() {
|
||||
list = append(list, id)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (c *inMemoryCache) Clear() {
|
||||
for _, cache := range c.caches.Items() {
|
||||
cache.Object.(*gocache.Cache).Flush()
|
||||
}
|
||||
|
||||
c.caches.Flush()
|
||||
}
|
||||
|
||||
func (c *inMemoryCache) Shared() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewInMermoryCache() Cache {
|
||||
cache := gocache.New(gocache.NoExpiration, 5*time.Minute)
|
||||
cache.OnEvicted(func(s string, i interface{}) {
|
||||
if c, ok := i.(*gocache.Cache); ok {
|
||||
c.Flush()
|
||||
}
|
||||
})
|
||||
|
||||
return &inMemoryCache{
|
||||
cache: gocache.New(gocache.NoExpiration, 5*time.Minute),
|
||||
caches: gocache.New(gocache.NoExpiration, 5*time.Minute),
|
||||
}
|
||||
}
|
||||
|
|
64
pkg/cache/redis.go
vendored
64
pkg/cache/redis.go
vendored
|
@ -2,11 +2,12 @@ package cache
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
goredis "github.com/go-redis/redis/v8"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
)
|
||||
|
@ -18,28 +19,73 @@ type redisCache struct {
|
|||
}
|
||||
|
||||
func (r *redisCache) AddReport(report v1alpha2.ReportInterface) {
|
||||
list := reportResultsIds(report)
|
||||
next := make(map[string]bool)
|
||||
|
||||
value, _ := json.Marshal(list)
|
||||
for _, result := range report.GetResults() {
|
||||
r.rdb.Set(context.Background(), r.generateKey(report.GetID(), result.GetID()), nil, 0)
|
||||
next[result.GetID()] = true
|
||||
}
|
||||
|
||||
r.rdb.Set(context.Background(), r.generateKey(report.GetID()), string(value), 0)
|
||||
for _, id := range r.GetResults(report.GetID()) {
|
||||
if !next[id] {
|
||||
r.rdb.Set(context.Background(), r.generateKey(report.GetID(), id), nil, 6*time.Hour)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *redisCache) RemoveReport(id string) {
|
||||
r.rdb.Del(context.Background(), r.generateKey(id))
|
||||
keys, err := r.rdb.Keys(context.Background(), r.generateKeyPattern(id)).Result()
|
||||
if err != nil {
|
||||
zap.L().Error("failed to load report keys", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
r.rdb.Expire(context.Background(), key, 10*time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *redisCache) GetResults(id string) []string {
|
||||
list, _ := r.rdb.Get(context.Background(), r.generateKey(id)).Result()
|
||||
results := make([]string, 0)
|
||||
pattern := r.generateKeyPattern(id)
|
||||
|
||||
json.Unmarshal([]byte(list), &results)
|
||||
keys, err := r.rdb.Keys(context.Background(), pattern).Result()
|
||||
if err != nil {
|
||||
zap.L().Error("failed to load report keys", zap.Error(err))
|
||||
return results
|
||||
}
|
||||
|
||||
prefix := strings.TrimSuffix(pattern, "*")
|
||||
for _, key := range keys {
|
||||
results = append(results, strings.TrimPrefix(key, prefix))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (r *redisCache) generateKey(id string) string {
|
||||
return fmt.Sprintf("%s:%s", r.prefix, id)
|
||||
func (r *redisCache) Shared() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *redisCache) Clear() {
|
||||
results := r.rdb.Keys(context.Background(), r.prefix+":*")
|
||||
|
||||
keys, err := results.Result()
|
||||
if err != nil {
|
||||
zap.L().Error("failed to find cache keys in redis", zap.Error(err))
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
r.rdb.Del(context.Background(), key)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *redisCache) generateKey(report, id string) string {
|
||||
return fmt.Sprintf("%s:%s:%s", r.prefix, report, id)
|
||||
}
|
||||
|
||||
func (r *redisCache) generateKeyPattern(report string) string {
|
||||
return fmt.Sprintf("%s:%s:*", r.prefix, report)
|
||||
}
|
||||
|
||||
func NewRedisCache(prefix string, rdb *goredis.Client, ttl time.Duration) Cache {
|
||||
|
|
|
@ -256,8 +256,21 @@ type Logging struct {
|
|||
Development bool `mapstructure:"development"`
|
||||
}
|
||||
|
||||
type Database struct {
|
||||
Type string `mapstructure:"type"`
|
||||
DSN string `mapstructure:"dsn"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
Database string `mapstructure:"database"`
|
||||
Host string `mapstructure:"host"`
|
||||
EnableSSL bool `mapstructure:"enableSSL"`
|
||||
SecretRef string `mapstructure:"secretRef"`
|
||||
MountedSecret string `mapstructure:"mountedSecret"`
|
||||
}
|
||||
|
||||
// Config of the PolicyReporter
|
||||
type Config struct {
|
||||
Version string
|
||||
Namespace string `mapstructure:"namespace"`
|
||||
Loki Loki `mapstructure:"loki"`
|
||||
Elasticsearch Elasticsearch `mapstructure:"elasticsearch"`
|
||||
|
@ -283,4 +296,5 @@ type Config struct {
|
|||
LeaderElection LeaderElection `mapstructure:"leaderElection"`
|
||||
K8sClient K8sClient `mapstructure:"k8sClient"`
|
||||
Logging Logging `mapstructure:"logging"`
|
||||
Database Database `mapstructure:"database"`
|
||||
}
|
||||
|
|
142
pkg/config/database_factory.go
Normal file
142
pkg/config/database_factory.go
Normal file
|
@ -0,0 +1,142 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/mysqldialect"
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/database"
|
||||
"github.com/kyverno/policy-reporter/pkg/kubernetes/secrets"
|
||||
)
|
||||
|
||||
var ErrNoConfig = errors.New("no configuration for the provider found")
|
||||
|
||||
// DatabaseFactory manages database connection and creation
|
||||
type DatabaseFactory struct {
|
||||
secretClient secrets.Client
|
||||
}
|
||||
|
||||
func (f *DatabaseFactory) NewPostgres(config Database) *bun.DB {
|
||||
if (config.SecretRef != "" && f.secretClient != nil) || config.MountedSecret != "" {
|
||||
f.mapSecretValues(&config, config.SecretRef, config.MountedSecret)
|
||||
}
|
||||
|
||||
if config.Host == "" && config.DSN == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
dsn := config.DSN
|
||||
if config.DSN == "" {
|
||||
sslMode := "disable"
|
||||
if config.EnableSSL {
|
||||
sslMode = "verify-full"
|
||||
}
|
||||
|
||||
dsn = fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", config.Username, config.Password, config.Host, config.Database, sslMode)
|
||||
}
|
||||
|
||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
sqldb.SetMaxOpenConns(25)
|
||||
sqldb.SetMaxIdleConns(25)
|
||||
sqldb.SetConnMaxLifetime(15 * time.Minute)
|
||||
|
||||
return bun.NewDB(sqldb, pgdialect.New())
|
||||
}
|
||||
|
||||
func (f *DatabaseFactory) NewMySQL(config Database) *bun.DB {
|
||||
if (config.SecretRef != "" && f.secretClient != nil) || config.MountedSecret != "" {
|
||||
f.mapSecretValues(&config, config.SecretRef, config.MountedSecret)
|
||||
}
|
||||
|
||||
if config.Host == "" && config.DSN == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
dsn := config.DSN
|
||||
if config.DSN == "" {
|
||||
dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s?tls=%v", config.Username, config.Password, config.Host, config.Database, config.EnableSSL)
|
||||
}
|
||||
|
||||
sqldb, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to create mysql connection", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
sqldb.SetMaxOpenConns(25)
|
||||
sqldb.SetMaxIdleConns(25)
|
||||
sqldb.SetConnMaxLifetime(15 * time.Minute)
|
||||
|
||||
return bun.NewDB(sqldb, mysqldialect.New())
|
||||
}
|
||||
|
||||
func (f *DatabaseFactory) NewSQLite(file string) *bun.DB {
|
||||
sqldb, err := database.NewSQLiteDB(file)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to create sqlite connection", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return sqldb
|
||||
}
|
||||
|
||||
func (f *DatabaseFactory) mapSecretValues(config any, ref, mountedSecret string) {
|
||||
values := secrets.Values{}
|
||||
|
||||
if ref != "" {
|
||||
secretValues, err := f.secretClient.Get(context.Background(), ref)
|
||||
values = secretValues
|
||||
if err != nil {
|
||||
zap.L().Warn("failed to get secret reference", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if mountedSecret != "" {
|
||||
file, err := os.ReadFile(mountedSecret)
|
||||
if err != nil {
|
||||
zap.L().Warn("failed to get mounted secret", zap.Error(err))
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(file, &values)
|
||||
if err != nil {
|
||||
zap.L().Warn("failed to unmarshal mounted secret", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if c, ok := config.(*Database); ok {
|
||||
if values.Host != "" {
|
||||
c.Host = values.Host
|
||||
}
|
||||
if values.Username != "" {
|
||||
c.Username = values.Username
|
||||
}
|
||||
if values.Password != "" {
|
||||
c.Password = values.Password
|
||||
}
|
||||
if values.Database != "" {
|
||||
c.Database = values.Database
|
||||
}
|
||||
if values.DSN != "" {
|
||||
c.DSN = values.DSN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewDatabaseFactory(client secrets.Client) *DatabaseFactory {
|
||||
return &DatabaseFactory{
|
||||
secretClient: client,
|
||||
}
|
||||
}
|
70
pkg/config/database_factory_test.go
Normal file
70
pkg/config/database_factory_test.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package config_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/config"
|
||||
"github.com/kyverno/policy-reporter/pkg/kubernetes/secrets"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
)
|
||||
|
||||
func Test_ResolveDatabase(t *testing.T) {
|
||||
factory := config.NewDatabaseFactory(nil)
|
||||
|
||||
t.Run("SQLite Fallback", func(t *testing.T) {
|
||||
db := factory.NewSQLite("test.db")
|
||||
if db == nil || db.Dialect().Name() != dialect.SQLite {
|
||||
t.Error("Expected SQLite database as fallback")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MySQL", func(t *testing.T) {
|
||||
db := factory.NewMySQL(config.Database{
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
Host: "localhost:3306",
|
||||
EnableSSL: true,
|
||||
})
|
||||
if db == nil || db.Dialect().Name() != dialect.MySQL {
|
||||
t.Error("Expected MySQL DB")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("PostgreSQL", func(t *testing.T) {
|
||||
db := factory.NewPostgres(config.Database{
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
Host: "localhost:5432",
|
||||
EnableSSL: true,
|
||||
})
|
||||
if db == nil || db.Dialect().Name() != dialect.PG {
|
||||
t.Error("Expected PostgreSQL")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DatabaseValuesFromSecret(t *testing.T) {
|
||||
factory := config.NewDatabaseFactory(secrets.NewClient(newFakeClient()))
|
||||
mountSecret()
|
||||
|
||||
t.Run("Values from SecretRef", func(t *testing.T) {
|
||||
db := factory.NewPostgres(config.Database{SecretRef: secretName, EnableSSL: false})
|
||||
if db == nil {
|
||||
t.Error("Expected PostgreSQL connection created")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Values from MountedSecret", func(t *testing.T) {
|
||||
db := factory.NewMySQL(config.Database{MountedSecret: mountedSecret, EnableSSL: false})
|
||||
if db == nil {
|
||||
t.Error("Expected MySQL connection created")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Get none existing mounted secret skips target", func(t *testing.T) {
|
||||
db := factory.NewPostgres(config.Database{MountedSecret: "no-exists"})
|
||||
if db != nil {
|
||||
t.Error("Expected no connection created without host or DSN config")
|
||||
}
|
||||
})
|
||||
}
|
|
@ -81,6 +81,10 @@ func Load(cmd *cobra.Command) (*Config, error) {
|
|||
v.BindPFlag("leaderElection.lockName", flag)
|
||||
}
|
||||
|
||||
if flag := cmd.Flags().Lookup("pod-name"); flag != nil {
|
||||
v.BindPFlag("leaderElection.podName", flag)
|
||||
}
|
||||
|
||||
if err := v.BindEnv("leaderElection.podName", "POD_NAME"); err != nil {
|
||||
log.Printf("[WARNING] failed to bind env POD_NAME")
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ func createCMD() *cobra.Command {
|
|||
cmd.Flags().BoolP("rest-enabled", "r", false, "Enable Policy Reporter's REST API")
|
||||
cmd.Flags().Bool("profile", false, "Enable application profiling with pprof")
|
||||
cmd.Flags().StringP("template-dir", "t", "./templates", "template directory for email reports")
|
||||
cmd.Flags().String("lease-name", "policy-reporter", "name of the LeaseLock")
|
||||
cmd.Flags().String("pod-name", "policy-reporter", "name of the pod, used for leaderelection")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -33,6 +35,8 @@ func Test_Load(t *testing.T) {
|
|||
_ = cmd.Flags().Set("profile", "1")
|
||||
_ = cmd.Flags().Set("template-dir", "/app/templates")
|
||||
_ = cmd.Flags().Set("dbfile", "")
|
||||
_ = cmd.Flags().Set("pod-name", "policy-reporter")
|
||||
_ = cmd.Flags().Set("lease-name", "policy-reporter")
|
||||
|
||||
c, err := config.Load(cmd)
|
||||
if err != nil {
|
||||
|
|
50
pkg/config/readinessprobe.go
Normal file
50
pkg/config/readinessprobe.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ReadinessProbe struct {
|
||||
config *Config
|
||||
|
||||
ready chan bool
|
||||
running bool
|
||||
}
|
||||
|
||||
func (r *ReadinessProbe) required() bool {
|
||||
if !r.config.REST.Enabled {
|
||||
return false
|
||||
}
|
||||
|
||||
return r.config.LeaderElection.Enabled
|
||||
}
|
||||
|
||||
func (r *ReadinessProbe) Ready() {
|
||||
if r.required() && !r.running {
|
||||
go func() {
|
||||
zap.L().Debug("readiness probe ready")
|
||||
r.ready <- true
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReadinessProbe) Wait() {
|
||||
if r.required() && !r.running {
|
||||
r.running = <-r.ready
|
||||
close(r.ready)
|
||||
zap.L().Debug("readiness probe finished")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReadinessProbe) Running() bool {
|
||||
return r.running
|
||||
}
|
||||
|
||||
func NewReadinessProbe(config *Config) *ReadinessProbe {
|
||||
return &ReadinessProbe{
|
||||
config: config,
|
||||
ready: make(chan bool),
|
||||
running: false,
|
||||
}
|
||||
}
|
53
pkg/config/readinessprobe_test.go
Normal file
53
pkg/config/readinessprobe_test.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package config_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/config"
|
||||
)
|
||||
|
||||
func Test_ReadinessProbe(t *testing.T) {
|
||||
t.Run("immediate return without REST enabled", func(t *testing.T) {
|
||||
rdy := config.NewReadinessProbe(
|
||||
&config.Config{
|
||||
REST: config.REST{Enabled: false},
|
||||
LeaderElection: config.LeaderElection{Enabled: false},
|
||||
},
|
||||
)
|
||||
|
||||
rdy.Wait()
|
||||
})
|
||||
|
||||
t.Run("immediate return without LeaderElection enabled", func(t *testing.T) {
|
||||
rdy := config.NewReadinessProbe(
|
||||
&config.Config{
|
||||
REST: config.REST{Enabled: true},
|
||||
LeaderElection: config.LeaderElection{Enabled: false},
|
||||
},
|
||||
)
|
||||
|
||||
rdy.Wait()
|
||||
})
|
||||
|
||||
t.Run("wait for ready state", func(t *testing.T) {
|
||||
rdy := config.NewReadinessProbe(
|
||||
&config.Config{
|
||||
REST: config.REST{Enabled: true},
|
||||
LeaderElection: config.LeaderElection{Enabled: true},
|
||||
},
|
||||
)
|
||||
|
||||
if rdy.Running() {
|
||||
t.Error("should not be running until ready was called")
|
||||
}
|
||||
|
||||
go func() {
|
||||
rdy.Wait()
|
||||
if !rdy.Running() {
|
||||
t.Error("should be running after ready was called")
|
||||
}
|
||||
}()
|
||||
|
||||
rdy.Ready()
|
||||
})
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
goredis "github.com/go-redis/redis/v8"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
mail "github.com/xhit/go-simple-mail/v2"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
@ -18,6 +20,7 @@ import (
|
|||
"github.com/kyverno/policy-reporter/pkg/cache"
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/client/clientset/versioned"
|
||||
wgpolicyk8sv1alpha2 "github.com/kyverno/policy-reporter/pkg/crd/client/clientset/versioned/typed/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/database"
|
||||
"github.com/kyverno/policy-reporter/pkg/email"
|
||||
"github.com/kyverno/policy-reporter/pkg/email/summary"
|
||||
"github.com/kyverno/policy-reporter/pkg/email/violations"
|
||||
|
@ -27,7 +30,6 @@ 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/sqlite3"
|
||||
"github.com/kyverno/policy-reporter/pkg/target"
|
||||
"github.com/kyverno/policy-reporter/pkg/validate"
|
||||
)
|
||||
|
@ -38,13 +40,15 @@ type Resolver struct {
|
|||
k8sConfig *rest.Config
|
||||
mapper report.Mapper
|
||||
publisher report.EventPublisher
|
||||
policyStore sqlite3.PolicyReportStore
|
||||
policyStore *database.Store
|
||||
database *bun.DB
|
||||
policyReportClient report.PolicyReportClient
|
||||
leaderElector *leaderelection.Client
|
||||
targetClients []target.Client
|
||||
resultCache cache.Cache
|
||||
targetsCreated bool
|
||||
logger *zap.Logger
|
||||
resultListener *listener.ResultListener
|
||||
}
|
||||
|
||||
// APIServer resolver method
|
||||
|
@ -63,17 +67,43 @@ func (r *Resolver) APIServer(synced func() bool) api.Server {
|
|||
}
|
||||
|
||||
// Database resolver method
|
||||
func (r *Resolver) Database() (*sql.DB, error) {
|
||||
return sqlite3.NewDatabase(r.config.DBFile)
|
||||
func (r *Resolver) Database() *bun.DB {
|
||||
if r.database != nil {
|
||||
return r.database
|
||||
}
|
||||
|
||||
factory := r.DatabaseFactory()
|
||||
|
||||
switch r.config.Database.Type {
|
||||
case database.MySQL:
|
||||
if r.database = factory.NewMySQL(r.config.Database); r.database != nil {
|
||||
zap.L().Info("mysql connection created")
|
||||
return r.database
|
||||
}
|
||||
case database.MariaDB:
|
||||
if r.database = factory.NewMySQL(r.config.Database); r.database != nil {
|
||||
zap.L().Info("mariadb connection created")
|
||||
return r.database
|
||||
}
|
||||
case database.PostgreSQL:
|
||||
if r.database = factory.NewPostgres(r.config.Database); r.database != nil {
|
||||
zap.L().Info("postgres connection created")
|
||||
return r.database
|
||||
}
|
||||
}
|
||||
|
||||
zap.L().Info("sqlite connection created")
|
||||
r.database = factory.NewSQLite(r.config.DBFile)
|
||||
return r.database
|
||||
}
|
||||
|
||||
// PolicyReportStore resolver method
|
||||
func (r *Resolver) PolicyReportStore(db *sql.DB) (sqlite3.PolicyReportStore, error) {
|
||||
func (r *Resolver) PolicyReportStore(db *bun.DB) (*database.Store, error) {
|
||||
if r.policyStore != nil {
|
||||
return r.policyStore, nil
|
||||
}
|
||||
|
||||
s, err := sqlite3.NewPolicyReportStore(db)
|
||||
s, err := database.NewStore(db, r.config.Version)
|
||||
r.policyStore = s
|
||||
|
||||
return r.policyStore, err
|
||||
|
@ -130,20 +160,49 @@ func (r *Resolver) Queue() (*kubernetes.Queue, error) {
|
|||
), nil
|
||||
}
|
||||
|
||||
// RegisterSendResultListener resolver method
|
||||
func (r *Resolver) RegisterSendResultListener() {
|
||||
// RegisterNewResultsListener resolver method
|
||||
func (r *Resolver) RegisterNewResultsListener() {
|
||||
targets := r.TargetClients()
|
||||
if len(targets) > 0 {
|
||||
newResultListener := listener.NewResultListener(r.SkipExistingOnStartup(), r.ResultCache(), time.Now())
|
||||
newResultListener.RegisterListener(listener.NewSendResultListener(targets, r.Mapper()))
|
||||
|
||||
r.EventPublisher().RegisterListener(listener.NewResults, newResultListener.Listen)
|
||||
if len(targets) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
newResultListener := listener.NewResultListener(r.SkipExistingOnStartup(), r.ResultCache(), time.Now())
|
||||
r.resultListener = newResultListener
|
||||
r.EventPublisher().RegisterListener(listener.NewResults, newResultListener.Listen)
|
||||
}
|
||||
|
||||
// RegisterSendResultListener resolver method
|
||||
func (r *Resolver) RegisterStoreListener(store report.PolicyReportStore) {
|
||||
r.EventPublisher().RegisterListener(listener.Store, listener.NewStoreListener(store))
|
||||
func (r *Resolver) RegisterSendResultListener() {
|
||||
targets := r.TargetClients()
|
||||
|
||||
if len(targets) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if r.resultListener == nil {
|
||||
r.RegisterNewResultsListener()
|
||||
}
|
||||
|
||||
r.resultListener.RegisterListener(listener.NewSendResultListener(targets, r.Mapper()))
|
||||
}
|
||||
|
||||
// RegisterSendResultListener resolver method
|
||||
func (r *Resolver) UnregisterSendResultListener() {
|
||||
if r.ResultCache().Shared() {
|
||||
r.EventPublisher().UnregisterListener(listener.NewResults)
|
||||
}
|
||||
|
||||
if r.resultListener == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.resultListener.UnregisterListener()
|
||||
}
|
||||
|
||||
// RegisterSendResultListener resolver method
|
||||
func (r *Resolver) RegisterStoreListener(ctx context.Context, store report.PolicyReportStore) {
|
||||
r.EventPublisher().RegisterListener(listener.Store, listener.NewStoreListener(ctx, store))
|
||||
}
|
||||
|
||||
// RegisterMetricsListener resolver method
|
||||
|
@ -190,7 +249,12 @@ func (r *Resolver) SecretClient() secrets.Client {
|
|||
|
||||
func (r *Resolver) TargetFactory() *TargetFactory {
|
||||
return &TargetFactory{
|
||||
namespace: r.config.Namespace,
|
||||
secretClient: r.SecretClient(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Resolver) DatabaseFactory() *DatabaseFactory {
|
||||
return &DatabaseFactory{
|
||||
secretClient: r.SecretClient(),
|
||||
}
|
||||
}
|
||||
|
@ -230,6 +294,18 @@ func (r *Resolver) HasTargets() bool {
|
|||
return len(r.TargetClients()) > 0
|
||||
}
|
||||
|
||||
func (r *Resolver) EnableLeaderElection() bool {
|
||||
if !r.config.LeaderElection.Enabled {
|
||||
return false
|
||||
}
|
||||
|
||||
if !r.HasTargets() && r.Database().Dialect().Name() == dialect.SQLite {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// SkipExistingOnStartup config method
|
||||
func (r *Resolver) SkipExistingOnStartup() bool {
|
||||
for _, client := range r.TargetClients() {
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package config_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/config"
|
||||
"github.com/kyverno/policy-reporter/pkg/database"
|
||||
"github.com/kyverno/policy-reporter/pkg/report"
|
||||
)
|
||||
|
||||
|
@ -254,7 +256,7 @@ func Test_ResolveLeaderElectionClient(t *testing.T) {
|
|||
|
||||
func Test_ResolvePolicyStore(t *testing.T) {
|
||||
resolver := config.NewResolver(&config.Config{DBFile: "test.db"}, &rest.Config{})
|
||||
db, _ := resolver.Database()
|
||||
db := resolver.Database()
|
||||
defer db.Close()
|
||||
|
||||
store1, err := resolver.PolicyReportStore(db)
|
||||
|
@ -401,7 +403,7 @@ func Test_ResolveSecretCClientWithInvalidK8sConfig(t *testing.T) {
|
|||
func Test_RegisterStoreListener(t *testing.T) {
|
||||
t.Run("Register StoreListener", func(t *testing.T) {
|
||||
resolver := config.NewResolver(testConfig, &rest.Config{})
|
||||
resolver.RegisterStoreListener(report.NewPolicyReportStore())
|
||||
resolver.RegisterStoreListener(context.Background(), report.NewPolicyReportStore())
|
||||
|
||||
if len(resolver.EventPublisher().GetListener()) != 1 {
|
||||
t.Error("Expected one Listener to be registered")
|
||||
|
@ -532,3 +534,42 @@ func Test_ResolveLogger(t *testing.T) {
|
|||
t.Error("A second call resolver.Mapper() should return the cached first cache")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ResolveEnableLeaderElection(t *testing.T) {
|
||||
t.Run("general disabled", func(t *testing.T) {
|
||||
resolver := config.NewResolver(&config.Config{
|
||||
LeaderElection: config.LeaderElection{Enabled: false},
|
||||
Loki: config.Loki{Host: "localhost:3100"},
|
||||
Database: config.Database{Type: database.MySQL},
|
||||
}, &rest.Config{})
|
||||
|
||||
if resolver.EnableLeaderElection() {
|
||||
t.Error("leaderelection should be not enabled if its general disabled")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no pushes and SQLite Database", func(t *testing.T) {
|
||||
resolver := config.NewResolver(&config.Config{
|
||||
LeaderElection: config.LeaderElection{Enabled: true},
|
||||
Database: config.Database{Type: database.SQLite},
|
||||
DBFile: "test.db",
|
||||
}, &rest.Config{})
|
||||
|
||||
if resolver.EnableLeaderElection() {
|
||||
t.Error("leaderelection should be not enabled if no pushes configured and SQLite is used")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("enabled if pushes defined", func(t *testing.T) {
|
||||
resolver := config.NewResolver(&config.Config{
|
||||
LeaderElection: config.LeaderElection{Enabled: true},
|
||||
Database: config.Database{Type: database.SQLite},
|
||||
Loki: config.Loki{Host: "localhost:3100"},
|
||||
DBFile: "test.db",
|
||||
}, &rest.Config{})
|
||||
|
||||
if !resolver.EnableLeaderElection() {
|
||||
t.Error("leaderelection should be enabled if general enabled and targets configured")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import (
|
|||
// TargetFactory manages target creation
|
||||
type TargetFactory struct {
|
||||
secretClient secrets.Client
|
||||
namespace string
|
||||
}
|
||||
|
||||
// LokiClients resolver method
|
||||
|
@ -990,6 +989,6 @@ func createReportFilter(filter TargetFilter) *report.ReportFilter {
|
|||
)
|
||||
}
|
||||
|
||||
func NewTargetFactory(namespace string, secretClient secrets.Client) *TargetFactory {
|
||||
return &TargetFactory{namespace: namespace, secretClient: secretClient}
|
||||
func NewTargetFactory(secretClient secrets.Client) *TargetFactory {
|
||||
return &TargetFactory{secretClient: secretClient}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ func newFakeClient() v1.SecretInterface {
|
|||
"kmsKeyId": []byte("kmsKeyId"),
|
||||
"token": []byte("token"),
|
||||
"credentials": []byte(`{"token": "token", "type": "authorized_user"}`),
|
||||
"database": []byte("database"),
|
||||
"dsn": []byte(""),
|
||||
},
|
||||
}).CoreV1().Secrets("default")
|
||||
}
|
||||
|
@ -52,6 +54,8 @@ func mountSecret() {
|
|||
KmsKeyID: "kmsKeyId",
|
||||
Token: "token",
|
||||
Credentials: `{"token": "token", "type": "authorized_user"}`,
|
||||
Database: "database",
|
||||
DSN: "",
|
||||
}
|
||||
file, _ := json.MarshalIndent(secretValues, "", " ")
|
||||
_ = os.WriteFile(mountedSecret, file, 0o644)
|
||||
|
@ -60,7 +64,7 @@ func mountSecret() {
|
|||
var logger = zap.NewNop()
|
||||
|
||||
func Test_ResolveTarget(t *testing.T) {
|
||||
factory := config.NewTargetFactory("", nil)
|
||||
factory := config.NewTargetFactory(nil)
|
||||
|
||||
t.Run("Loki", func(t *testing.T) {
|
||||
clients := factory.LokiClients(testConfig.Loki)
|
||||
|
@ -125,7 +129,7 @@ func Test_ResolveTarget(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_ResolveTargetWithoutHost(t *testing.T) {
|
||||
factory := config.NewTargetFactory("", nil)
|
||||
factory := config.NewTargetFactory(nil)
|
||||
|
||||
t.Run("Loki", func(t *testing.T) {
|
||||
if len(factory.LokiClients(config.Loki{})) != 0 {
|
||||
|
@ -260,7 +264,7 @@ func Test_ResolveTargetWithoutHost(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_GetValuesFromSecret(t *testing.T) {
|
||||
factory := config.NewTargetFactory("default", secrets.NewClient(newFakeClient()))
|
||||
factory := config.NewTargetFactory(secrets.NewClient(newFakeClient()))
|
||||
|
||||
t.Run("Get Loki values from Secret", func(t *testing.T) {
|
||||
clients := factory.LokiClients(config.Loki{TargetBaseOptions: config.TargetBaseOptions{SecretRef: secretName}})
|
||||
|
@ -512,7 +516,7 @@ func Test_GetValuesFromSecret(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_GetValuesFromMountedSecret(t *testing.T) {
|
||||
factory := config.NewTargetFactory("", nil)
|
||||
factory := config.NewTargetFactory(nil)
|
||||
mountSecret()
|
||||
defer os.Remove(mountedSecret)
|
||||
|
||||
|
|
996
pkg/database/bun.go
Normal file
996
pkg/database/bun.go
Normal file
|
@ -0,0 +1,996 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/dialect/sqlitedialect"
|
||||
"go.uber.org/zap"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
type Type = string
|
||||
|
||||
const (
|
||||
MySQL Type = "mysql"
|
||||
MariaDB Type = "mariadb"
|
||||
PostgreSQL Type = "postgres"
|
||||
SQLite Type = "sqlite"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *bun.DB
|
||||
version string
|
||||
|
||||
jsonExtractLayout string
|
||||
}
|
||||
|
||||
func (s *Store) CreateSchemas(ctx context.Context) error {
|
||||
if s.db.Dialect().Name() == dialect.SQLite {
|
||||
if _, err := s.db.Exec("PRAGMA foreign_keys = ON"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err := s.db.
|
||||
NewCreateTable().
|
||||
IfNotExists().
|
||||
Model((*Config)(nil)).
|
||||
Exec(ctx)
|
||||
logOnError("create policy_report table", err)
|
||||
|
||||
_, err = s.db.
|
||||
NewCreateTable().
|
||||
IfNotExists().
|
||||
Model((*PolicyReport)(nil)).
|
||||
Exec(ctx)
|
||||
logOnError("create policy_report table", err)
|
||||
|
||||
_, err = s.db.
|
||||
NewCreateTable().
|
||||
IfNotExists().
|
||||
Model((*PolicyReportResult)(nil)).
|
||||
ForeignKey(`(policy_report_id) REFERENCES policy_report(id) ON DELETE CASCADE`).
|
||||
Exec(ctx)
|
||||
logOnError("create policy_report_result table", err)
|
||||
|
||||
_, err = s.db.
|
||||
NewCreateTable().
|
||||
IfNotExists().
|
||||
Model((*PolicyReportFilter)(nil)).
|
||||
ForeignKey(`(policy_report_id) REFERENCES policy_report(id) ON DELETE CASCADE`).
|
||||
Exec(ctx)
|
||||
logOnError("create policy_report_filter table", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) DropSchema(ctx context.Context) error {
|
||||
_, err := s.db.NewDropTable().
|
||||
IfExists().
|
||||
Model((*Config)(nil)).
|
||||
Exec(ctx)
|
||||
logOnError("drop policy_report_config table", err)
|
||||
|
||||
_, err = s.db.NewDropTable().
|
||||
IfExists().
|
||||
Model((*PolicyReportFilter)(nil)).
|
||||
Exec(ctx)
|
||||
logOnError("drop policy_report_filter table", err)
|
||||
|
||||
_, err = s.db.NewDropTable().
|
||||
IfExists().
|
||||
Model((*PolicyReportResult)(nil)).
|
||||
Exec(ctx)
|
||||
logOnError("drop policy_report_result table", err)
|
||||
|
||||
_, err = s.db.NewDropTable().
|
||||
IfExists().
|
||||
Model((*PolicyReport)(nil)).
|
||||
Exec(ctx)
|
||||
logOnError("drop policy_report table", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) Add(ctx context.Context, report v1alpha2.ReportInterface) error {
|
||||
_, err := s.db.NewInsert().Model(MapPolicyReport(report)).Exec(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to persist policy report", zap.Error(err))
|
||||
}
|
||||
|
||||
filters := chunkSlice(MapPolicyReportFilter(report), 50)
|
||||
for _, list := range filters {
|
||||
_, err = s.db.NewInsert().Ignore().Model(&list).Exec(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to bulk import policy report filter", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
results := chunkSlice(MapPolicyReportResults(report), 50)
|
||||
for _, list := range results {
|
||||
_, err = s.db.NewInsert().Ignore().Model(&list).Exec(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to bulk import policy report results", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) Update(ctx context.Context, report v1alpha2.ReportInterface) error {
|
||||
err := s.Remove(ctx, report.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Add(ctx, report)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) Remove(ctx context.Context, id string) error {
|
||||
_, err := s.db.NewDelete().Model((*PolicyReport)(nil)).Where("id = ?", id).Exec(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to remove previews policy report", zap.Error(err))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) CleanUp(ctx context.Context) error {
|
||||
_, err := s.db.NewDelete().Model((*PolicyReport)(nil)).Where("id is not null").Exec(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to remove policy reports", zap.Error(err))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) FetchPolicyReports(ctx context.Context, filter api.Filter, pagination api.Pagination) ([]*api.PolicyReport, error) {
|
||||
list := []*api.PolicyReport{}
|
||||
query := s.db.NewSelect().Model((*PolicyReport)(nil))
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportFilter(query, filter)
|
||||
query.Where(`pr.type = ?`, report.PolicyReportType)
|
||||
|
||||
addPagination(query, pagination)
|
||||
|
||||
err := query.Scan(ctx, &list)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to select policy report results", zap.Error(err), zap.Any("filter", filter), zap.Any("pagination", pagination))
|
||||
}
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (s *Store) CountPolicyReports(ctx context.Context, filter api.Filter) (int, error) {
|
||||
query := s.db.NewSelect().Model((*PolicyReport)(nil))
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportFilter(query, filter)
|
||||
query.Where(`pr.type = ?`, report.PolicyReportType)
|
||||
|
||||
count, err := query.Count(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to select policy report results", zap.Error(err), zap.Any("filter", filter))
|
||||
}
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (s *Store) FetchNamespacedReportLabels(ctx context.Context, filter api.Filter) (map[string][]string, error) {
|
||||
results := []string{}
|
||||
list := make(map[string][]string)
|
||||
|
||||
query := s.db.NewSelect().
|
||||
TableExpr("policy_report as pr").
|
||||
Distinct().
|
||||
Where(`pr.type = ?`, report.PolicyReportType)
|
||||
|
||||
if s.db.Dialect().Name() == dialect.PG {
|
||||
query.ColumnExpr("labels::text")
|
||||
} else {
|
||||
query.Column("labels")
|
||||
}
|
||||
|
||||
addPolicyReportFilter(query, filter)
|
||||
|
||||
err := query.Scan(ctx, &results)
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
||||
for _, labels := range results {
|
||||
for key, value := range convertJSONToMap(labels) {
|
||||
_, ok := list[key]
|
||||
contained := contains(value, list[key])
|
||||
|
||||
if ok && !contained {
|
||||
list[key] = append(list[key], value)
|
||||
continue
|
||||
} else if ok && contained {
|
||||
continue
|
||||
}
|
||||
|
||||
list[key] = []string{value}
|
||||
}
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Store) FetchClusterPolicyReports(ctx context.Context, filter api.Filter, pagination api.Pagination) ([]*api.PolicyReport, error) {
|
||||
list := []*api.PolicyReport{}
|
||||
query := s.db.NewSelect().Model((*PolicyReport)(nil))
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportFilter(query, filter)
|
||||
query.Where(`pr.type = ?`, report.ClusterPolicyReportType)
|
||||
|
||||
addPagination(query, pagination)
|
||||
|
||||
err := query.Scan(ctx, &list)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to select policy report results", zap.Error(err), zap.Any("filter", filter), zap.Any("pagination", pagination))
|
||||
}
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (s *Store) CountClusterPolicyReports(ctx context.Context, filter api.Filter) (int, error) {
|
||||
query := s.db.NewSelect().Model((*PolicyReport)(nil))
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportFilter(query, filter)
|
||||
query.Where(`pr.type = ?`, report.ClusterPolicyReportType)
|
||||
|
||||
count, err := query.Count(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to select policy report results", zap.Error(err), zap.Any("filter", filter))
|
||||
}
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (s *Store) FetchClusterReportLabels(ctx context.Context, filter api.Filter) (map[string][]string, error) {
|
||||
results := []string{}
|
||||
list := make(map[string][]string)
|
||||
|
||||
query := s.db.NewSelect().
|
||||
TableExpr("policy_report as pr").
|
||||
Distinct().
|
||||
Where(`pr.type = ?`, report.ClusterPolicyReportType)
|
||||
|
||||
if s.db.Dialect().Name() == dialect.PG {
|
||||
query.ColumnExpr("labels::text")
|
||||
} else {
|
||||
query.Column("labels")
|
||||
}
|
||||
|
||||
addPolicyReportFilter(query, filter)
|
||||
|
||||
err := query.Scan(ctx, &results)
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
||||
for _, labels := range results {
|
||||
for key, value := range convertJSONToMap(labels) {
|
||||
_, ok := list[key]
|
||||
contained := contains(value, list[key])
|
||||
|
||||
if ok && !contained {
|
||||
list[key] = append(list[key], value)
|
||||
continue
|
||||
} else if ok && contained {
|
||||
continue
|
||||
}
|
||||
|
||||
list[key] = []string{value}
|
||||
}
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Store) FetchClusterRules(ctx context.Context, filter api.Filter) ([]string, error) {
|
||||
list := make([]string, 0)
|
||||
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
TableExpr("policy_report_result as r").
|
||||
Column("rule").
|
||||
Distinct().
|
||||
Order("rule ASC").
|
||||
Where(`r.resource_namespace = ''`)
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = r.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportResultFilter(query, filter)
|
||||
|
||||
query.Scan(ctx, &list)
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Store) FetchClusterResources(ctx context.Context, filter api.Filter) ([]*api.Resource, error) {
|
||||
list := make([]*api.Resource, 0)
|
||||
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
TableExpr("policy_report_result as r").
|
||||
ColumnExpr("resource_name as name, resource_kind as kind").
|
||||
Distinct().
|
||||
Order("kind ASC", "name ASC").
|
||||
Where(`r.resource_namespace = ''`)
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = r.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportResultFilter(query, filter)
|
||||
|
||||
query.Scan(ctx, &list)
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Store) FetchClusterPolicies(ctx context.Context, filter api.Filter) ([]string, error) {
|
||||
return s.fetchFilterOptions(ctx, "policy", filter, false)
|
||||
}
|
||||
|
||||
func (s *Store) FetchClusterKinds(ctx context.Context, filter api.Filter) ([]string, error) {
|
||||
return s.fetchFilterOptions(ctx, "kind", filter, false)
|
||||
}
|
||||
|
||||
func (s *Store) FetchClusterCategories(ctx context.Context, filter api.Filter) ([]string, error) {
|
||||
return s.fetchFilterOptions(ctx, "category", filter, false)
|
||||
}
|
||||
|
||||
func (s *Store) FetchClusterSources(ctx context.Context) ([]string, error) {
|
||||
return s.fetchFilterOptions(ctx, "source", api.Filter{}, false)
|
||||
}
|
||||
|
||||
func (s *Store) FetchClusterStatusCounts(ctx context.Context, filter api.Filter) ([]api.StatusCount, error) {
|
||||
var list map[string]api.StatusCount
|
||||
|
||||
if len(filter.Status) == 0 {
|
||||
list = map[string]api.StatusCount{
|
||||
v1alpha2.StatusPass: {Status: v1alpha2.StatusPass},
|
||||
v1alpha2.StatusFail: {Status: v1alpha2.StatusFail},
|
||||
v1alpha2.StatusWarn: {Status: v1alpha2.StatusWarn},
|
||||
v1alpha2.StatusError: {Status: v1alpha2.StatusError},
|
||||
v1alpha2.StatusSkip: {Status: v1alpha2.StatusSkip},
|
||||
}
|
||||
} else {
|
||||
list = map[string]api.StatusCount{}
|
||||
|
||||
for _, status := range filter.Status {
|
||||
list[status] = api.StatusCount{Status: status}
|
||||
}
|
||||
}
|
||||
|
||||
counts := make([]api.StatusCount, 0, len(list))
|
||||
results := make([]api.StatusCount, 0)
|
||||
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
TableExpr("policy_report_filter as f").
|
||||
ColumnExpr("SUM(f.count) as count, f.result as status").
|
||||
Where(`f.namespace = ''`).
|
||||
Group("status")
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = f.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportFilterFilter(query, filter)
|
||||
|
||||
err := query.Scan(ctx, &results)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to load cluster status counts", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, count := range results {
|
||||
list[count.Status] = count
|
||||
}
|
||||
|
||||
for _, count := range list {
|
||||
counts = append(counts, count)
|
||||
}
|
||||
|
||||
return counts, nil
|
||||
}
|
||||
|
||||
func (s *Store) FetchClusterResults(ctx context.Context, filter api.Filter, pagination api.Pagination) ([]*api.ListResult, error) {
|
||||
results := make([]*PolicyReportResult, 0)
|
||||
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
Model(&results).
|
||||
Where(`r.resource_namespace = ''`)
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = r.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportResultFilter(query, filter)
|
||||
addPagination(query, pagination)
|
||||
|
||||
err := query.Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return MapListResult(results), nil
|
||||
}
|
||||
|
||||
func (s *Store) CountClusterResults(ctx context.Context, filter api.Filter) (int, error) {
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
Model((*PolicyReportResult)(nil)).
|
||||
Where(`r.resource_namespace = ''`)
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = r.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportResultFilter(query, filter)
|
||||
|
||||
return query.Count(ctx)
|
||||
}
|
||||
|
||||
func (s *Store) FetchNamespacedRules(ctx context.Context, filter api.Filter) ([]string, error) {
|
||||
list := make([]string, 0)
|
||||
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
TableExpr("policy_report_result as r").
|
||||
Column("rule").
|
||||
Distinct().
|
||||
Order("rule ASC").
|
||||
Where(`r.resource_namespace != ''`)
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = r.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportResultFilter(query, filter)
|
||||
|
||||
query.Scan(ctx, &list)
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Store) FetchNamespacedResources(ctx context.Context, filter api.Filter) ([]*api.Resource, error) {
|
||||
list := make([]*api.Resource, 0)
|
||||
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
TableExpr("policy_report_result as r").
|
||||
ColumnExpr("resource_name as name, resource_kind as kind").
|
||||
Distinct().
|
||||
Order("kind ASC", "name ASC").
|
||||
Where(`r.resource_namespace != ''`)
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = r.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportResultFilter(query, filter)
|
||||
|
||||
query.Scan(ctx, &list)
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Store) FetchNamespacedPolicies(ctx context.Context, filter api.Filter) ([]string, error) {
|
||||
return s.fetchFilterOptions(ctx, "policy", filter, true)
|
||||
}
|
||||
|
||||
func (s *Store) FetchNamespacedKinds(ctx context.Context, filter api.Filter) ([]string, error) {
|
||||
return s.fetchFilterOptions(ctx, "kind", filter, true)
|
||||
}
|
||||
|
||||
func (s *Store) FetchNamespacedCategories(ctx context.Context, filter api.Filter) ([]string, error) {
|
||||
return s.fetchFilterOptions(ctx, "category", filter, true)
|
||||
}
|
||||
|
||||
func (s *Store) FetchNamespacedSources(ctx context.Context) ([]string, error) {
|
||||
return s.fetchFilterOptions(ctx, "source", api.Filter{}, true)
|
||||
}
|
||||
|
||||
func (s *Store) FetchNamespaces(ctx context.Context, filter api.Filter) ([]string, error) {
|
||||
return s.fetchFilterOptions(ctx, "f.namespace", filter, true)
|
||||
}
|
||||
|
||||
func (s *Store) FetchNamespacedStatusCounts(ctx context.Context, filter api.Filter) ([]api.NamespacedStatusCount, error) {
|
||||
var list map[string][]api.NamespaceCount
|
||||
|
||||
if len(filter.Status) == 0 {
|
||||
list = map[string][]api.NamespaceCount{
|
||||
v1alpha2.StatusPass: make([]api.NamespaceCount, 0),
|
||||
v1alpha2.StatusFail: make([]api.NamespaceCount, 0),
|
||||
v1alpha2.StatusWarn: make([]api.NamespaceCount, 0),
|
||||
v1alpha2.StatusError: make([]api.NamespaceCount, 0),
|
||||
v1alpha2.StatusSkip: make([]api.NamespaceCount, 0),
|
||||
}
|
||||
} else {
|
||||
list = map[string][]api.NamespaceCount{}
|
||||
|
||||
for _, status := range filter.Status {
|
||||
list[status] = make([]api.NamespaceCount, 0)
|
||||
}
|
||||
}
|
||||
|
||||
statusCounts := make([]api.NamespacedStatusCount, 0, 5)
|
||||
counts := make([]api.NamespaceCount, 0)
|
||||
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
TableExpr("policy_report_filter as f").
|
||||
ColumnExpr("SUM(f.count) as count, f.namespace, f.result as status").
|
||||
Where(`f.namespace != ''`).
|
||||
Group("f.namespace", "status").
|
||||
Order("f.namespace ASC")
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = f.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportFilterFilter(query, filter)
|
||||
|
||||
err := query.Scan(ctx, &counts)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to load namespaced status counts", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, count := range counts {
|
||||
list[count.Status] = append(list[count.Status], count)
|
||||
}
|
||||
|
||||
for status, items := range list {
|
||||
statusCounts = append(statusCounts, api.NamespacedStatusCount{
|
||||
Status: status,
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
|
||||
return statusCounts, nil
|
||||
}
|
||||
|
||||
func (s *Store) FetchRuleStatusCounts(ctx context.Context, policy, rule string) ([]api.StatusCount, error) {
|
||||
list := map[string]api.StatusCount{
|
||||
v1alpha2.StatusPass: {Status: v1alpha2.StatusPass},
|
||||
v1alpha2.StatusFail: {Status: v1alpha2.StatusFail},
|
||||
v1alpha2.StatusWarn: {Status: v1alpha2.StatusWarn},
|
||||
v1alpha2.StatusError: {Status: v1alpha2.StatusError},
|
||||
v1alpha2.StatusSkip: {Status: v1alpha2.StatusSkip},
|
||||
}
|
||||
|
||||
statusCounts := make([]api.StatusCount, 0, len(list))
|
||||
counts := make([]api.StatusCount, 0)
|
||||
|
||||
err := s.db.NewSelect().
|
||||
Table("policy_report_result").
|
||||
ColumnExpr("COUNT(id) as count, result as status").
|
||||
Where("rule = ?", rule).
|
||||
Where("policy = ?", policy).
|
||||
Group("status").
|
||||
Scan(ctx, &counts)
|
||||
if err != nil {
|
||||
return statusCounts, err
|
||||
}
|
||||
|
||||
for _, count := range counts {
|
||||
list[count.Status] = count
|
||||
}
|
||||
|
||||
for _, count := range list {
|
||||
statusCounts = append(statusCounts, count)
|
||||
}
|
||||
|
||||
return statusCounts, nil
|
||||
}
|
||||
|
||||
func (s *Store) FetchNamespacedResults(ctx context.Context, filter api.Filter, pagination api.Pagination) ([]*api.ListResult, error) {
|
||||
results := make([]*PolicyReportResult, 0)
|
||||
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
Model(&results).
|
||||
Where(`r.resource_namespace != ''`)
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = r.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportResultFilter(query, filter)
|
||||
addPagination(query, pagination)
|
||||
|
||||
err := query.Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return MapListResult(results), nil
|
||||
}
|
||||
|
||||
func (s *Store) CountNamespacedResults(ctx context.Context, filter api.Filter) (int, error) {
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
Model((*PolicyReportResult)(nil)).
|
||||
Where(`r.resource_namespace != ''`)
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = r.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportResultFilter(query, filter)
|
||||
|
||||
return query.Count(ctx)
|
||||
}
|
||||
|
||||
func (s *Store) Get(ctx context.Context, id string) (v1alpha2.ReportInterface, error) {
|
||||
polr := &PolicyReport{}
|
||||
|
||||
err := s.db.NewSelect().Model(polr).Where("id = ?", id).Scan(ctx)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, err
|
||||
} else if err != nil {
|
||||
zap.L().Error("failed to load policy report", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results, err := s.fetchResults(ctx, id)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to load policy report results", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1alpha2.PolicyReport{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: polr.Name,
|
||||
Namespace: polr.Namespace,
|
||||
CreationTimestamp: v1.NewTime(time.Unix(polr.Created, 0)),
|
||||
Labels: polr.Labels,
|
||||
},
|
||||
Summary: v1alpha2.PolicyReportSummary{
|
||||
Skip: polr.Skip,
|
||||
Pass: polr.Pass,
|
||||
Warn: polr.Warn,
|
||||
Fail: polr.Fail,
|
||||
Error: polr.Error,
|
||||
},
|
||||
Results: results,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) SQLDialect() dialect.Name {
|
||||
return s.db.Dialect().Name()
|
||||
}
|
||||
|
||||
func (s *Store) IsSQLite() bool {
|
||||
return s.db.Dialect().Name() == dialect.SQLite
|
||||
}
|
||||
|
||||
func (s *Store) fetchResults(ctx context.Context, id string) ([]v1alpha2.PolicyReportResult, error) {
|
||||
polr := []*PolicyReportResult{}
|
||||
|
||||
err := s.db.NewSelect().Model(&polr).Where("policy_report_id = ?", id).Scan(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to load policy report results", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := make([]v1alpha2.PolicyReportResult, 0, len(polr))
|
||||
for _, result := range polr {
|
||||
list = append(list, v1alpha2.PolicyReportResult{
|
||||
ID: result.ID,
|
||||
Result: v1alpha2.PolicyResult(result.Result),
|
||||
Severity: v1alpha2.PolicySeverity(result.Severity),
|
||||
Policy: result.Policy,
|
||||
Rule: result.Rule,
|
||||
Message: result.Message,
|
||||
Source: result.Source,
|
||||
Resources: []corev1.ObjectReference{
|
||||
{
|
||||
APIVersion: result.Resource.APIVersion,
|
||||
Kind: result.Resource.Kind,
|
||||
Namespace: result.Resource.Namespace,
|
||||
Name: result.Resource.Name,
|
||||
UID: types.UID(result.Resource.UID),
|
||||
},
|
||||
},
|
||||
Scored: result.Scored,
|
||||
Properties: result.Properties,
|
||||
Category: result.Category,
|
||||
Timestamp: v1.Timestamp{
|
||||
Seconds: result.Created,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *Store) fetchFilterOptions(ctx context.Context, option string, filter api.Filter, namespaced bool) ([]string, error) {
|
||||
list := make([]string, 0)
|
||||
|
||||
query := s.db.
|
||||
NewSelect().
|
||||
TableExpr("policy_report_filter as f").
|
||||
Column(option).
|
||||
Distinct().
|
||||
Order(option+" ASC").
|
||||
Where(`? != ''`, bun.Ident(option))
|
||||
|
||||
if namespaced {
|
||||
query.Where(`f.namespace != ''`)
|
||||
} else {
|
||||
query.Where(`f.namespace = ''`)
|
||||
}
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
query.Join("JOIN policy_report AS pr ON pr.id = f.policy_report_id")
|
||||
}
|
||||
|
||||
s.addFilter(query, filter)
|
||||
addPolicyReportFilterFilter(query, filter)
|
||||
|
||||
err := query.Scan(ctx, &list)
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (s *Store) Configure() {
|
||||
if s.db.Dialect().Name() == dialect.PG {
|
||||
s.jsonExtractLayout = "(pr.labels->>'%s') = ?"
|
||||
return
|
||||
}
|
||||
|
||||
s.jsonExtractLayout = "json_extract(pr.labels, '$.\"%s\"') = ?"
|
||||
}
|
||||
|
||||
func (s *Store) RequireSchemaUpgrade(ctx context.Context) bool {
|
||||
config := Config{}
|
||||
|
||||
err := s.db.NewSelect().Model(&config).Where("id = ?", 1).Scan(ctx)
|
||||
if err != nil {
|
||||
zap.L().Debug("failed to load config", zap.Error(err))
|
||||
return true
|
||||
}
|
||||
|
||||
return config.Version != s.version
|
||||
}
|
||||
|
||||
func (s *Store) PersistSchemaVersion(ctx context.Context) error {
|
||||
config := Config{
|
||||
Version: s.version,
|
||||
}
|
||||
|
||||
_, err := s.db.NewInsert().Model(&config).Exec(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to persist database version", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) PrepareDatabase(ctx context.Context) error {
|
||||
zap.L().Debug("preparing database")
|
||||
if s.RequireSchemaUpgrade(ctx) {
|
||||
zap.L().Debug("database schema upgrade started")
|
||||
if err := s.DropSchema(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.CreateSchemas(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.PersistSchemaVersion(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.CleanUp(ctx)
|
||||
}
|
||||
|
||||
func NewStore(db *bun.DB, version string) (*Store, error) {
|
||||
if db == nil {
|
||||
return nil, errors.New("missing database connection")
|
||||
}
|
||||
|
||||
s := &Store{
|
||||
db: db,
|
||||
version: version,
|
||||
}
|
||||
|
||||
s.Configure()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func NewSQLiteDB(dbFile string) (*bun.DB, error) {
|
||||
sqldb, err := createSQLiteDB(dbFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bun.NewDB(sqldb, sqlitedialect.New()), nil
|
||||
}
|
||||
|
||||
func createSQLiteDB(dbFile string) (*sql.DB, error) {
|
||||
os.Remove(dbFile)
|
||||
file, err := os.Create(dbFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file.Close()
|
||||
|
||||
db, err := sql.Open("sqlite3", dbFile+"?cache=shared")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func addPolicyReportFilterFilter(query *bun.SelectQuery, filter api.Filter) {
|
||||
if len(filter.Namespaces) > 0 {
|
||||
query.Where("f.namespace IN (?)", bun.In(filter.Namespaces))
|
||||
}
|
||||
if len(filter.Kinds) > 0 {
|
||||
query.Where("f.kind IN (?)", bun.In(filter.Kinds))
|
||||
}
|
||||
if len(filter.Sources) > 0 {
|
||||
query.Where("f.source IN (?)", bun.In(filter.Sources))
|
||||
}
|
||||
}
|
||||
|
||||
func addPolicyReportResultFilter(query *bun.SelectQuery, filter api.Filter) {
|
||||
if len(filter.Namespaces) > 0 {
|
||||
query.Where("r.resource_namespace IN (?)", bun.In(filter.Namespaces))
|
||||
}
|
||||
if len(filter.Rules) > 0 {
|
||||
query.Where("r.rule IN (?)", bun.In(filter.Rules))
|
||||
}
|
||||
if len(filter.Kinds) > 0 {
|
||||
query.Where("r.resource_kind IN (?)", bun.In(filter.Kinds))
|
||||
}
|
||||
if len(filter.Resources) > 0 {
|
||||
query.Where("r.resource_name IN (?)", bun.In(filter.Resources))
|
||||
}
|
||||
if len(filter.Sources) > 0 {
|
||||
query.Where("r.source IN (?)", bun.In(filter.Sources))
|
||||
}
|
||||
|
||||
if filter.Search != "" {
|
||||
query.Where(`(resource_namespace LIKE ?0 OR resource_name LIKE ?0 OR policy LIKE ?0 OR rule LIKE ?0 OR severity = ?1 OR result = ?1 OR LOWER(resource_kind) = LOWER(?1))`, "%"+filter.Search+"%", filter.Search)
|
||||
}
|
||||
}
|
||||
|
||||
func addPolicyReportFilter(query *bun.SelectQuery, filter api.Filter) {
|
||||
if len(filter.Namespaces) > 0 {
|
||||
query.Where("pr.namespace IN (?)", bun.In(filter.Namespaces))
|
||||
}
|
||||
if len(filter.Sources) > 0 {
|
||||
query.Where("pr.source IN (?)", bun.In(filter.Sources))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) addFilter(query *bun.SelectQuery, filter api.Filter) {
|
||||
if len(filter.Policies) > 0 {
|
||||
query.Where("policy IN (?)", bun.In(filter.Policies))
|
||||
}
|
||||
if len(filter.Categories) > 0 {
|
||||
query.Where("category IN (?)", bun.In(filter.Categories))
|
||||
}
|
||||
if len(filter.Severities) > 0 {
|
||||
query.Where("severity IN (?)", bun.In(filter.Severities))
|
||||
}
|
||||
if len(filter.Status) > 0 {
|
||||
query.Where("result IN (?)", bun.In(filter.Status))
|
||||
}
|
||||
|
||||
if len(filter.ReportLabel) > 0 {
|
||||
for key, value := range filter.ReportLabel {
|
||||
query.Where(fmt.Sprintf(s.jsonExtractLayout, key), value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addPagination(query *bun.SelectQuery, pagination api.Pagination) {
|
||||
query.OrderExpr(fmt.Sprintf(
|
||||
"%s %s",
|
||||
strings.Join(pagination.SortBy, ","),
|
||||
pagination.Direction,
|
||||
))
|
||||
|
||||
if pagination.Page == 0 || pagination.Offset == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
query.Limit(pagination.Offset)
|
||||
query.Offset((pagination.Page - 1) * pagination.Offset)
|
||||
}
|
||||
|
||||
func convertJSONToMap(s string) map[string]string {
|
||||
m := make(map[string]string)
|
||||
if s == "" {
|
||||
return m
|
||||
}
|
||||
|
||||
_ = json.Unmarshal([]byte(s), &m)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func contains(source string, sources []string) bool {
|
||||
for _, s := range sources {
|
||||
if strings.EqualFold(s, source) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func chunkSlice[K interface{}](slice []K, chunkSize int) [][]K {
|
||||
var chunks [][]K
|
||||
for i := 0; i < len(slice); i += chunkSize {
|
||||
end := i + chunkSize
|
||||
|
||||
if end > len(slice) {
|
||||
end = len(slice)
|
||||
}
|
||||
|
||||
chunks = append(chunks, slice[i:end])
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
||||
func logOnError(operation string, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Error("failed to execute db operatopn", zap.String("operatopm", operation), zap.Error(err))
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package sqlite3_test
|
||||
package database_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
@ -8,8 +10,8 @@ import (
|
|||
|
||||
v1 "github.com/kyverno/policy-reporter/pkg/api/v1"
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/database"
|
||||
"github.com/kyverno/policy-reporter/pkg/fixtures"
|
||||
"github.com/kyverno/policy-reporter/pkg/sqlite3"
|
||||
)
|
||||
|
||||
var pagination = v1.Pagination{Page: 1, Offset: 20, Direction: "ASC", SortBy: []string{"resource_name"}}
|
||||
|
@ -77,35 +79,43 @@ var scopeReport = &v1alpha2.PolicyReport{
|
|||
}
|
||||
|
||||
func Test_PolicyReportStore(t *testing.T) {
|
||||
db, _ := sqlite3.NewDatabase("test.db")
|
||||
db, err := database.NewSQLiteDB("test.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
store, _ := sqlite3.NewPolicyReportStore(db)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
store, _ := database.NewStore(db, "develop")
|
||||
store.PrepareDatabase(ctx)
|
||||
|
||||
t.Run("Add/Get/Update PolicyReport", func(t *testing.T) {
|
||||
_, ok := store.Get(preport.GetID())
|
||||
if ok == true {
|
||||
_, err := store.Get(ctx, preport.GetID())
|
||||
if err != sql.ErrNoRows {
|
||||
t.Fatalf("Should not be found in empty Store")
|
||||
}
|
||||
|
||||
err := store.Add(preport)
|
||||
err = store.Add(ctx, preport)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
r1, ok := store.Get(preport.GetID())
|
||||
if ok == false {
|
||||
t.Errorf("Should be found in Store after adding report to the store")
|
||||
}
|
||||
if r1.GetSummary().Pass != 0 {
|
||||
t.Errorf("Expected 0 Passed Results in GetSummary()")
|
||||
polr, err := store.Get(ctx, preport.GetID())
|
||||
if err != nil {
|
||||
t.Fatalf("Should found policy reporter after adding: %v", err)
|
||||
}
|
||||
|
||||
if r1.GetLabels()["app"] != "policy-reporter" {
|
||||
t.Errorf("Expected Labels are persisted")
|
||||
if len(polr.GetResults()) == 0 {
|
||||
t.Fatalf("Failed to load PolicyReportResults: %v", err)
|
||||
}
|
||||
|
||||
store.Update(ureport)
|
||||
r2, _ := store.Get(preport.GetID())
|
||||
err = store.Update(ctx, ureport)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update policy report: %v", err)
|
||||
}
|
||||
|
||||
r2, _ := store.Get(ctx, ureport.GetID())
|
||||
if r2.GetSummary().Pass != 1 {
|
||||
t.Errorf("Expected 1 Passed Results in GetSummary() after Update")
|
||||
}
|
||||
|
@ -114,31 +124,20 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
t.Errorf("Expected Labels are updated")
|
||||
}
|
||||
})
|
||||
t.Run("Add/Get ClusterPolicyReport", func(t *testing.T) {
|
||||
_, ok := store.Get(creport.GetID())
|
||||
if ok == true {
|
||||
t.Fatalf("Should not be found in empty Store")
|
||||
}
|
||||
|
||||
store.Add(creport)
|
||||
_, ok = store.Get(creport.GetID())
|
||||
if ok == false {
|
||||
t.Errorf("Should be found in Store after adding report to the store")
|
||||
}
|
||||
})
|
||||
t.Run("Add/Get PolicyReport with ScopeResource", func(t *testing.T) {
|
||||
_, ok := store.Get(scopeReport.GetID())
|
||||
if ok == true {
|
||||
_, err := store.Get(ctx, scopeReport.GetID())
|
||||
if err != sql.ErrNoRows {
|
||||
t.Fatalf("Should not be found in empty Store")
|
||||
}
|
||||
|
||||
err := store.Add(scopeReport)
|
||||
err = store.Add(ctx, scopeReport)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected add error: %s", err)
|
||||
}
|
||||
|
||||
rep, ok := store.Get(scopeReport.GetID())
|
||||
if ok == false {
|
||||
rep, err := store.Get(ctx, scopeReport.GetID())
|
||||
if err != nil {
|
||||
t.Error("Should be found in Store after adding report to the store")
|
||||
}
|
||||
if len(rep.GetResults()) == 0 {
|
||||
|
@ -149,56 +148,178 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
t.Error("Expected scope resource set as result resource")
|
||||
}
|
||||
|
||||
store.Remove(rep.GetID())
|
||||
store.Remove(ctx, rep.GetID())
|
||||
})
|
||||
|
||||
t.Run("FetchNamespacedKinds", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedKinds(v1.Filter{Sources: []string{"kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
t.Run("Add/Get ClusterPolicyReport", func(t *testing.T) {
|
||||
_, err := store.Get(ctx, creport.GetID())
|
||||
if err != sql.ErrNoRows {
|
||||
t.Fatalf("Should not be found in empty Store")
|
||||
}
|
||||
|
||||
err = store.Add(ctx, creport)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to persist ClusterPolicyReport: %v", err)
|
||||
}
|
||||
|
||||
_, err = store.Get(ctx, creport.GetID())
|
||||
if err != nil {
|
||||
t.Fatalf("Should be found in Store after adding report to the store")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchPolicyReports", func(t *testing.T) {
|
||||
items, err := store.FetchPolicyReports(ctx, v1.Filter{Namespaces: []string{"test"}, ReportLabel: map[string]string{"scope": "namespaced"}}, polrPagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("Should Find 2 Kinds with Namespace Scope")
|
||||
}
|
||||
if items[0] != "Deployment" {
|
||||
t.Errorf("Should return 'Deployment' as first result")
|
||||
}
|
||||
if items[1] != "Pod" {
|
||||
t.Errorf("Should return 'Pod' as second result")
|
||||
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("Should return one policy report, got %d", len(items))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchClusterKinds", func(t *testing.T) {
|
||||
items, err := store.FetchClusterKinds(v1.Filter{Sources: []string{"kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
t.Run("CountPolicyReports", func(t *testing.T) {
|
||||
count, err := store.CountPolicyReports(ctx, v1.Filter{Namespaces: []string{"test"}, ReportLabel: map[string]string{"scope": "namespaced"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if count != 1 {
|
||||
t.Fatalf("Should return one policy report, got %d", count)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NamespacedGetLabels()", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedReportLabels(ctx, v1.Filter{Sources: []string{"Kyverno"}, Namespaces: []string{"test"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 3 {
|
||||
t.Fatalf("Should return 3 GetLabels() results")
|
||||
}
|
||||
|
||||
if len(items["scope"]) != 1 && items["scope"][0] != "namespaced" {
|
||||
t.Fatalf("Should return cluster as scope value")
|
||||
}
|
||||
|
||||
if len(items["app"]) != 1 && items["app"][0] != "policy-reporter" {
|
||||
t.Fatalf("Should return policy-reporter as app value")
|
||||
}
|
||||
|
||||
if len(items["owner"]) != 1 && items["owner"][0] != "team-a" {
|
||||
t.Fatalf("Should return policy-reporter as app value")
|
||||
}
|
||||
})
|
||||
t.Run("FetchClusterReports", func(t *testing.T) {
|
||||
items, err := store.FetchClusterPolicyReports(ctx, v1.Filter{ReportLabel: map[string]string{"scope": "cluster"}}, polrPagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("Should return one policy report, got %d", len(items))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CountClusterReports", func(t *testing.T) {
|
||||
items, err := store.CountClusterPolicyReports(ctx, v1.Filter{ReportLabel: map[string]string{"scope": "cluster"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if items != 1 {
|
||||
t.Fatalf("Should return one policy report, got %d", items)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ClusterGetLabels()", func(t *testing.T) {
|
||||
items, err := store.FetchClusterReportLabels(ctx, v1.Filter{Sources: []string{"Kyverno"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("Should return 2 GetLabels() results")
|
||||
}
|
||||
|
||||
if len(items["scope"]) != 1 && items["scope"][0] != "cluster" {
|
||||
t.Fatalf("Should return cluster as scope value")
|
||||
}
|
||||
|
||||
if len(items["app"]) != 1 && items["app"][0] != "policy-reporter" {
|
||||
t.Fatalf("Should return policy-reporter as app value")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchClusterPolicies", func(t *testing.T) {
|
||||
items, err := store.FetchClusterPolicies(ctx, v1.Filter{Sources: []string{"Kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("Should find 1 kind with cluster scope")
|
||||
t.Fatalf("Should Find 1 cluster scoped policy, found %d", len(items))
|
||||
}
|
||||
if items[0] != "Namespace" {
|
||||
t.Errorf("Should return 'Namespace' as first result")
|
||||
if items[0] != "require-ns-GetLabels()" {
|
||||
t.Fatalf("Should return 'require-ns-GetLabels()' policy")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchClusterRules", func(t *testing.T) {
|
||||
items, err := store.FetchClusterRules(ctx, v1.Filter{Sources: []string{"Kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("Should Find 1 cluster scoped rule, found %d", len(items))
|
||||
}
|
||||
if items[0] != "check-for-GetLabels()-on-namespace" {
|
||||
t.Fatalf("Should return 'check-for-GetLabels()-on-namespace' rule")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespacedPolicies", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedPolicies(ctx, v1.Filter{Sources: []string{"Kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Errorf("Should find 1 namespace scoped policy")
|
||||
}
|
||||
if items[0] != "require-requests-and-limits-required" {
|
||||
t.Errorf("Should return 'require-requests-and-limits-required' policy")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespacedRules", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedRules(ctx, v1.Filter{Sources: []string{"Kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("Should find 1 namespace scoped policy, found %d", len(items))
|
||||
}
|
||||
if items[0] != "autogen-check-for-requests-and-limits" {
|
||||
t.Fatalf("Should return 'require-requests-and-limits-required' policy")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespacedResources", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedResources(v1.Filter{Sources: []string{"kyverno"}, Kinds: []string{"pod"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
items, err := store.FetchNamespacedResources(ctx, v1.Filter{Sources: []string{"Kyverno"}, Kinds: []string{"Pod"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("Should Find 2 Resources with Namespace Scope")
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("Should find 1 distinct resource with namespace Scope, got %d", len(items))
|
||||
}
|
||||
if items[0].Name != "nginx" {
|
||||
t.Errorf("Should return 'nginx' as first result, got %s", items[0].Name)
|
||||
}
|
||||
if items[1].Name != "nginx" {
|
||||
t.Errorf("Should return 'nginx' as second result get %s", items[1].Name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchClusterResources", func(t *testing.T) {
|
||||
items, err := store.FetchClusterResources(v1.Filter{Sources: []string{"kyverno"}, Kinds: []string{"namespace"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
items, err := store.FetchClusterResources(ctx, v1.Filter{Sources: []string{"Kyverno"}, Kinds: []string{"Namespace"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -213,8 +334,47 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("FetchClusterSources", func(t *testing.T) {
|
||||
items, err := store.FetchClusterSources(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Errorf("Should find 1 Source")
|
||||
}
|
||||
if items[0] != "Kyverno" {
|
||||
t.Errorf("Should return Kyverno")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespacedSources", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedSources(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Errorf("Should find 1 Source")
|
||||
}
|
||||
if items[0] != "Kyverno" {
|
||||
t.Errorf("Should return Kyverno")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespaces", func(t *testing.T) {
|
||||
items, err := store.FetchNamespaces(ctx, v1.Filter{Sources: []string{"Kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatal("Should find 1 Namespace")
|
||||
}
|
||||
if items[0] != "test" {
|
||||
t.Errorf("Should return test namespace")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespacedStatusCounts", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedStatusCounts(v1.Filter{ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
items, err := store.FetchNamespacedStatusCounts(ctx, v1.Filter{ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -249,7 +409,7 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("FetchNamespacedStatusCounts with StatusFilter", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedStatusCounts(v1.Filter{Status: []string{v1alpha2.StatusPass}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
items, err := store.FetchNamespacedStatusCounts(ctx, v1.Filter{Status: []string{v1alpha2.StatusPass}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -264,33 +424,8 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("FetchRuleStatusCounts", func(t *testing.T) {
|
||||
items, err := store.FetchRuleStatusCounts("require-requests-and-limits-required", "autogen-check-for-requests-and-limits")
|
||||
var passed v1.StatusCount
|
||||
var failed v1.StatusCount
|
||||
for _, item := range items {
|
||||
if item.Status == v1alpha2.StatusPass {
|
||||
passed = item
|
||||
}
|
||||
if item.Status == v1alpha2.StatusFail {
|
||||
failed = item
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if passed.Count != 1 {
|
||||
t.Errorf("Expected count to be one for pass")
|
||||
}
|
||||
|
||||
if failed.Count != 1 {
|
||||
t.Errorf("Expected count to be one for fail")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchStatusCounts", func(t *testing.T) {
|
||||
items, err := store.FetchStatusCounts(v1.Filter{ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
t.Run("FetchClusterStatusCounts", func(t *testing.T) {
|
||||
items, err := store.FetchClusterStatusCounts(ctx, v1.Filter{ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -315,8 +450,8 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("FetchStatusCounts with StatusFilter", func(t *testing.T) {
|
||||
items, err := store.FetchStatusCounts(v1.Filter{Status: []string{v1alpha2.StatusPass}})
|
||||
t.Run("FetchClusterStatusCounts with StatusFilter", func(t *testing.T) {
|
||||
items, err := store.FetchClusterStatusCounts(ctx, v1.Filter{Status: []string{v1alpha2.StatusPass}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -331,8 +466,33 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("FetchRuleStatusCounts", func(t *testing.T) {
|
||||
items, err := store.FetchRuleStatusCounts(ctx, "require-requests-and-limits-required", "autogen-check-for-requests-and-limits")
|
||||
var passed v1.StatusCount
|
||||
var failed v1.StatusCount
|
||||
for _, item := range items {
|
||||
if item.Status == v1alpha2.StatusPass {
|
||||
passed = item
|
||||
}
|
||||
if item.Status == v1alpha2.StatusFail {
|
||||
failed = item
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if passed.Count != 1 {
|
||||
t.Errorf("Expected count to be one for pass")
|
||||
}
|
||||
|
||||
if failed.Count != 1 {
|
||||
t.Errorf("Expected count to be one for fail")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespacedResults", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedResults(v1.Filter{Namespaces: []string{"test"}}, pagination)
|
||||
items, err := store.FetchNamespacedResults(ctx, v1.Filter{Namespaces: []string{"test"}}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -343,7 +503,7 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("FetchNamespacedResults with SeverityFilter", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedResults(v1.Filter{Severities: []string{v1alpha2.SeverityHigh}}, pagination)
|
||||
items, err := store.FetchNamespacedResults(ctx, v1.Filter{Severities: []string{v1alpha2.SeverityHigh}}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -357,7 +517,7 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("CountNamespacedResults", func(t *testing.T) {
|
||||
count, err := store.CountNamespacedResults(v1.Filter{ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
count, err := store.CountNamespacedResults(ctx, v1.Filter{ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -368,7 +528,7 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("CountNamespacedResults with SeverityFilter", func(t *testing.T) {
|
||||
count, err := store.CountNamespacedResults(v1.Filter{Severities: []string{v1alpha2.SeverityHigh}})
|
||||
count, err := store.CountNamespacedResults(ctx, v1.Filter{Severities: []string{v1alpha2.SeverityHigh}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -379,7 +539,7 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("FetchNamespacedResults with SearchFilter::Severity", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedResults(v1.Filter{Search: v1alpha2.SeverityHigh}, pagination)
|
||||
items, err := store.FetchNamespacedResults(ctx, v1.Filter{Search: v1alpha2.SeverityHigh}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -393,7 +553,7 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("FetchNamespacedResults with SearchFilter::Kind", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedResults(v1.Filter{Search: "deployment"}, pagination)
|
||||
items, err := store.FetchNamespacedResults(ctx, v1.Filter{Search: "deployment"}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -407,7 +567,7 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("FetchClusterResults", func(t *testing.T) {
|
||||
items, err := store.FetchClusterResults(v1.Filter{Status: []string{v1alpha2.StatusPass, v1alpha2.StatusFail}, ReportLabel: map[string]string{"app": "policy-reporter"}}, pagination)
|
||||
items, err := store.FetchClusterResults(ctx, v1.Filter{Status: []string{v1alpha2.StatusPass, v1alpha2.StatusFail}, ReportLabel: map[string]string{"app": "policy-reporter"}}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -418,7 +578,7 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("CountClusterResults", func(t *testing.T) {
|
||||
count, err := store.CountClusterResults(v1.Filter{Status: []string{v1alpha2.StatusPass, v1alpha2.StatusFail}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
count, err := store.CountClusterResults(ctx, v1.Filter{Status: []string{v1alpha2.StatusPass, v1alpha2.StatusFail}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -429,7 +589,7 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("FetchClusterResults with SeverityFilter", func(t *testing.T) {
|
||||
items, err := store.FetchClusterResults(v1.Filter{Severities: []string{v1alpha2.SeverityHigh}}, pagination)
|
||||
items, err := store.FetchClusterResults(ctx, v1.Filter{Severities: []string{v1alpha2.SeverityHigh}}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -443,7 +603,7 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("FetchClusterResults with SearchFilter", func(t *testing.T) {
|
||||
items, err := store.FetchClusterResults(v1.Filter{Search: v1alpha2.SeverityHigh}, pagination)
|
||||
items, err := store.FetchClusterResults(ctx, v1.Filter{Search: v1alpha2.SeverityHigh}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
@ -456,294 +616,63 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("FetchStatusCounts with StatusFilter", func(t *testing.T) {
|
||||
items, err := store.FetchStatusCounts(v1.Filter{Status: []string{v1alpha2.StatusPass}})
|
||||
t.Run("FetchNamespacedKinds", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedKinds(ctx, v1.Filter{Sources: []string{"Kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("Should Find 2 Kinds with Namespace Scope")
|
||||
}
|
||||
if items[0] != "Deployment" {
|
||||
t.Errorf("Should return 'Deployment' as first result")
|
||||
}
|
||||
if items[1] != "Pod" {
|
||||
t.Errorf("Should return 'Pod' as second result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchClusterKinds", func(t *testing.T) {
|
||||
items, err := store.FetchClusterKinds(ctx, v1.Filter{Sources: []string{"Kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("Should have only 1 item for pass counts")
|
||||
t.Fatalf("Should find 1 kind with cluster scope")
|
||||
}
|
||||
if items[0].Status != v1alpha2.StatusPass {
|
||||
t.Errorf("Expected Pass Counts")
|
||||
}
|
||||
if items[0].Count != 1 {
|
||||
t.Errorf("Expected count to be one for pass")
|
||||
if items[0] != "Namespace" {
|
||||
t.Errorf("Should return 'Namespace' as first result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespaces", func(t *testing.T) {
|
||||
items, err := store.FetchNamespaces(v1.Filter{Sources: []string{"kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
t.Run("FetchNamespacedCategories", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedCategories(ctx, v1.Filter{Sources: []string{"Kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Errorf("Should find 1 Namespace")
|
||||
}
|
||||
if items[0] != "test" {
|
||||
t.Errorf("Should return test namespace")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchCategories", func(t *testing.T) {
|
||||
items, err := store.FetchCategories(v1.Filter{Sources: []string{"kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 3 {
|
||||
t.Errorf("Should Find 2 Categories")
|
||||
if len(items) != 2 {
|
||||
t.Errorf("Should find 2 categories, got %d", len(items))
|
||||
}
|
||||
if items[0] != "Best Practices" {
|
||||
t.Errorf("Should return 'Best Practices' as first category")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchClusterPolicies", func(t *testing.T) {
|
||||
items, err := store.FetchClusterPolicies(v1.Filter{Sources: []string{"kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
t.Run("FetchClusterCategories", func(t *testing.T) {
|
||||
items, err := store.FetchClusterCategories(ctx, v1.Filter{Sources: []string{"Kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Errorf("Should Find 1 cluster scoped Policy")
|
||||
t.Errorf("Should find 1 category, got %d", len(items))
|
||||
}
|
||||
if items[0] != "require-ns-GetLabels()" {
|
||||
t.Errorf("Should return 'require-ns-GetLabels()' policy")
|
||||
if items[0] != "namespaces" {
|
||||
t.Errorf("Should return 'Best Practices' as first category, get '%s'", items[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchClusterRules", func(t *testing.T) {
|
||||
items, err := store.FetchClusterRules(v1.Filter{Sources: []string{"kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Errorf("Should Find 1 cluster scoped Policy")
|
||||
}
|
||||
if items[0] != "check-for-GetLabels()-on-namespace" {
|
||||
t.Errorf("Should return 'check-for-GetLabels()-on-namespace' rule")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespacedPolicies", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedPolicies(v1.Filter{Sources: []string{"kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Errorf("Should find 1 namespace scoped policy")
|
||||
}
|
||||
if items[0] != "require-requests-and-limits-required" {
|
||||
t.Errorf("Should return 'require-requests-and-limits-required' policy")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespacedRules", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedRules(v1.Filter{Sources: []string{"kyverno"}, ReportLabel: map[string]string{"app": "policy-reporter"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Errorf("Should find 1 namespace scoped policy")
|
||||
}
|
||||
if items[0] != "autogen-check-for-requests-and-limits" {
|
||||
t.Errorf("Should return 'require-requests-and-limits-required' policy")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchClusterSources", func(t *testing.T) {
|
||||
items, err := store.FetchClusterSources()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Errorf("Should find 1 Source")
|
||||
}
|
||||
if items[0] != "Kyverno" {
|
||||
t.Errorf("Should return Kyverno")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchNamespacedSources", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedSources()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Errorf("Should find 1 Source")
|
||||
}
|
||||
if items[0] != "Kyverno" {
|
||||
t.Errorf("Should return Kyverno")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NamespacedResults: ReportLabel Filter", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedResults(v1.Filter{ReportLabel: map[string]string{"app": "policy-reporter"}}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("Should return 2 namespaced results")
|
||||
}
|
||||
|
||||
items, err = store.FetchNamespacedResults(v1.Filter{ReportLabel: map[string]string{"app": "not-exist"}}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 0 {
|
||||
t.Fatalf("Should return 0 namespaced results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ClusterResults: ReportLabel Filter", func(t *testing.T) {
|
||||
items, err := store.FetchClusterResults(v1.Filter{ReportLabel: map[string]string{"app": "policy-reporter"}}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("Should return 2 namespaced results")
|
||||
}
|
||||
|
||||
items, err = store.FetchClusterResults(v1.Filter{ReportLabel: map[string]string{"app": "not-exist"}}, pagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 0 {
|
||||
t.Fatalf("Should return 0 namespaced results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NamespacedGetLabels()", func(t *testing.T) {
|
||||
items, err := store.FetchNamespacedReportLabels(v1.Filter{Sources: []string{"Kyverno"}, Namespaces: []string{"test"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 3 {
|
||||
t.Fatalf("Should return 3 GetLabels() results")
|
||||
}
|
||||
|
||||
if len(items["scope"]) != 1 && items["scope"][0] != "namespaced" {
|
||||
t.Fatalf("Should return cluster as scope value")
|
||||
}
|
||||
|
||||
if len(items["app"]) != 1 && items["app"][0] != "policy-reporter" {
|
||||
t.Fatalf("Should return policy-reporter as app value")
|
||||
}
|
||||
|
||||
if len(items["owner"]) != 1 && items["owner"][0] != "team-a" {
|
||||
t.Fatalf("Should return policy-reporter as app value")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CountPolicyReports", func(t *testing.T) {
|
||||
items, err := store.CountPolicyReports(v1.Filter{Namespaces: []string{"test"}, ReportLabel: map[string]string{"scope": "namespaced"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if items != 1 {
|
||||
t.Fatalf("Should return one policy report, got %d", items)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchPolicyReports", func(t *testing.T) {
|
||||
items, err := store.FetchPolicyReports(v1.Filter{Namespaces: []string{"test"}, ReportLabel: map[string]string{"scope": "namespaced"}}, polrPagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("Should return one policy report, got %d", len(items))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CountClusterReports", func(t *testing.T) {
|
||||
items, err := store.CountClusterPolicyReports(v1.Filter{ReportLabel: map[string]string{"scope": "cluster"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if items != 1 {
|
||||
t.Fatalf("Should return one policy report, got %d", items)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FetchClusterReports", func(t *testing.T) {
|
||||
items, err := store.FetchClusterPolicyReports(v1.Filter{ReportLabel: map[string]string{"scope": "cluster"}}, polrPagination)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("Should return one policy report, got %d", len(items))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ClusterGetLabels()", func(t *testing.T) {
|
||||
items, err := store.FetchClusterReportLabels(v1.Filter{Sources: []string{"Kyverno"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("Should return 2 GetLabels() results")
|
||||
}
|
||||
|
||||
if len(items["scope"]) != 1 && items["scope"][0] != "cluster" {
|
||||
t.Fatalf("Should return cluster as scope value")
|
||||
}
|
||||
|
||||
if len(items["app"]) != 1 && items["app"][0] != "policy-reporter" {
|
||||
t.Fatalf("Should return policy-reporter as app value")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Delete/Get", func(t *testing.T) {
|
||||
_, ok := store.Get(preport.GetID())
|
||||
if ok == false {
|
||||
t.Errorf("Should be found in Store after adding report to the store")
|
||||
}
|
||||
|
||||
store.Remove(preport.GetID())
|
||||
_, ok = store.Get(preport.GetID())
|
||||
if ok == true {
|
||||
t.Fatalf("Should not be found after Remove report from Store")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CleanUp", func(t *testing.T) {
|
||||
store.Add(preport)
|
||||
|
||||
store.CleanUp()
|
||||
list, _ := store.FetchNamespacedResults(v1.Filter{}, v1.Pagination{Page: 0})
|
||||
if len(list) == 1 {
|
||||
t.Fatalf("Should have no results after CleanUp")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Insert duplicates", func(t *testing.T) {
|
||||
err := store.Add(dreport)
|
||||
if err != nil {
|
||||
t.Errorf("Should ignore duplicated ID: %s", err)
|
||||
}
|
||||
|
||||
polr, ok := store.Get(preport.GetID())
|
||||
if ok == false {
|
||||
t.Errorf("Should be found in Store after adding report to the store")
|
||||
}
|
||||
|
||||
if len(polr.GetResults()) != 2 {
|
||||
t.Errorf("Should ignore duplicated result")
|
||||
}
|
||||
|
||||
store.Remove(dreport.GetID())
|
||||
_, ok = store.Get(dreport.GetID())
|
||||
if ok == true {
|
||||
t.Fatalf("Should not be found after Remove report from Store")
|
||||
}
|
||||
})
|
||||
err = store.CleanUp(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to cleanup policy reports: %v", err)
|
||||
}
|
||||
}
|
206
pkg/database/model.go
Normal file
206
pkg/database/model.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
bun.BaseModel `bun:"table:policy_report_config,alias:c"`
|
||||
|
||||
ID int `bun:"id,pk,autoincrement" json:"id"`
|
||||
Version string
|
||||
}
|
||||
|
||||
type PolicyReport struct {
|
||||
bun.BaseModel `bun:"table:policy_report,alias:pr" json:"-"`
|
||||
|
||||
ID string `bun:",pk" json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Source string `json:"source"`
|
||||
Labels map[string]string `bun:",type:json" json:"labels"`
|
||||
Skip int `json:"skip"`
|
||||
Pass int `json:"pass"`
|
||||
Warn int `json:"warn"`
|
||||
Fail int `json:"fail"`
|
||||
Error int `json:"error"`
|
||||
Created int64 `json:"created"`
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
APIVersion string `bun:"api_version"`
|
||||
Kind string
|
||||
Name string
|
||||
Namespace string
|
||||
UID string
|
||||
}
|
||||
|
||||
type PolicyReportResult struct {
|
||||
bun.BaseModel `bun:"table:policy_report_result,alias:r" json:"-"`
|
||||
|
||||
ID string `bun:",pk" json:"id"`
|
||||
PolicyReportID string `bund:"policy_report_id" json:"-"`
|
||||
Resource Resource `bun:"embed:resource_"`
|
||||
Policy string
|
||||
Rule string
|
||||
Message string
|
||||
Scored bool
|
||||
Result string
|
||||
Severity string
|
||||
Category string
|
||||
Source string
|
||||
Properties map[string]string `bun:",type:json"`
|
||||
Created int64
|
||||
}
|
||||
|
||||
type PolicyReportFilter struct {
|
||||
bun.BaseModel `bun:"table:policy_report_filter,alias:f"`
|
||||
|
||||
PolicyReportID string `bund:"policy_report_id"`
|
||||
Namespace string
|
||||
Policy string
|
||||
Kind string
|
||||
Result string
|
||||
Severity string
|
||||
Category string
|
||||
Source string
|
||||
Count int
|
||||
}
|
||||
|
||||
func (r *PolicyReportFilter) Hash() string {
|
||||
h1 := fnv1a.Init64
|
||||
h1 = fnv1a.AddString64(h1, r.PolicyReportID)
|
||||
h1 = fnv1a.AddString64(h1, r.Namespace)
|
||||
h1 = fnv1a.AddString64(h1, r.Source)
|
||||
h1 = fnv1a.AddString64(h1, r.Kind)
|
||||
h1 = fnv1a.AddString64(h1, r.Category)
|
||||
h1 = fnv1a.AddString64(h1, r.Policy)
|
||||
h1 = fnv1a.AddString64(h1, r.Severity)
|
||||
h1 = fnv1a.AddString64(h1, r.Result)
|
||||
|
||||
return strconv.FormatUint(h1, 10)
|
||||
}
|
||||
|
||||
func MapPolicyReport(r v1alpha2.ReportInterface) *PolicyReport {
|
||||
return &PolicyReport{
|
||||
ID: r.GetID(),
|
||||
Type: report.GetType(r),
|
||||
Name: r.GetName(),
|
||||
Namespace: r.GetNamespace(),
|
||||
Source: r.GetSource(),
|
||||
Labels: r.GetLabels(),
|
||||
Skip: r.GetSummary().Skip,
|
||||
Pass: r.GetSummary().Pass,
|
||||
Warn: r.GetSummary().Warn,
|
||||
Fail: r.GetSummary().Fail,
|
||||
Error: r.GetSummary().Error,
|
||||
Created: r.GetCreationTimestamp().Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
ns := res.Namespace
|
||||
if ns == "" {
|
||||
ns = polr.GetNamespace()
|
||||
}
|
||||
|
||||
list = append(list, &PolicyReportResult{
|
||||
ID: result.GetID(),
|
||||
PolicyReportID: polr.GetID(),
|
||||
Resource: Resource{
|
||||
APIVersion: res.APIVersion,
|
||||
Kind: res.Kind,
|
||||
Name: res.Name,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func MapPolicyReportFilter(polr v1alpha2.ReportInterface) []*PolicyReportFilter {
|
||||
mapping := make(map[string]*PolicyReportFilter)
|
||||
for _, res := range polr.GetResults() {
|
||||
kind := res.GetKind()
|
||||
if kind == "" && polr.GetScope() != nil {
|
||||
kind = polr.GetScope().Namespace
|
||||
}
|
||||
|
||||
value := &PolicyReportFilter{
|
||||
PolicyReportID: polr.GetID(),
|
||||
Namespace: polr.GetNamespace(),
|
||||
Source: res.Source,
|
||||
Kind: kind,
|
||||
Category: res.Category,
|
||||
Policy: res.Policy,
|
||||
Severity: string(res.Severity),
|
||||
Result: string(res.Result),
|
||||
Count: 1,
|
||||
}
|
||||
|
||||
if item, ok := mapping[value.Hash()]; ok {
|
||||
item.Count = item.Count + 1
|
||||
} else {
|
||||
mapping[value.Hash()] = value
|
||||
}
|
||||
}
|
||||
list := make([]*PolicyReportFilter, 0, len(mapping))
|
||||
for _, v := range mapping {
|
||||
list = append(list, v)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func MapListResult(results []*PolicyReportResult) []*api.ListResult {
|
||||
list := make([]*api.ListResult, 0, len(results))
|
||||
for _, res := range results {
|
||||
list = append(list, &api.ListResult{
|
||||
ID: res.ID,
|
||||
Namespace: res.Resource.Namespace,
|
||||
Kind: res.Resource.Kind,
|
||||
APIVersion: res.Resource.APIVersion,
|
||||
Name: res.Resource.Name,
|
||||
Message: res.Message,
|
||||
Category: res.Category,
|
||||
Policy: res.Policy,
|
||||
Rule: res.Rule,
|
||||
Status: res.Result,
|
||||
Severity: res.Severity,
|
||||
Timestamp: res.Created,
|
||||
Properties: res.Properties,
|
||||
})
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"go.uber.org/zap"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/metadata"
|
||||
"k8s.io/client-go/metadata/metadatainformer"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
@ -16,31 +15,40 @@ import (
|
|||
"github.com/kyverno/policy-reporter/pkg/report"
|
||||
)
|
||||
|
||||
var (
|
||||
polrResource = pr.SchemeGroupVersion.WithResource("policyreports")
|
||||
cpolrResource = pr.SchemeGroupVersion.WithResource("clusterpolicyreports")
|
||||
)
|
||||
|
||||
type k8sPolicyReportClient struct {
|
||||
queue *Queue
|
||||
fatcory metadatainformer.SharedInformerFactory
|
||||
polr informers.GenericInformer
|
||||
cpolr informers.GenericInformer
|
||||
metaClient metadata.Interface
|
||||
synced bool
|
||||
mx *sync.Mutex
|
||||
reportFilter *report.Filter
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
func (k *k8sPolicyReportClient) HasSynced() bool {
|
||||
return k.synced
|
||||
}
|
||||
|
||||
func (k *k8sPolicyReportClient) Stop() {
|
||||
close(k.stopChan)
|
||||
}
|
||||
|
||||
func (k *k8sPolicyReportClient) Sync(stopper chan struct{}) error {
|
||||
factory := metadatainformer.NewSharedInformerFactory(k.metaClient, 15*time.Minute)
|
||||
|
||||
var cpolrInformer cache.SharedIndexInformer
|
||||
|
||||
polrInformer := k.configureInformer(k.polr.Informer())
|
||||
polrInformer := k.configureInformer(factory.ForResource(polrResource).Informer())
|
||||
|
||||
if !k.reportFilter.DisableClusterReports() {
|
||||
cpolrInformer = k.configureInformer(k.cpolr.Informer())
|
||||
cpolrInformer = k.configureInformer(factory.ForResource(cpolrResource).Informer())
|
||||
}
|
||||
|
||||
k.fatcory.Start(stopper)
|
||||
factory.Start(stopper)
|
||||
|
||||
if !cache.WaitForCacheSync(stopper, polrInformer.HasSynced) {
|
||||
return fmt.Errorf("failed to sync policy reports")
|
||||
|
@ -58,6 +66,8 @@ func (k *k8sPolicyReportClient) Sync(stopper chan struct{}) error {
|
|||
}
|
||||
|
||||
func (k *k8sPolicyReportClient) Run(worker int, stopper chan struct{}) error {
|
||||
k.stopChan = stopper
|
||||
|
||||
if err := k.Sync(stopper); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -101,14 +111,8 @@ func (k *k8sPolicyReportClient) configureInformer(informer cache.SharedIndexInfo
|
|||
|
||||
// NewPolicyReportClient new Client for Policy Report Kubernetes API
|
||||
func NewPolicyReportClient(metaClient metadata.Interface, reportFilter *report.Filter, queue *Queue) report.PolicyReportClient {
|
||||
fatcory := metadatainformer.NewSharedInformerFactory(metaClient, 15*time.Minute)
|
||||
polr := fatcory.ForResource(pr.SchemeGroupVersion.WithResource("policyreports"))
|
||||
cpolr := fatcory.ForResource(pr.SchemeGroupVersion.WithResource("clusterpolicyreports"))
|
||||
|
||||
return &k8sPolicyReportClient{
|
||||
fatcory: fatcory,
|
||||
polr: polr,
|
||||
cpolr: cpolr,
|
||||
metaClient: metaClient,
|
||||
mx: &sync.Mutex{},
|
||||
queue: queue,
|
||||
reportFilter: reportFilter,
|
||||
|
|
|
@ -41,8 +41,6 @@ func (q *Queue) Add(obj *v1.PartialObjectMetadata) error {
|
|||
func (q *Queue) Run(workers int, stopCh chan struct{}) {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
defer q.queue.ShutDown()
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(q.runWorker, time.Second, stopCh)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ type Values struct {
|
|||
KmsKeyID string `json:"kmsKeyId,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Credentials string `json:"credentials,omitempty"`
|
||||
Database string `json:"database,omitempty"`
|
||||
DSN string `json:"dsn,omitempty"`
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
|
@ -56,6 +58,14 @@ func (c *k8sClient) Get(ctx context.Context, name string) (Values, error) {
|
|||
values.Password = string(password)
|
||||
}
|
||||
|
||||
if database, ok := secret.Data["database"]; ok {
|
||||
values.Database = string(database)
|
||||
}
|
||||
|
||||
if dsn, ok := secret.Data["dsn"]; ok {
|
||||
values.DSN = string(dsn)
|
||||
}
|
||||
|
||||
if accessKeyID, ok := secret.Data["accessKeyID"]; ok {
|
||||
values.AccessKeyID = string(accessKeyID)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ func newFakeClient() v1.SecretInterface {
|
|||
"kmsKeyId": []byte("kmsKeyId"),
|
||||
"token": []byte("token"),
|
||||
"accountID": []byte("accountID"),
|
||||
"database": []byte("database"),
|
||||
"dsn": []byte("dsn"),
|
||||
},
|
||||
}).CoreV1().Secrets("default")
|
||||
}
|
||||
|
@ -79,6 +81,14 @@ func Test_Client(t *testing.T) {
|
|||
if values.AccountID != "accountID" {
|
||||
t.Errorf("Unexpected AccountID: %s", values.AccountID)
|
||||
}
|
||||
|
||||
if values.Database != "database" {
|
||||
t.Errorf("Unexpected Database: %s", values.Database)
|
||||
}
|
||||
|
||||
if values.DSN != "dsn" {
|
||||
t.Errorf("Unexpected DSN: %s", values.DSN)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Get values from not existing secret", func(t *testing.T) {
|
||||
|
|
|
@ -23,13 +23,24 @@ func (l *ResultListener) RegisterListener(listener report.PolicyReportResultList
|
|||
l.listener = append(l.listener, listener)
|
||||
}
|
||||
|
||||
func (l *ResultListener) UnregisterListener() {
|
||||
l.listener = make([]report.PolicyReportResultListener, 0)
|
||||
}
|
||||
|
||||
func (l *ResultListener) Listen(event report.LifecycleEvent) {
|
||||
if event.Type != report.Added && event.Type != report.Updated {
|
||||
l.cache.RemoveReport(event.PolicyReport.GetID())
|
||||
return
|
||||
}
|
||||
|
||||
if len(event.PolicyReport.GetResults()) == 0 {
|
||||
resultCount := len(event.PolicyReport.GetResults())
|
||||
if resultCount == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
listenerCount := len(l.listener)
|
||||
if listenerCount == 0 {
|
||||
l.cache.AddReport(event.PolicyReport)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -44,28 +55,42 @@ func (l *ResultListener) Listen(event report.LifecycleEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
existing := l.cache.GetResults(event.PolicyReport.GetID())
|
||||
|
||||
for _, r := range event.PolicyReport.GetResults() {
|
||||
if helper.Contains(r.GetID(), existing) {
|
||||
continue
|
||||
}
|
||||
grp := sync.WaitGroup{}
|
||||
grp.Add(resultCount)
|
||||
for _, res := range event.PolicyReport.GetResults() {
|
||||
go func(r v1alpha2.PolicyReportResult) {
|
||||
defer grp.Done()
|
||||
|
||||
wg.Add(len(l.listener))
|
||||
if helper.Contains(r.GetID(), existing) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, cb := range l.listener {
|
||||
go func(callback report.PolicyReportResultListener, result v1alpha2.PolicyReportResult) {
|
||||
callback(event.PolicyReport, result, preExisted)
|
||||
wg.Done()
|
||||
}(cb, r)
|
||||
}
|
||||
if r.Timestamp.Seconds > 0 {
|
||||
created := time.Unix(r.Timestamp.Seconds, int64(r.Timestamp.Nanos))
|
||||
if l.skipExisting && created.Local().Before(l.startUp) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(listenerCount)
|
||||
|
||||
for _, cb := range l.listener {
|
||||
go func(callback report.PolicyReportResultListener, result v1alpha2.PolicyReportResult) {
|
||||
callback(event.PolicyReport, result, preExisted)
|
||||
wg.Done()
|
||||
}(cb, r)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}(res)
|
||||
}
|
||||
|
||||
l.cache.AddReport(event.PolicyReport)
|
||||
grp.Wait()
|
||||
|
||||
wg.Wait()
|
||||
l.cache.AddReport(event.PolicyReport)
|
||||
}
|
||||
|
||||
func NewResultListener(skipExisting bool, rcache cache.Cache, startUp time.Time) *ResultListener {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package listener
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/report"
|
||||
|
@ -8,19 +10,19 @@ import (
|
|||
|
||||
const Store = "store_listener"
|
||||
|
||||
func NewStoreListener(store report.PolicyReportStore) report.PolicyReportListener {
|
||||
func NewStoreListener(ctx context.Context, store report.PolicyReportStore) report.PolicyReportListener {
|
||||
return func(event report.LifecycleEvent) {
|
||||
if event.Type == report.Deleted {
|
||||
logOnError("remove", event.PolicyReport.GetName(), store.Remove(event.PolicyReport.GetID()))
|
||||
logOnError("remove", event.PolicyReport.GetName(), store.Remove(ctx, event.PolicyReport.GetID()))
|
||||
return
|
||||
}
|
||||
|
||||
if event.Type == report.Updated {
|
||||
logOnError("update", event.PolicyReport.GetName(), store.Update(event.PolicyReport))
|
||||
logOnError("update", event.PolicyReport.GetName(), store.Update(ctx, event.PolicyReport))
|
||||
return
|
||||
}
|
||||
|
||||
logOnError("add", event.PolicyReport.GetName(), store.Add(event.PolicyReport))
|
||||
logOnError("add", event.PolicyReport.GetName(), store.Update(ctx, event.PolicyReport))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,36 +1,39 @@
|
|||
package listener_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/listener"
|
||||
"github.com/kyverno/policy-reporter/pkg/report"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func Test_StoreListener(t *testing.T) {
|
||||
store := report.NewPolicyReportStore()
|
||||
|
||||
t.Run("Save New Report", func(t *testing.T) {
|
||||
slistener := listener.NewStoreListener(store)
|
||||
slistener := listener.NewStoreListener(ctx, store)
|
||||
slistener(report.LifecycleEvent{Type: report.Added, PolicyReport: preport1})
|
||||
|
||||
if _, ok := store.Get(preport1.GetID()); !ok {
|
||||
if _, err := store.Get(ctx, preport1.GetID()); err != nil {
|
||||
t.Error("Expected Report to be stored")
|
||||
}
|
||||
})
|
||||
t.Run("Update Modified Report", func(t *testing.T) {
|
||||
slistener := listener.NewStoreListener(store)
|
||||
slistener := listener.NewStoreListener(ctx, store)
|
||||
slistener(report.LifecycleEvent{Type: report.Updated, PolicyReport: preport2})
|
||||
|
||||
if preport, ok := store.Get(preport2.GetID()); !ok && len(preport.GetResults()) == 2 {
|
||||
if preport, err := store.Get(ctx, preport2.GetID()); err != nil && len(preport.GetResults()) == 2 {
|
||||
t.Error("Expected Report to be updated")
|
||||
}
|
||||
})
|
||||
t.Run("Remove Deleted Report", func(t *testing.T) {
|
||||
slistener := listener.NewStoreListener(store)
|
||||
slistener := listener.NewStoreListener(ctx, store)
|
||||
slistener(report.LifecycleEvent{Type: report.Deleted, PolicyReport: preport2})
|
||||
|
||||
if _, ok := store.Get(preport2.GetID()); ok {
|
||||
if _, err := store.Get(ctx, preport2.GetID()); err == nil {
|
||||
t.Error("Expected Report to be removed")
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package report
|
||||
|
||||
import "github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
import (
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
)
|
||||
|
||||
// PolicyReportListener is called whenever a new PolicyReport comes in
|
||||
type PolicyReportListener = func(LifecycleEvent)
|
||||
|
@ -16,4 +18,6 @@ type PolicyReportClient interface {
|
|||
Sync(stopper chan struct{}) error
|
||||
// HasSynced the configured PolicyReport
|
||||
HasSynced() bool
|
||||
// Stop the client
|
||||
Stop()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package report
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
|
@ -8,17 +10,17 @@ import (
|
|||
|
||||
type PolicyReportStore interface {
|
||||
// CreateSchemas for PolicyReports and PolicyReportResults
|
||||
CreateSchemas() error
|
||||
CreateSchemas(context.Context) error
|
||||
// Get an PolicyReport by Type and ID
|
||||
Get(id string) (v1alpha2.ReportInterface, bool)
|
||||
Get(ctx context.Context, id string) (v1alpha2.ReportInterface, error)
|
||||
// Add a PolicyReport to the Store
|
||||
Add(r v1alpha2.ReportInterface) error
|
||||
Add(ctx context.Context, r v1alpha2.ReportInterface) error
|
||||
// Update a PolicyReport to the Store
|
||||
Update(r v1alpha2.ReportInterface) error
|
||||
Update(ctx context.Context, r v1alpha2.ReportInterface) error
|
||||
// Remove a PolicyReport with the given Type and ID from the Store
|
||||
Remove(id string) error
|
||||
Remove(ctx context.Context, id string) error
|
||||
// CleanUp removes all items in the store
|
||||
CleanUp() error
|
||||
CleanUp(ctx context.Context) error
|
||||
}
|
||||
|
||||
// PolicyReportStore caches the latest version of an PolicyReport
|
||||
|
@ -27,26 +29,29 @@ type policyReportStore struct {
|
|||
rwm *sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *policyReportStore) CreateSchemas() error {
|
||||
func (s *policyReportStore) CreateSchemas(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *policyReportStore) Get(id string) (v1alpha2.ReportInterface, bool) {
|
||||
func (s *policyReportStore) Get(_ context.Context, id string) (v1alpha2.ReportInterface, error) {
|
||||
s.rwm.RLock()
|
||||
r, ok := s.store[PolicyReportType][id]
|
||||
s.rwm.RUnlock()
|
||||
if ok {
|
||||
return r, ok
|
||||
return r, nil
|
||||
}
|
||||
|
||||
s.rwm.RLock()
|
||||
r, ok = s.store[ClusterPolicyReportType][id]
|
||||
s.rwm.RUnlock()
|
||||
if ok {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
return r, ok
|
||||
return nil, errors.New("report not found")
|
||||
}
|
||||
|
||||
func (s *policyReportStore) Add(r v1alpha2.ReportInterface) error {
|
||||
func (s *policyReportStore) Add(_ context.Context, r v1alpha2.ReportInterface) error {
|
||||
s.rwm.Lock()
|
||||
defer s.rwm.Unlock()
|
||||
s.store[GetType(r)][r.GetID()] = r
|
||||
|
@ -54,7 +59,7 @@ func (s *policyReportStore) Add(r v1alpha2.ReportInterface) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *policyReportStore) Update(r v1alpha2.ReportInterface) error {
|
||||
func (s *policyReportStore) Update(_ context.Context, r v1alpha2.ReportInterface) error {
|
||||
s.rwm.Lock()
|
||||
defer s.rwm.Unlock()
|
||||
s.store[GetType(r)][r.GetID()] = r
|
||||
|
@ -62,8 +67,8 @@ func (s *policyReportStore) Update(r v1alpha2.ReportInterface) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *policyReportStore) Remove(id string) error {
|
||||
if r, ok := s.Get(id); ok {
|
||||
func (s *policyReportStore) Remove(ctx context.Context, id string) error {
|
||||
if r, err := s.Get(ctx, id); err == nil {
|
||||
s.rwm.Lock()
|
||||
defer s.rwm.Unlock()
|
||||
delete(s.store[GetType(r)], id)
|
||||
|
@ -72,7 +77,7 @@ func (s *policyReportStore) Remove(id string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *policyReportStore) CleanUp() error {
|
||||
func (s *policyReportStore) CleanUp(_ context.Context) error {
|
||||
s.rwm.Lock()
|
||||
defer s.rwm.Unlock()
|
||||
s.store = map[ResourceType]map[string]v1alpha2.ReportInterface{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package report_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -9,19 +10,21 @@ import (
|
|||
"github.com/kyverno/policy-reporter/pkg/report"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func Test_PolicyReportStore(t *testing.T) {
|
||||
store := report.NewPolicyReportStore()
|
||||
store.CreateSchemas()
|
||||
store.CreateSchemas(ctx)
|
||||
|
||||
t.Run("Add/Get", func(t *testing.T) {
|
||||
_, ok := store.Get(preport.GetID())
|
||||
if ok == true {
|
||||
_, err := store.Get(ctx, preport.GetID())
|
||||
if err == nil {
|
||||
t.Fatalf("Should not be found in empty Store")
|
||||
}
|
||||
|
||||
store.Add(preport)
|
||||
_, ok = store.Get(preport.GetID())
|
||||
if ok == false {
|
||||
store.Add(ctx, preport)
|
||||
_, err = store.Get(ctx, preport.GetID())
|
||||
if err != nil {
|
||||
t.Errorf("Should be found in Store after adding report to the store")
|
||||
}
|
||||
})
|
||||
|
@ -37,38 +40,38 @@ func Test_PolicyReportStore(t *testing.T) {
|
|||
Summary: v1alpha2.PolicyReportSummary{Skip: 1},
|
||||
}
|
||||
|
||||
store.Add(preport)
|
||||
r, _ := store.Get(preport.GetID())
|
||||
store.Add(ctx, preport)
|
||||
r, _ := store.Get(ctx, preport.GetID())
|
||||
if r.GetSummary().Skip != 0 {
|
||||
t.Errorf("Expected Summary.Skip to be 0")
|
||||
}
|
||||
|
||||
store.Update(ureport)
|
||||
r2, _ := store.Get(preport.GetID())
|
||||
store.Update(ctx, ureport)
|
||||
r2, _ := store.Get(ctx, preport.GetID())
|
||||
if r2.GetSummary().Skip != 1 {
|
||||
t.Errorf("Expected Summary.Skip to be 1 after update")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Delete/Get", func(t *testing.T) {
|
||||
_, ok := store.Get(preport.GetID())
|
||||
if ok == false {
|
||||
_, err := store.Get(ctx, preport.GetID())
|
||||
if err != nil {
|
||||
t.Errorf("Should be found in Store after adding report to the store")
|
||||
}
|
||||
|
||||
store.Remove(preport.GetID())
|
||||
_, ok = store.Get(preport.GetID())
|
||||
if ok == true {
|
||||
store.Remove(ctx, preport.GetID())
|
||||
_, err = store.Get(ctx, preport.GetID())
|
||||
if err == nil {
|
||||
t.Fatalf("Should not be found after Remove report from Store")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CleanUp", func(t *testing.T) {
|
||||
store.Add(preport)
|
||||
store.Add(ctx, preport)
|
||||
|
||||
store.CleanUp()
|
||||
_, ok := store.Get(preport.GetID())
|
||||
if ok == true {
|
||||
store.CleanUp(ctx)
|
||||
_, err := store.Get(ctx, preport.GetID())
|
||||
if err == nil {
|
||||
t.Fatalf("Should have no results after CleanUp")
|
||||
}
|
||||
})
|
||||
|
|
1549
pkg/sqlite3/store.go
1549
pkg/sqlite3/store.go
File diff suppressed because it is too large
Load diff
|
@ -4,9 +4,9 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
hub "github.com/aws/aws-sdk-go/service/securityhub"
|
||||
"go.uber.org/zap"
|
||||
|
||||
hub "github.com/aws/aws-sdk-go/service/securityhub"
|
||||
"github.com/kyverno/policy-reporter/pkg/crd/api/policyreport/v1alpha2"
|
||||
"github.com/kyverno/policy-reporter/pkg/target"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue