Skip to content

Commit

Permalink
Merge pull request #322 from fsequeira1/add-grafana
Browse files Browse the repository at this point in the history
Add support for Grafana annotations API
  • Loading branch information
stefanprodan authored Feb 9, 2022
2 parents 171b5eb + 659c01f commit 0715fec
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 4 deletions.
3 changes: 2 additions & 1 deletion api/v1beta1/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (
// ProviderSpec defines the desired state of Provider
type ProviderSpec struct {
// Type of provider
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;github;gitlab;bitbucket;azuredevops;googlechat;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;github;gitlab;bitbucket;azuredevops;googlechat;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;
// +required
Type string `json:"type"`

Expand Down Expand Up @@ -71,6 +71,7 @@ type ProviderSpec struct {
const (
GenericProvider string = "generic"
SlackProvider string = "slack"
GrafanaProvider string = "grafana"
DiscordProvider string = "discord"
MSTeamsProvider string = "msteams"
RocketProvider string = "rocket"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ spec:
- matrix
- opsgenie
- alertmanager
- grafana
type: string
username:
description: Bot username for this provider
Expand Down
35 changes: 32 additions & 3 deletions docs/spec/v1beta1/provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Notification providers:
| Discord | discord |
| Generic webhook | generic |
| Google Chat | googlechat |
| Grafana | grafana |
| Lark | lark |
| Matrix | matrix |
| Microsoft Teams | msteams |
Expand Down Expand Up @@ -122,7 +123,7 @@ kubectl create secret generic webhook-url \
Note that the secret must contain an `address` field.

The provider type can be: `slack`, `msteams`, `rocket`, `discord`, `googlechat`, `webex`, `sentry`,
`telegram`, `lark`, `matrix`, `azureeventhub`, `opsgenie`, `alertmanager` or `generic`.
`telegram`, `lark`, `matrix`, `azureeventhub`, `opsgenie`, `alertmanager`, `grafana` or `generic`.

When type `generic` is specified, the notification controller will post the
incoming [event](event.md) in JSON format to the webhook address.
Expand Down Expand Up @@ -336,7 +337,6 @@ spec:
name: opsgenie-token
```


### Prometheus Alertmanager

Sends notifications to [alertmanager v2 api](https://github.com/prometheus/alertmanager/blob/main/api/v2/openapi.yaml) if alert manager has basic authentication configured it is recommended to use
Expand Down Expand Up @@ -370,7 +370,6 @@ The provider will send the following labels for the event.
| name | The name of the involved object associated with the event |
| namespace | The namespace of the involved object associated with the event |


### Slack App

It is possible to use a Slack App bot integration to send messages. To obtain a bot token, follow
Expand Down Expand Up @@ -409,6 +408,35 @@ spec:
```


### Grafana

To send notifications to [Grafana annotations API](https://grafana.com/docs/grafana/latest/http_api/annotations/),
you have to enable the annotations on a Dashboard like so:

- Annotations > Query > Enable Match any
- Annotations > Query > Tags (Add Tag: `flux`)

If Grafana has authentication configured, create a Kubernetes Secret with the API URL and the API token:
```shell
kubectl create secret generic grafana-token \
--from-literal=token=<grafana-api-key> \
--from-literal=address=https://<grafana-url>/api/annotations
```

Then reference the secret in `spec.secretRef`:

```yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Provider
metadata:
name: grafana
namespace: default
spec:
type: grafana
secretRef:
name: grafana-token
```

### Git commit status

The GitHub, GitLab, Bitbucket, and Azure DevOps provider will write to the
Expand Down Expand Up @@ -491,6 +519,7 @@ metadata:
data:
token: <api-key>
```

### Azure Event Hub

The Azure Event Hub supports two authentication methods, [JWT](https://docs.microsoft.com/en-us/azure/event-hubs/authenticate-application)
Expand Down
2 changes: 2 additions & 0 deletions internal/notifier/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ func (f Factory) Notifier(provider string) (Interface, error) {
n, err = NewOpsgenie(f.URL, f.ProxyURL, f.CertPool, f.Token)
case v1beta1.AlertManagerProvider:
n, err = NewAlertmanager(f.URL, f.ProxyURL, f.CertPool)
case v1beta1.GrafanaProvider:
n, err = NewGrafana(f.URL, f.ProxyURL, f.Token, f.CertPool)
default:
err = fmt.Errorf("provider %s not supported", provider)
}
Expand Down
90 changes: 90 additions & 0 deletions internal/notifier/grafana.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package notifier

import (
"crypto/x509"
"fmt"
"net/url"
"strings"

"github.com/fluxcd/pkg/runtime/events"
"github.com/hashicorp/go-retryablehttp"
)

// Discord holds the hook URL
type Grafana struct {
URL string
Token string
ProxyURL string
CertPool *x509.CertPool
}

// GraphiteAnnotation represents a Grafana API annotation in Graphite format
type GraphitePayload struct {
//What string `json:"what"` //optional
When int64 `json:"when"` //optional unix timestamp (ms)
Text string `json:"text"`
Tags []string `json:"tags,omitempty"`
}

// NewGrafana validates the Grafana URL and returns a Grafana object
func NewGrafana(URL string, proxyURL string, token string, certPool *x509.CertPool) (*Grafana, error) {
_, err := url.ParseRequestURI(URL)
if err != nil {
return nil, fmt.Errorf("invalid Grafana URL %s", URL)
}

return &Grafana{
URL: URL,
ProxyURL: proxyURL,
Token: token,
CertPool: certPool,
}, nil
}

// Post annotation
func (s *Grafana) Post(event events.Event) error {
// Skip any update events
if isCommitStatus(event.Metadata, "update") {
return nil
}

sfields := make([]string, 0, len(event.Metadata))
sfields = append(sfields, "flux")
sfields = append(sfields, event.ReportingController)
for k, v := range event.Metadata {
sfields = append(sfields, fmt.Sprintf("%s: %s", k, v))
}
// add tag to filter on grafana

payload := GraphitePayload{
When: event.Timestamp.Unix(),
Text: fmt.Sprintf("%s/%s.%s", strings.ToLower(event.InvolvedObject.Kind), event.InvolvedObject.Name, event.InvolvedObject.Namespace),
Tags: sfields,
}

err := postMessage(s.URL, s.ProxyURL, s.CertPool, payload, func(request *retryablehttp.Request) {
if s.Token != "" {
request.Header.Add("Authorization", "Bearer "+s.Token)
}
})
if err != nil {
return fmt.Errorf("postMessage failed: %w", err)
}
return nil
}
49 changes: 49 additions & 0 deletions internal/notifier/grafana_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Copyright 2020 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package notifier

import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/require"
)

func TestGrafana_Post(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := io.ReadAll(r.Body)
require.NoError(t, err)
var payload = GraphitePayload{}
err = json.Unmarshal(b, &payload)
require.NoError(t, err)

require.Equal(t, "gitrepository/webapp.gitops-system", payload.Text)
require.Equal(t, "flux", payload.Tags[0])
require.Equal(t, "source-controller", payload.Tags[1])
require.Equal(t, "test: metadata", payload.Tags[2])
}))
defer ts.Close()

grafana, err := NewGrafana(ts.URL, "", "", nil)
require.NoError(t, err)

err = grafana.Post(testEvent())
require.NoError(t, err)
}

0 comments on commit 0715fec

Please sign in to comment.