Skip to content

Commit

Permalink
Merge branch 'main' of github.com:grafana/grizzly into mimir-integrat…
Browse files Browse the repository at this point in the history
…ion-tests
  • Loading branch information
spinillos committed Apr 29, 2024
2 parents 0140c38 + 0022dcf commit 9d67cb7
Show file tree
Hide file tree
Showing 44 changed files with 479 additions and 288 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
*.orig
*.swp
.idea

/resources
24 changes: 24 additions & 0 deletions .golangci.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[linters]
disable-all = true
enable = [
"dogsled",
"errcheck",
"exportloopref",
"goconst",
"gocritic",
"gocyclo",
"goimports",
"goprintffuncname",
"gosimple",
"govet",
"ineffassign",
"misspell",
"nakedret",
"rowserrcheck",
"staticcheck",
"stylecheck",
"typecheck",
"unconvert",
"unused",
"whitespace"
]
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ GOX := $(BIN_DIR)/gox
DOCKER_COMPOSE := docker compose -f ./test-docker-compose/docker-compose.yml

lint:
test -z $$(gofmt -s -l cmd/ pkg/)
go vet ./...
docker run \
--rm \
--volume "$(shell pwd):/src" \
--workdir "/src" \
golangci/golangci-lint:v1.57 golangci-lint run ./... -v

run-test-image-locally: test-clean
$(DOCKER_COMPOSE) up --force-recreate --detach --remove-orphans --wait
Expand Down
6 changes: 4 additions & 2 deletions cmd/grr/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/grafana/grizzly/pkg/grizzly/notifier"
"github.com/hashicorp/go-multierror"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
terminal "golang.org/x/term"
)

const generalFolderUID = "general"
Expand Down Expand Up @@ -508,7 +508,9 @@ func configCmd() *cli.Command {
func initialiseCmd(cmd *cli.Command, opts *Opts) *cli.Command {
// Keep the old flags for backwards compatibility
cmd.Flags().BoolVarP(&opts.Directory, "directory", "d", false, "treat resource path as a directory")
cmd.Flags().MarkDeprecated("directory", "now it is inferred from the operating system")
if err := cmd.Flags().MarkDeprecated("directory", "now it is inferred from the operating system"); err != nil {
log.Fatal(err)
}

cmd.Flags().StringSliceVarP(&opts.Targets, "target", "t", nil, "resources to target")
cmd.Flags().StringSliceVarP(&opts.JsonnetPaths, "jpath", "J", getDefaultJsonnetFolders(), "Specify an additional library search dir (right-most wins)")
Expand Down
55 changes: 30 additions & 25 deletions docs/content/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,34 @@ 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.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.

## Configuring Targets
Grizzly supports a number of resource types (`grr providers` will list those supported). Often, however, we do not
Expand Down Expand Up @@ -135,37 +138,39 @@ In some circumstances (e.g. when used within automated pipelines) it makes sense
with environment variables as opposed to contexts. Environment variables, when set, take precedence over
Grizzly contexts as described above. Below are the variables that can be used for this.

| Name | Description | Required | Default |
| --- | --- | --- | --- |
| `GRAFANA_URL` | Fully qualified domain name of your Grafana instance. | true | - |
| `GRAFANA_USER` | Basic auth username if applicable. | false | `api_key` |
| `GRAFANA_TOKEN` | Basic auth password or API token. | false | - |
| Name | Description | Required | Default |
|-----------------|-------------------------------------------------------|----------|-----------|
| `GRAFANA_URL` | Fully qualified domain name of your Grafana instance. | true | - |
| `GRAFANA_USER` | Basic auth username if applicable. | false | `api_key` |
| `GRAFANA_TOKEN` | Basic auth password or API token. | false | - |

See Grafana's [Authentication API
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:

| Name | Description | Required |
| --- | --- | --- |
| `GRAFANA_SM_TOKEN` | Authentication token/api key (must have MetricsPublisher permissions) | true |
| `GRAFANA_SM_STACK_ID` | Grafana instance/stack ID | true |
| `GRAFANA_SM_LOGS_ID` | Logs instance ID | true |
| `GRAFANA_SM_METRICS_ID` | Metrics instance ID | true |
| Name | Description | Required |
|-------------------------|-----------------------------------------------------------------------|----------|
| `GRAFANA_SM_TOKEN` | Authentication token/api key (must have MetricsPublisher permissions) | true |
| `GRAFANA_SM_STACK_ID` | Grafana instance/stack ID | true |
| `GRAFANA_SM_LOGS_ID` | Logs instance ID | true |
| `GRAFANA_SM_METRICS_ID` | Metrics instance ID | true |
| `GRAFANA_SM_URL` | Synthetic Monitoring instance URL | true |

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.

# Grizzly configuration file
To get the path of the config file:
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ require (
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
2 changes: 1 addition & 1 deletion integration/folder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func TestFolders(t *testing.T) {

t.Run("get remote folder - not found", func(t *testing.T) {
_, err := handler.GetByUID("dummy")
require.ErrorContains(t, err, "Couldn't fetch folder 'dummy' from remote: not found")
require.ErrorContains(t, err, "couldn't fetch folder 'dummy' from remote: not found")
})

t.Run("get folders list", func(t *testing.T) {
Expand Down
1 change: 0 additions & 1 deletion integration/pull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ func TestPull(t *testing.T) {
assert.FileExists(t, filepath.Join(pullDir, "dashboards", "abcdefghi", "dashboard-ReciqtgGk.yaml"))
assert.DirExists(t, filepath.Join(pullDir, "datasources"))
assert.DirExists(t, filepath.Join(pullDir, "folders"))

},
})
})
Expand Down
66 changes: 47 additions & 19 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import (
)

const (
API_VERSION = "v1alpha1"
CURRENT_CONTEXT = "current-context"
CurrentContextSetting = "current-context"
)

func Initialise() {
Expand All @@ -38,19 +37,39 @@ func override(v *viper.Viper) {
"synthetic-monitoring.stack-id": "GRAFANA_SM_STACK_ID",
"synthetic-monitoring.logs-id": "GRAFANA_SM_LOGS_ID",
"synthetic-monitoring.metrics-id": "GRAFANA_SM_METRICS_ID",
"synthetic-monitoring.url": "GRAFANA_SM_URL",

"mimir.address": "CORTEX_ADDRESS",
"mimir.tenant-id": "CORTEX_TENANT_ID",
"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 All @@ -70,7 +89,7 @@ func Mock(values map[string]interface{}) {
}

func Import() error {
name := viper.GetString(CURRENT_CONTEXT)
name := viper.GetString(CurrentContextSetting)
if name == "" {
NewConfig()
return Import()
Expand All @@ -90,14 +109,16 @@ func Import() error {

func NewConfig() {
viper.Set("apiVersion", "v1alpha1")
viper.Set(CURRENT_CONTEXT, "default")
viper.Set(CurrentContextSetting, "default")
viper.Set("contexts.default.name", "default")
}

func GetContexts() error {
contexts := map[string]interface{}{}
currentContext := viper.GetString(CURRENT_CONTEXT)
viper.UnmarshalKey("contexts", &contexts)
currentContext := viper.GetString(CurrentContextSetting)
if err := viper.UnmarshalKey("contexts", &contexts); err != nil {
return err
}
keys := make([]string, 0, len(contexts))
for k := range contexts {
keys = append(keys, k)
Expand All @@ -115,18 +136,20 @@ func GetContexts() error {

func UseContext(context string) error {
contexts := map[string]interface{}{}
viper.UnmarshalKey("contexts", &contexts)
if err := viper.UnmarshalKey("contexts", &contexts); err != nil {
return err
}
for k := range contexts {
if k == context {
viper.Set(CURRENT_CONTEXT, context)
viper.Set(CurrentContextSetting, context)
return Write()
}
}
return fmt.Errorf("context %s not found", context)
}

func CurrentContext() (*Context, error) {
name := viper.GetString(CURRENT_CONTEXT)
name := viper.GetString(CurrentContextSetting)
if name == "" {
NewConfig()
return CurrentContext()
Expand All @@ -138,7 +161,9 @@ func CurrentContext() (*Context, error) {
}
override(ctx)
var context Context
ctx.Unmarshal(&context)
if err := ctx.Unmarshal(&context); err != nil {
return nil, err
}
context.Name = name
return &context, nil
}
Expand All @@ -147,20 +172,23 @@ var acceptableKeys = map[string]string{
"grafana.url": "string",
"grafana.token": "string",
"grafana.user": "string",
"grafana.insecure-skip-verify": "bool",
"grafana.tls-host": "string",
"mimir.address": "string",
"mimir.tenant-id": "string",
"mimir.api-key": "string",
"synthetic-monitoring.token": "string",
"synthetic-monitoring.stack-id": "int",
"synthetic-monitoring.metrics-id": "int",
"synthetic-monitoring.logs-id": "int",
"synthetic-monitoring.url": "string",
"targets": "[]string",
"output-format": "string",
"only-spec": "bool",
}

func Get(path, outputFormat string) (string, error) {
ctx := viper.GetString(CURRENT_CONTEXT)
ctx := viper.GetString(CurrentContextSetting)
fullPath := fmt.Sprintf("contexts.%s", ctx)
if path != "" {
fullPath = fmt.Sprintf("%s.%s", fullPath, path)
Expand All @@ -183,7 +211,7 @@ func Get(path, outputFormat string) (string, error) {
func Set(path string, value string) error {
for key, typ := range acceptableKeys {
if path == key {
ctx := viper.GetString(CURRENT_CONTEXT)
ctx := viper.GetString(CurrentContextSetting)
fullPath := fmt.Sprintf("contexts.%s.%s", ctx, path)
var val any
switch typ {
Expand Down Expand Up @@ -222,7 +250,7 @@ func Unset(path string) error {
return fmt.Errorf("%s is not a valid path", path)
}

ctx := viper.GetString(CURRENT_CONTEXT)
ctx := viper.GetString(CurrentContextSetting)
fullPath := fmt.Sprintf("contexts.%s.%s", ctx, path)

if !viper.InConfig(fullPath) {
Expand Down Expand Up @@ -253,7 +281,7 @@ func deleteValue(settings map[string]any, deleteKey string, iteratorKeys ...stri
}

func CreateContext(name string) error {
viper.Set(CURRENT_CONTEXT, name)
viper.Set(CurrentContextSetting, name)
viper.Set(fmt.Sprintf("contexts.%s.name", name), name)
return Write()
}
Expand Down
Loading

0 comments on commit 9d67cb7

Please sign in to comment.