mirror of
https://github.com/kyverno/policy-reporter.git
synced 2024-12-14 11:57:32 +00:00
commit
a949b409cd
11 changed files with 439 additions and 92 deletions
|
@ -1,5 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
## 0.9.0
|
||||
|
||||
* Implement Discord as Target for PolicyReportResults
|
||||
|
||||
## 0.8.0
|
||||
|
||||
* Implement Slack as Target for PolicyReportResults
|
||||
|
@ -8,4 +12,4 @@
|
|||
|
||||
* Implement Elasticsearch as Target for PolicyReportResults
|
||||
* Replace CLI flags with a single `config.yaml` to manage target-configurations as separate `ConfigMap`
|
||||
* Set `loki.skipExistingOnStartup` default value to `true`
|
||||
* Set `loki.skipExistingOnStartup` default value to `true`
|
||||
|
|
63
README.md
63
README.md
|
@ -28,6 +28,17 @@ helm install policy-reporter policy-reporter/policy-reporter -n policy-reporter
|
|||
```bash
|
||||
helm install policy-reporter policy-reporter/policy-reporter --set loki.host=http://loki:3100 -n policy-reporter --create-namespace
|
||||
```
|
||||
#### Additional configurations for Loki
|
||||
|
||||
* Configure `loki.minimumPriority` to send only results with the configured minimumPriority or above, empty means all results. (info < warning < error)
|
||||
* Configure `loki.skipExistingOnStartup` to skip all results who already existed before the PolicyReporter started (default: `true`).
|
||||
|
||||
```yaml
|
||||
loki:
|
||||
host: ""
|
||||
minimumPriority: ""
|
||||
skipExistingOnStartup: true
|
||||
```
|
||||
|
||||
### Installation with Elasticsearch
|
||||
|
||||
|
@ -35,26 +46,7 @@ helm install policy-reporter policy-reporter/policy-reporter --set loki.host=htt
|
|||
helm install policy-reporter policy-reporter/policy-reporter --set elasticsearch.host=http://elasticsearch:3100 -n policy-reporter --create-namespace
|
||||
```
|
||||
|
||||
### Installation with Slack
|
||||
|
||||
```bash
|
||||
helm install policy-reporter policy-reporter/policy-reporter --set slack.webhook=http://hook.slack -n policy-reporter --create-namespace
|
||||
```
|
||||
|
||||
You can also customize the `./charts/policy-reporter/values.yaml` to change the default configurations.
|
||||
|
||||
### Additional configurations for Loki
|
||||
|
||||
* Configure `loki.minimumPriority` to send only results with the configured minimumPriority or above, empty means all results. (info < warning < error)
|
||||
* Configure `loki.skipExistingOnStartup` to skip all results who already existed before the PolicyReporter started (default: `true`).
|
||||
|
||||
```yaml
|
||||
loki:
|
||||
minimumPriority: ""
|
||||
skipExistingOnStartup: true
|
||||
```
|
||||
|
||||
### Additional configurations for Elasticsearch
|
||||
#### Additional configurations for Elasticsearch
|
||||
|
||||
* Configure `elasticsearch.index` to customize the elasticsearch index.
|
||||
* Configure `elasticsearch.rotation` is added as suffix to the index. Possible values are `daily`, `monthly`, `annually` and `none`.
|
||||
|
@ -63,12 +55,20 @@ loki:
|
|||
|
||||
```yaml
|
||||
elasticsearch:
|
||||
host: ""
|
||||
index: "policy-reporter"
|
||||
rotation: "daily"
|
||||
minimumPriority: ""
|
||||
skipExistingOnStartup: true
|
||||
```
|
||||
|
||||
|
||||
### Installation with Slack
|
||||
|
||||
```bash
|
||||
helm install policy-reporter policy-reporter/policy-reporter --set slack.webhook=http://hook.slack -n policy-reporter --create-namespace
|
||||
```
|
||||
|
||||
### Additional configurations for Slack
|
||||
|
||||
* Configure `slack.minimumPriority` to send only results with the configured minimumPriority or above, empty means all results. (info < warning < error)
|
||||
|
@ -76,10 +76,33 @@ elasticsearch:
|
|||
|
||||
```yaml
|
||||
slack:
|
||||
webhook: ""
|
||||
minimumPriority: ""
|
||||
skipExistingOnStartup: true
|
||||
```
|
||||
|
||||
### Installation with Discord
|
||||
|
||||
```bash
|
||||
helm install policy-reporter policy-reporter/policy-reporter --set discord.webhook=http://hook.discord -n policy-reporter --create-namespace
|
||||
```
|
||||
|
||||
#### Additional configurations for Discord
|
||||
|
||||
* Configure `discord.minimumPriority` to send only results with the configured minimumPriority or above, empty means all results. (info < warning < error)
|
||||
* Configure `discord.skipExistingOnStartup` to skip all results who already existed before the PolicyReporter started (default: `true`).
|
||||
|
||||
```yaml
|
||||
discord:
|
||||
webhook: ""
|
||||
minimumPriority: ""
|
||||
skipExistingOnStartup: true
|
||||
```
|
||||
|
||||
### Customization
|
||||
|
||||
You can combine multiple targets by setting the required `host` or `webhook` configuration for your targets of choice. For all possible configurations checkout the `./charts/policy-reporter/values.yaml` to change any configurations available.
|
||||
|
||||
### Configure Policy Priorities
|
||||
|
||||
By default kyverno PolicyReports has no priority or severity for policies. So every passed rule validation will be processed as notice, a failed validation is processed as error. To customize this you can configure a mapping from policies to fail priorities. So you can send them as warnings instead of errors. To configure the priorities create a ConfigMap in the `policy-reporter` namespace with the name `policy-reporter-priorities`. Configure each priority as value with the Policyname as key and the Priority as value. This Configuration is loaded and synchronized during runtime. Any change to this configmap will automaticly synchronized, no new deployment needed.
|
||||
|
|
|
@ -3,5 +3,5 @@ name: policy-reporter
|
|||
description: K8s PolicyReporter watches for wgpolicyk8s.io/v1alpha1.PolicyReport resources. It creates Prometheus Metrics and can send rule validation events to Loki
|
||||
|
||||
type: application
|
||||
version: 0.9.0
|
||||
appVersion: 0.8.0
|
||||
version: 0.10.0
|
||||
appVersion: 0.9.0
|
||||
|
|
|
@ -20,5 +20,10 @@ data:
|
|||
|
||||
slack:
|
||||
webhook: {{ .Values.slack.webhook | quote }}
|
||||
minimumPriority: {{ .Values.loki.minimumPriority | quote }}
|
||||
skipExistingOnStartup: {{ .Values.loki.skipExistingOnStartup }}
|
||||
minimumPriority: {{ .Values.slack.minimumPriority | quote }}
|
||||
skipExistingOnStartup: {{ .Values.slack.skipExistingOnStartup }}
|
||||
|
||||
discord:
|
||||
webhook: {{ .Values.discord.webhook | quote }}
|
||||
minimumPriority: {{ .Values.discord.minimumPriority | quote }}
|
||||
skipExistingOnStartup: {{ .Values.discord.skipExistingOnStartup }}
|
||||
|
|
|
@ -27,6 +27,14 @@ slack:
|
|||
# Skip already existing PolicyReportResults on startup
|
||||
skipExistingOnStartup: true
|
||||
|
||||
discord:
|
||||
# discord app webhook address
|
||||
webhook: ""
|
||||
# minimum priority "" < info < warning < error
|
||||
minimumPriority: ""
|
||||
# Skip already existing PolicyReportResults on startup
|
||||
skipExistingOnStartup: true
|
||||
|
||||
metrics:
|
||||
serviceMonitor: false
|
||||
dashboard:
|
||||
|
@ -36,7 +44,7 @@ metrics:
|
|||
image:
|
||||
repository: fjogeleit/policy-reporter
|
||||
pullPolicy: IfNotPresent
|
||||
tag: 0.8.0
|
||||
tag: 0.9.0
|
||||
|
||||
imagePullSecrets: []
|
||||
|
||||
|
|
|
@ -23,11 +23,19 @@ type Slack struct {
|
|||
MinimumPriority string `mapstructure:"minimumPriority"`
|
||||
}
|
||||
|
||||
// Discord configuration
|
||||
type Discord struct {
|
||||
Webhook string `mapstructure:"webhook"`
|
||||
SkipExisting bool `mapstructure:"skipExistingOnStartup"`
|
||||
MinimumPriority string `mapstructure:"minimumPriority"`
|
||||
}
|
||||
|
||||
// Config of the PolicyReporter
|
||||
type Config struct {
|
||||
Loki Loki `mapstructure:"loki"`
|
||||
Elasticsearch Elasticsearch `mapstructure:"elasticsearch"`
|
||||
Slack Slack `mapstructure:"slack"`
|
||||
Discord Discord `mapstructure:"discord"`
|
||||
Kubeconfig string `mapstructure:"kubeconfig"`
|
||||
Namespace string `mapstructure:"namespace"`
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"github.com/fjogeleit/policy-reporter/pkg/metrics"
|
||||
"github.com/fjogeleit/policy-reporter/pkg/report"
|
||||
"github.com/fjogeleit/policy-reporter/pkg/target"
|
||||
"github.com/fjogeleit/policy-reporter/pkg/target/discord"
|
||||
"github.com/fjogeleit/policy-reporter/pkg/target/elasticsearch"
|
||||
"github.com/fjogeleit/policy-reporter/pkg/target/loki"
|
||||
"github.com/fjogeleit/policy-reporter/pkg/target/slack"
|
||||
|
@ -21,6 +23,7 @@ type Resolver struct {
|
|||
lokiClient target.Client
|
||||
elasticsearchClient target.Client
|
||||
slackClient target.Client
|
||||
discordClient target.Client
|
||||
policyReportMetrics metrics.Metrics
|
||||
clusterPolicyReportMetrics metrics.Metrics
|
||||
}
|
||||
|
@ -60,6 +63,8 @@ func (r *Resolver) LokiClient() target.Client {
|
|||
&http.Client{},
|
||||
)
|
||||
|
||||
log.Println("[INFO] Loki configured")
|
||||
|
||||
return r.lokiClient
|
||||
}
|
||||
|
||||
|
@ -88,10 +93,12 @@ func (r *Resolver) ElasticsearchClient() target.Client {
|
|||
&http.Client{},
|
||||
)
|
||||
|
||||
log.Println("[INFO] Elasticsearch configured")
|
||||
|
||||
return r.elasticsearchClient
|
||||
}
|
||||
|
||||
// ElasticsearchClient resolver method
|
||||
// SlackClient resolver method
|
||||
func (r *Resolver) SlackClient() target.Client {
|
||||
if r.slackClient != nil {
|
||||
return r.slackClient
|
||||
|
@ -108,9 +115,33 @@ func (r *Resolver) SlackClient() target.Client {
|
|||
&http.Client{},
|
||||
)
|
||||
|
||||
log.Println("[INFO] Slack configured")
|
||||
|
||||
return r.slackClient
|
||||
}
|
||||
|
||||
// DiscordClient resolver method
|
||||
func (r *Resolver) DiscordClient() target.Client {
|
||||
if r.discordClient != nil {
|
||||
return r.discordClient
|
||||
}
|
||||
|
||||
if r.config.Discord.Webhook == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.discordClient = discord.NewClient(
|
||||
r.config.Discord.Webhook,
|
||||
r.config.Discord.MinimumPriority,
|
||||
r.config.Discord.SkipExisting,
|
||||
&http.Client{},
|
||||
)
|
||||
|
||||
log.Println("[INFO] Discord configured")
|
||||
|
||||
return r.discordClient
|
||||
}
|
||||
|
||||
// PolicyReportMetrics resolver method
|
||||
func (r *Resolver) PolicyReportMetrics() (metrics.Metrics, error) {
|
||||
if r.policyReportMetrics != nil {
|
||||
|
@ -158,6 +189,10 @@ func (r *Resolver) TargetClients() []target.Client {
|
|||
clients = append(clients, slack)
|
||||
}
|
||||
|
||||
if discord := r.DiscordClient(); discord != nil {
|
||||
clients = append(clients, discord)
|
||||
}
|
||||
|
||||
return clients
|
||||
}
|
||||
|
||||
|
|
|
@ -20,60 +20,72 @@ var testConfig = &config.Config{
|
|||
MinimumPriority: "debug",
|
||||
},
|
||||
Slack: config.Slack{
|
||||
Webhook: "http://localhost:80",
|
||||
Webhook: "http://hook.slack:80",
|
||||
SkipExisting: true,
|
||||
MinimumPriority: "debug",
|
||||
},
|
||||
Discord: config.Discord{
|
||||
Webhook: "http://hook.discord:80",
|
||||
SkipExisting: true,
|
||||
MinimumPriority: "debug",
|
||||
},
|
||||
}
|
||||
|
||||
func Test_ResolveLokiClient(t *testing.T) {
|
||||
func Test_ResolveClient(t *testing.T) {
|
||||
resolver := config.NewResolver(testConfig)
|
||||
|
||||
client := resolver.LokiClient()
|
||||
if client == nil {
|
||||
t.Error("Expected Client, got nil")
|
||||
}
|
||||
t.Run("Loki", func(t *testing.T) {
|
||||
client := resolver.LokiClient()
|
||||
if client == nil {
|
||||
t.Error("Expected Client, got nil")
|
||||
}
|
||||
|
||||
client2 := resolver.LokiClient()
|
||||
if client != client2 {
|
||||
t.Error("Error: Should reuse first instance")
|
||||
}
|
||||
}
|
||||
client2 := resolver.LokiClient()
|
||||
if client != client2 {
|
||||
t.Error("Error: Should reuse first instance")
|
||||
}
|
||||
})
|
||||
t.Run("Elasticsearch", func(t *testing.T) {
|
||||
client := resolver.ElasticsearchClient()
|
||||
if client == nil {
|
||||
t.Error("Expected Client, got nil")
|
||||
}
|
||||
|
||||
func Test_ResolveElasticSearchClient(t *testing.T) {
|
||||
resolver := config.NewResolver(testConfig)
|
||||
client2 := resolver.ElasticsearchClient()
|
||||
if client != client2 {
|
||||
t.Error("Error: Should reuse first instance")
|
||||
}
|
||||
})
|
||||
t.Run("Slack", func(t *testing.T) {
|
||||
client := resolver.SlackClient()
|
||||
if client == nil {
|
||||
t.Error("Expected Client, got nil")
|
||||
}
|
||||
|
||||
client := resolver.ElasticsearchClient()
|
||||
if client == nil {
|
||||
t.Error("Expected Client, got nil")
|
||||
}
|
||||
client2 := resolver.SlackClient()
|
||||
if client != client2 {
|
||||
t.Error("Error: Should reuse first instance")
|
||||
}
|
||||
})
|
||||
t.Run("Discord", func(t *testing.T) {
|
||||
client := resolver.DiscordClient()
|
||||
if client == nil {
|
||||
t.Error("Expected Client, got nil")
|
||||
}
|
||||
|
||||
client2 := resolver.ElasticsearchClient()
|
||||
if client != client2 {
|
||||
t.Error("Error: Should reuse first instance")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ResolveSlackClient(t *testing.T) {
|
||||
resolver := config.NewResolver(testConfig)
|
||||
|
||||
client := resolver.SlackClient()
|
||||
if client == nil {
|
||||
t.Error("Expected Client, got nil")
|
||||
}
|
||||
|
||||
client2 := resolver.SlackClient()
|
||||
if client != client2 {
|
||||
t.Error("Error: Should reuse first instance")
|
||||
}
|
||||
client2 := resolver.DiscordClient()
|
||||
if client != client2 {
|
||||
t.Error("Error: Should reuse first instance")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ResolveTargets(t *testing.T) {
|
||||
resolver := config.NewResolver(testConfig)
|
||||
|
||||
clients := resolver.TargetClients()
|
||||
if count := len(clients); count != 3 {
|
||||
t.Errorf("Expected 3 Clients, got %d", count)
|
||||
if count := len(clients); count != 4 {
|
||||
t.Errorf("Expected 4 Clients, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,25 +126,13 @@ func Test_ResolveSkipExistingOnStartup(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func Test_ResolveLokiClientWithoutHost(t *testing.T) {
|
||||
func Test_ResolveClientWithoutHost(t *testing.T) {
|
||||
config2 := &config.Config{
|
||||
Loki: config.Loki{
|
||||
Host: "",
|
||||
SkipExisting: true,
|
||||
MinimumPriority: "debug",
|
||||
},
|
||||
}
|
||||
|
||||
resolver := config.NewResolver(config2)
|
||||
resolver.Reset()
|
||||
|
||||
if resolver.LokiClient() != nil {
|
||||
t.Error("Expected Client to be nil if no host is configured")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ResolveElasticsearchClientWithoutHost(t *testing.T) {
|
||||
config2 := &config.Config{
|
||||
Elasticsearch: config.Elasticsearch{
|
||||
Host: "",
|
||||
Index: "policy-reporter",
|
||||
|
@ -140,27 +140,48 @@ func Test_ResolveElasticsearchClientWithoutHost(t *testing.T) {
|
|||
SkipExisting: true,
|
||||
MinimumPriority: "debug",
|
||||
},
|
||||
}
|
||||
|
||||
resolver := config.NewResolver(config2)
|
||||
|
||||
if resolver.ElasticsearchClient() != nil {
|
||||
t.Error("Expected Client to be nil if no host is configured")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ResolveSlackClientWithoutHost(t *testing.T) {
|
||||
config2 := &config.Config{
|
||||
Slack: config.Slack{
|
||||
Webhook: "",
|
||||
SkipExisting: true,
|
||||
MinimumPriority: "debug",
|
||||
},
|
||||
Discord: config.Discord{
|
||||
Webhook: "",
|
||||
SkipExisting: true,
|
||||
MinimumPriority: "debug",
|
||||
},
|
||||
}
|
||||
|
||||
resolver := config.NewResolver(config2)
|
||||
t.Run("Loki", func(t *testing.T) {
|
||||
resolver := config.NewResolver(config2)
|
||||
resolver.Reset()
|
||||
|
||||
if resolver.SlackClient() != nil {
|
||||
t.Error("Expected Client to be nil if no host is configured")
|
||||
}
|
||||
if resolver.LokiClient() != nil {
|
||||
t.Error("Expected Client to be nil if no host is configured")
|
||||
}
|
||||
})
|
||||
t.Run("Elasticsearch", func(t *testing.T) {
|
||||
resolver := config.NewResolver(config2)
|
||||
resolver.Reset()
|
||||
|
||||
if resolver.ElasticsearchClient() != nil {
|
||||
t.Error("Expected Client to be nil if no host is configured")
|
||||
}
|
||||
})
|
||||
t.Run("Slack", func(t *testing.T) {
|
||||
resolver := config.NewResolver(config2)
|
||||
resolver.Reset()
|
||||
|
||||
if resolver.SlackClient() != nil {
|
||||
t.Error("Expected Client to be nil if no host is configured")
|
||||
}
|
||||
})
|
||||
t.Run("Discord", func(t *testing.T) {
|
||||
resolver := config.NewResolver(config2)
|
||||
resolver.Reset()
|
||||
|
||||
if resolver.DiscordClient() != nil {
|
||||
t.Error("Expected Client to be nil if no host is configured")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
152
pkg/target/discord/discord.go
Normal file
152
pkg/target/discord/discord.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
package discord
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/fjogeleit/policy-reporter/pkg/report"
|
||||
"github.com/fjogeleit/policy-reporter/pkg/target"
|
||||
)
|
||||
|
||||
type payload struct {
|
||||
Content string `json:"content"`
|
||||
Embeds []embed `json:"embeds"`
|
||||
}
|
||||
|
||||
type embed struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Color string `json:"color"`
|
||||
Fields []embedField `json:"fields"`
|
||||
}
|
||||
|
||||
type embedField struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Inline bool `json:"inline"`
|
||||
}
|
||||
|
||||
func newPayload(result report.Result) payload {
|
||||
var color string
|
||||
switch result.Priority {
|
||||
case report.ErrorPriority:
|
||||
color = "15158332"
|
||||
case report.WarningPriority:
|
||||
color = "15105570"
|
||||
case report.InfoPriority:
|
||||
color = "3066993"
|
||||
case report.DebugPriority:
|
||||
color = "12370112"
|
||||
}
|
||||
|
||||
embedFields := make([]embedField, 0)
|
||||
|
||||
embedFields = append(embedFields, embedField{"Policy", result.Policy, true})
|
||||
|
||||
if result.Rule != "" {
|
||||
embedFields = append(embedFields, embedField{"Rule", result.Rule, true})
|
||||
}
|
||||
|
||||
embedFields = append(embedFields, embedField{"Priority", result.Priority.String(), true})
|
||||
|
||||
if result.Category != "" {
|
||||
embedFields = append(embedFields, embedField{"Category", result.Category, true})
|
||||
}
|
||||
if result.Severity != "" {
|
||||
embedFields = append(embedFields, embedField{"Severity", result.Severity, true})
|
||||
}
|
||||
res := report.Resource{}
|
||||
|
||||
if len(result.Resources) > 0 {
|
||||
res = result.Resources[0]
|
||||
}
|
||||
if res.Kind != "" {
|
||||
embedFields = append(embedFields, embedField{"Kind", res.Kind, true})
|
||||
embedFields = append(embedFields, embedField{"Name", res.Name, true})
|
||||
if res.Namespace != "" {
|
||||
embedFields = append(embedFields, embedField{"Namespace", res.Namespace, true})
|
||||
}
|
||||
embedFields = append(embedFields, embedField{"API Version", res.APIVersion, true})
|
||||
}
|
||||
|
||||
embeds := make([]embed, 0, 1)
|
||||
embeds = append(embeds, embed{
|
||||
Title: "New Policy Report Result",
|
||||
Description: result.Message,
|
||||
Color: color,
|
||||
Fields: embedFields,
|
||||
})
|
||||
|
||||
return payload{
|
||||
Content: "",
|
||||
Embeds: embeds,
|
||||
}
|
||||
}
|
||||
|
||||
type httpClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type client struct {
|
||||
webhook string
|
||||
minimumPriority string
|
||||
skipExistingOnStartup bool
|
||||
client httpClient
|
||||
}
|
||||
|
||||
func (d *client) Send(result report.Result) {
|
||||
if result.Priority < report.NewPriority(d.minimumPriority) {
|
||||
return
|
||||
}
|
||||
|
||||
payload := newPayload(result)
|
||||
body := new(bytes.Buffer)
|
||||
|
||||
if err := json.NewEncoder(body).Encode(payload); err != nil {
|
||||
log.Printf("[ERROR] DISCORD : %v\n", err.Error())
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", d.webhook, body)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] DISCORD : %v\n", err.Error())
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json; charset=utf-8")
|
||||
req.Header.Add("User-Agent", "Policy-Reporter")
|
||||
|
||||
resp, err := d.client.Do(req)
|
||||
defer func() {
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] DISCORD PUSH failed: %s\n", err.Error())
|
||||
} else if resp.StatusCode >= 400 {
|
||||
fmt.Printf("StatusCode: %d\n", resp.StatusCode)
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(resp.Body)
|
||||
|
||||
log.Printf("[ERROR] DISCORD PUSH failed [%d]: %s\n", resp.StatusCode, buf.String())
|
||||
} else {
|
||||
log.Println("[INFO] DISCORD PUSH OK")
|
||||
}
|
||||
}
|
||||
|
||||
func (d *client) SkipExistingOnStartup() bool {
|
||||
return d.skipExistingOnStartup
|
||||
}
|
||||
|
||||
// NewClient creates a new loki.client to send Results to Loki
|
||||
func NewClient(webhook, minimumPriority string, skipExistingOnStartup bool, httpClient httpClient) target.Client {
|
||||
return &client{
|
||||
webhook,
|
||||
minimumPriority,
|
||||
skipExistingOnStartup,
|
||||
httpClient,
|
||||
}
|
||||
}
|
90
pkg/target/discord/discord_test.go
Normal file
90
pkg/target/discord/discord_test.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package discord_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/fjogeleit/policy-reporter/pkg/report"
|
||||
"github.com/fjogeleit/policy-reporter/pkg/target/discord"
|
||||
)
|
||||
|
||||
var completeResult = report.Result{
|
||||
Message: "validation error: requests and limits required. Rule autogen-check-for-requests-and-limits failed at path /spec/template/spec/containers/0/resources/requests/",
|
||||
Policy: "require-requests-and-limits-required",
|
||||
Rule: "autogen-check-for-requests-and-limits",
|
||||
Priority: report.ErrorPriority,
|
||||
Status: report.Fail,
|
||||
Severity: report.Heigh,
|
||||
Category: "resources",
|
||||
Scored: true,
|
||||
Resources: []report.Resource{
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "Deployment",
|
||||
Name: "nginx",
|
||||
Namespace: "default",
|
||||
UID: "536ab69f-1b3c-4bd9-9ba4-274a56188409",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var minimalResult = report.Result{
|
||||
Message: "validation error: label required. Rule app-label-required failed at path /spec/template/spec/containers/0/resources/requests/",
|
||||
Policy: "app-label-requirement",
|
||||
Priority: report.WarningPriority,
|
||||
Status: report.Fail,
|
||||
Scored: true,
|
||||
}
|
||||
|
||||
type testClient struct {
|
||||
callback func(req *http.Request)
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (c testClient) Do(req *http.Request) (*http.Response, error) {
|
||||
c.callback(req)
|
||||
|
||||
return &http.Response{
|
||||
StatusCode: c.statusCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func Test_LokiTarget(t *testing.T) {
|
||||
t.Run("Send Complete Result", func(t *testing.T) {
|
||||
callback := func(req *http.Request) {
|
||||
if contentType := req.Header.Get("Content-Type"); contentType != "application/json; charset=utf-8" {
|
||||
t.Errorf("Unexpected Content-Type: %s", contentType)
|
||||
}
|
||||
|
||||
if agend := req.Header.Get("User-Agent"); agend != "Policy-Reporter" {
|
||||
t.Errorf("Unexpected Host: %s", agend)
|
||||
}
|
||||
|
||||
if url := req.URL.String(); url != "http://hook.discord:80" {
|
||||
t.Errorf("Unexpected Host: %s", url)
|
||||
}
|
||||
}
|
||||
|
||||
slack := discord.NewClient("http://hook.discord:80", "", false, testClient{callback, 200})
|
||||
slack.Send(minimalResult)
|
||||
})
|
||||
|
||||
t.Run("Send Minimal Result", func(t *testing.T) {
|
||||
callback := func(req *http.Request) {
|
||||
if contentType := req.Header.Get("Content-Type"); contentType != "application/json; charset=utf-8" {
|
||||
t.Errorf("Unexpected Content-Type: %s", contentType)
|
||||
}
|
||||
|
||||
if agend := req.Header.Get("User-Agent"); agend != "Policy-Reporter" {
|
||||
t.Errorf("Unexpected Host: %s", agend)
|
||||
}
|
||||
|
||||
if url := req.URL.String(); url != "http://hook.discord:80" {
|
||||
t.Errorf("Unexpected Host: %s", url)
|
||||
}
|
||||
}
|
||||
|
||||
slack := discord.NewClient("http://hook.discord:80", "", false, testClient{callback, 200})
|
||||
slack.Send(minimalResult)
|
||||
})
|
||||
}
|
|
@ -60,6 +60,7 @@ func newLokiPayload(result report.Result) payload {
|
|||
if res.Kind != "" {
|
||||
labels = append(labels, "kind=\""+res.Kind+"\"")
|
||||
labels = append(labels, "name=\""+res.Name+"\"")
|
||||
labels = append(labels, "apiVersion=\""+res.APIVersion+"\"")
|
||||
labels = append(labels, "uid=\""+res.UID+"\"")
|
||||
labels = append(labels, "namespace=\""+res.Namespace+"\"")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue