Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create mimir client #406

Merged
merged 28 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1d037ab
Create small client for mimir
spinillos Apr 4, 2024
3c5de89
Merge branch 'main' of github.com:grafana/grizzly into create-mimir-c…
spinillos Apr 5, 2024
41d9e75
Create MimirTool and refactor CortexTool as client
spinillos Apr 5, 2024
058ead2
Configure mimirtool, cortextool or api
spinillos Apr 5, 2024
075d366
Allow to select tool and fix http ListRules
spinillos Apr 8, 2024
72e3c33
Fix http client and unit tests
spinillos Apr 9, 2024
c0bfddf
Rename LoadRules to CreateRules and throw an error when multiple rule…
spinillos Apr 9, 2024
bc72257
Fix basic auth for mimir http
spinillos Apr 9, 2024
a8d5a65
Sort imports and update override values
spinillos Apr 10, 2024
a0d04f7
Remove auth-key
spinillos Apr 10, 2024
334a234
Fix cortextool path
spinillos Apr 10, 2024
3d7bde2
Check cortextool binary if any client set and update documentation
spinillos Apr 10, 2024
dd90a13
Update docs
spinillos Apr 10, 2024
e35ca4e
Iterate groups in http to push each one to mimir
spinillos Apr 11, 2024
cf49f74
Update tests
spinillos Apr 11, 2024
2335280
Merge branch 'main' of github.com:grafana/grizzly into create-mimir-c…
spinillos Apr 17, 2024
d5ad404
Use only mimir http way and remove cortex references
spinillos Apr 17, 2024
9abb58c
Fix lint
spinillos Apr 17, 2024
59dab5f
Fix lint
spinillos Apr 17, 2024
52ce846
Unused func
spinillos Apr 17, 2024
fa005e8
Merge branch 'main' of github.com:grafana/grizzly into create-mimir-c…
spinillos Apr 18, 2024
270867f
Remove unused function
spinillos Apr 18, 2024
c3ccd8f
Lint
spinillos Apr 18, 2024
35e4e03
Add comment for grizzly env variable and add retro compatibility for …
spinillos Apr 22, 2024
ec98e66
Merge branch 'main' of github.com:grafana/grizzly into create-mimir-c…
spinillos Apr 22, 2024
9675c1d
fix lint
spinillos Apr 22, 2024
6e091e3
Fix lint
spinillos Apr 22, 2024
e73da77
Merge fix and small renaming
spinillos Apr 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions docs/content/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,31 @@ establish authentication credentials.

```sh
grr config set grafana.url http://localhost:3000 # URL for the root of your Grafana instance
grr config set grafana.user admin # Optional: Username if using basic auth
grr config set grafana.user admin # (Optional) Username if using basic auth
grr config set grafana.token abcd12345 # Service account token (or basic auth password)
```

## Grafana Cloud Prometheus
To interact with Grafana Cloud Prometheus (aka Mimir), use these settings:

```sh
grr config set mimir.address https://mimir.example.com # URL for Grafana Cloud Prometheus instance
grr config set mimir.tenant-id 1234567 # Tenant ID for your Grafana Cloud Prometheus account
grr config set mimir.api-key abcdef12345 # Authentication token
grr config set mimir.address https://mimir.example.com # URL for Mimir instance or Grafana Cloud Prometheus instance
grr config set mimir.tenant-id myTenant # Tenant ID for your Grafana Cloud Prometheus account
grr config set mimir.api-key abcdef12345 # Authentication token (if you are using Grafana Cloud)
```

Note, this will also work with other Cortex installations, alongside Grafana Cloud Prometheus/Mimir.
**Notes**
* Be sure to set `api-key` when you need to interact with Grafana Cloud.

## Grafana Synthetic Monitoring
To interact with Grafana Synthetic Monitoring, you must configure the below settings:

```sh
grr config set synthetic-monitoring.token abcdef123456 # API key (must have MetricsPublisher permissions)
grr config set synthetic-monitoring.stack-id # Grafana stack ID
grr config set synthetic-monitoring.metrics-id # Metrics instance ID
grr config set synthetic-monitoring.logs-id # Logs instance ID
grr config set synthetic-monitoring.url # Synthetic Monitoring instance URL
grr config set synthetic-monitoring.stack-id 123 # Grafana stack ID
grr config set synthetic-monitoring.metrics-id 123 # Metrics instance ID
grr config set synthetic-monitoring.logs-id 123 # Logs instance ID
grr config set synthetic-monitoring.url https://synthetic-monitoring-api.grafana.net # Synthetic Monitoring instance URL
```
Your stack ID is the number at the end of the url when you view your Grafana instance details, ie. `grafana.com/orgs/myorg/stacks/123456` would be `123456`. Your metrics and logs ID's are the `User` when you view your Prometheus or Loki instance details in Grafana Cloud.
You can find your instance URL under your Synthetic Monitoring configuration.
Expand Down Expand Up @@ -149,13 +150,13 @@ docs](https://grafana.com/docs/grafana/latest/http_api/auth/) for more info.
## Grafana Cloud Prometheus
To interact with Grafana Cloud Prometheus, you must have these environment variables set:

| Name | Description | Required |
|--------------------|-----------------------------------------------------|----------|
| `CORTEX_ADDRESS` | URL for Grafana Cloud Prometheus instance | true |
| `CORTEX_TENANT_ID` | Tenant ID for your Grafana Cloud Prometheus account | true |
| `CORTEX_API_KEY` | Authentication token/api key | true |
| Name | Description | Required |
|-------------------|-----------------------------------------------------|----------|
| `MIMIR_ADDRESS` | URL for Grafana Cloud Prometheus instance | true |
| `MIMIR_TENANT_ID` | Tenant ID for your Grafana Cloud Prometheus account | true |
| `MIMIR_API_KEY` | Authentication token/api key | false |

Note, this will also work with other Cortex installations, alongside Grafana Cloud Prometheus.
Note, this will also work with other Mimir installations, alongside Grafana Cloud Prometheus.

## Grafana Synthetic Monitoring
To interact with Grafana Synthetic Monitoring, you must have these environment variable set:
Expand Down
2 changes: 1 addition & 1 deletion docs/content/prometheus.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ local disk. As Grizzly focuses on systems that can be managed via HTTP APIs,
Grizzly cannot (currently) work with Prometheus itself.

Various hosted Prometheus installations, such as Grafana Cloud Prometheus
are supported, as are systems running Cortex.
are supported, as are systems running Mimir.

## Configuring Prometheus
Prometheus alert and recording rules are both created using the same `kind`:
Expand Down
29 changes: 24 additions & 5 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,37 @@ func override(v *viper.Viper) {
"synthetic-monitoring.metrics-id": "GRAFANA_SM_METRICS_ID",
"synthetic-monitoring.url": "GRAFANA_SM_URL",

"mimir.address": "CORTEX_ADDRESS",
"mimir.tenant-id": "CORTEX_TENANT_ID",
spinillos marked this conversation as resolved.
Show resolved Hide resolved
"mimir.api-key": "CORTEX_API_KEY",
"mimir.address": "MIMIR_ADDRESS",
"mimir.tenant-id": "MIMIR_TENANT_ID",
"mimir.api-key": "MIMIR_API_KEY",
}

// To keep retro compatibility
legacyBindings := map[string]string{
"MIMIR_ADDRESS": "CORTEX_ADDRESS",
"MIMIR_TENANT_ID": "CORTEX_TENANT_ID",
"MIMIR_API_KEY": "CORTEX_API_KEY",
}

for key, env := range bindings {
val := os.Getenv(env)
if val != "" {
if val := getVal(env, legacyBindings); val != "" {
v.Set(key, val)
}
}
}

func getVal(env string, alternativeMap map[string]string) string {
if val := os.Getenv(env); val != "" {
return val
}

if alternativeMap[env] != "" {
return getVal(alternativeMap[env], nil)
}

return ""
}

func Read() error {
err := viper.ReadInConfig()
if err != nil {
Expand Down
130 changes: 130 additions & 0 deletions pkg/mimir/client/http_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package client

import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"

"github.com/grafana/grizzly/pkg/config"
"github.com/grafana/grizzly/pkg/mimir/models"
"gopkg.in/yaml.v3"
)

var loadRulesEndpoint = "%s/prometheus/config/v1/rules/%s"
var listRulesEndpoint = "%s/prometheus/api/v1/rules"

type ListGroupResponse struct {
Status string `yaml:"status"`
Data struct {
DataGroups []DataGroups `yaml:"groups"`
} `yaml:"data"`
}

type DataGroups struct {
Name string `yaml:"name"`
File string `yaml:"file"`
Rules []interface{} `yaml:"rules"`
}

type Client struct {
config *config.MimirConfig
}

func NewHTTPClient(config *config.MimirConfig) Mimir {
return &Client{config: config}
}

func (c *Client) ListRules() (map[string][]models.PrometheusRuleGroup, error) {
url := fmt.Sprintf(listRulesEndpoint, c.config.Address)
res, err := c.doRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}

var response ListGroupResponse
if err := yaml.Unmarshal(res, &response); err != nil {
return nil, err
}

groups := make(map[string][]models.PrometheusRuleGroup)
for _, g := range response.Data.DataGroups {
groups[g.File] = append(groups[g.File], models.PrometheusRuleGroup{
Name: g.Name,
Rules: g.Rules,
})
}

return groups, nil
}

func (c *Client) CreateRules(resource models.PrometheusRuleGrouping) error {
url := fmt.Sprintf(loadRulesEndpoint, c.config.Address, resource.Namespace)
for _, group := range resource.Groups {
out, err := yaml.Marshal(group)
if err != nil {
return fmt.Errorf("cannot marshall groups: %s", err)
}

if _, err = c.doRequest(http.MethodPost, url, out); err != nil {
return fmt.Errorf("error found creating rule group: %s", group.Name)
}
}

return nil
}

func (c *Client) doRequest(method string, url string, body []byte) ([]byte, error) {
if c.config.TenantID == "" {
return nil, errors.New("missing tenant-id")
}
req, err := http.NewRequest(method, url, bytes.NewReader(body))
if err != nil {
return nil, err
}

req.Header.Set("Content-Type", "application/yaml")
if c.config.APIKey != "" {
req.SetBasicAuth(c.config.TenantID, c.config.APIKey)
} else {
req.Header.Set("X-Scope-OrgID", c.config.TenantID)
}

client, err := createHTTPClient()
if err != nil {
return nil, err
}

res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request to load rules failed: %s", err)
}

if res.StatusCode >= 300 {
return nil, fmt.Errorf("error loading rules: %d", res.StatusCode)
}

b, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("cannot read response body: %s", err)
}

return b, nil
}

func createHTTPClient() (*http.Client, error) {
timeout := 10 * time.Second
// TODO: Move this configuration to the global configuration
if timeoutStr := os.Getenv("GRIZZLY_HTTP_TIMEOUT"); timeoutStr != "" {
spinillos marked this conversation as resolved.
Show resolved Hide resolved
timeoutSeconds, err := strconv.Atoi(timeoutStr)
if err != nil {
return nil, err
}
timeout = time.Duration(timeoutSeconds) * time.Second
}
return &http.Client{Timeout: timeout}, nil
}
10 changes: 10 additions & 0 deletions pkg/mimir/client/mimir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package client

import (
"github.com/grafana/grizzly/pkg/mimir/models"
)

type Mimir interface {
ListRules() (map[string][]models.PrometheusRuleGroup, error)
CreateRules(resource models.PrometheusRuleGrouping) error
}
39 changes: 0 additions & 39 deletions pkg/mimir/cortex_tool.go

This file was deleted.

13 changes: 13 additions & 0 deletions pkg/mimir/models/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package models

// PrometheusRuleGroup encapsulates a list of rules
type PrometheusRuleGroup struct {
Name string `yaml:"name"`
Rules []interface{} `yaml:"rules"`
}

// PrometheusRuleGrouping encapsulates a set of named rule groups
type PrometheusRuleGrouping struct {
Namespace string `yaml:"namespace"`
Groups []PrometheusRuleGroup `yaml:"groups"`
}
18 changes: 9 additions & 9 deletions pkg/mimir/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,32 @@ package mimir

import (
"fmt"
"os/exec"
"path/filepath"

"github.com/grafana/grizzly/pkg/config"
"github.com/grafana/grizzly/pkg/grizzly"
"github.com/grafana/grizzly/pkg/mimir/client"
)

// Provider is a grizzly.Provider implementation for Grafana.
type Provider struct {
config *config.MimirConfig
config *config.MimirConfig
clientTool client.Mimir
}

// NewProvider instantiates a new Provider.
func NewProvider(config *config.MimirConfig) (*Provider, error) {
if _, err := exec.LookPath("cortextool"); err != nil {
return nil, err
}
clientTool := client.NewHTTPClient(config)
if config.Address == "" {
return nil, fmt.Errorf("mimir address is not set")
}
if config.APIKey == "" {
return nil, fmt.Errorf("mimir api key is not set")
if config.TenantID == "" {
return nil, fmt.Errorf("mimir tenant id is not set")
}

return &Provider{
config: config,
config: config,
clientTool: clientTool,
}, nil
}

Expand All @@ -53,6 +53,6 @@ func (p *Provider) APIVersion() string {
// GetHandlers identifies the handlers for the Grafana provider
func (p *Provider) GetHandlers() []grizzly.Handler {
return []grizzly.Handler{
NewRuleHandler(p),
NewRuleHandler(p, p.clientTool),
}
}
Loading
Loading