diff --git a/README.md b/README.md index b1edf6ad..e4e9d008 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ Current Entities supported (See official docs for more details) - Teams - Users - Library Elements - - Alert rules - - Contact points + - Alerting: + - Contact points (master branch only) Grafana Enterprise Only feature - Connection Permissions diff --git a/cli/backup/alerting_contacts.go b/cli/backup/alerting_contacts.go index 353daee4..7887f982 100644 --- a/cli/backup/alerting_contacts.go +++ b/cli/backup/alerting_contacts.go @@ -8,7 +8,6 @@ import ( "github.com/bep/simplecobra" "github.com/esnet/gdg/cli/support" - "github.com/esnet/gdg/internal/service" "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" ) @@ -49,7 +48,7 @@ func newListContactPointsCmd() simplecobra.Commander { cmd.Aliases = []string{"l"} }, RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { - rootCmd.TableObj.AppendHeader(table.Row{"uid", "name", "slug", "type", "provenance", "settings"}) + rootCmd.TableObj.AppendHeader(table.Row{"uid", "name", "type", "settings"}) contactPoints, err := rootCmd.GrafanaSvc().ListContactPoints() slog.Info("Listing contact points for context", slog.String("Organization", GetOrganizationName()), @@ -71,7 +70,7 @@ func newListContactPointsCmd() simplecobra.Commander { if link.Type != nil { typeVal = *link.Type } - rootCmd.TableObj.AppendRow(table.Row{link.UID, link.Name, service.GetSlug(link.Name), typeVal, link.Provenance, string(rawBytes)}) + rootCmd.TableObj.AppendRow(table.Row{link.UID, link.Name, typeVal, string(rawBytes)}) } rootCmd.Render(cd.CobraCommand, contactPoints) } diff --git a/cli/backup/dashboard.go b/cli/backup/dashboard.go index fa738ea6..fae921b6 100644 --- a/cli/backup/dashboard.go +++ b/cli/backup/dashboard.go @@ -180,11 +180,7 @@ func newListDashboardsCmd() simplecobra.Commander { }, RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { cfg := config.Config().GetDefaultGrafanaConfig() - if cfg.GetDashboardSettings().NestedFolders { - rootCmd.TableObj.AppendHeader(table.Row{"id", "Title", "Slug", "Folder", "NestedPath", "UID", "Tags", "URL"}) - } else { - rootCmd.TableObj.AppendHeader(table.Row{"id", "Title", "Slug", "Folder", "UID", "Tags", "URL"}) - } + rootCmd.TableObj.AppendHeader(table.Row{"id", "Title", "Slug", "Folder", "NestedPath", "UID", "Tags", "URL"}) filters := service.NewDashboardFilter(parseDashboardGlobalFlags(cd.CobraCommand)...) boards := rootCmd.GrafanaSvc().ListDashboards(filters) diff --git a/cli/backup/folders.go b/cli/backup/folders.go index b63ec3de..44eb983e 100644 --- a/cli/backup/folders.go +++ b/cli/backup/folders.go @@ -85,11 +85,7 @@ func newFolderListCmd() simplecobra.Commander { RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { slog.Info("Listing Folders for context", "context", config.Config().GetGDGConfig().GetContext()) cfg := config.Config().GetDefaultGrafanaConfig() - if cfg.GetDashboardSettings().NestedFolders { - rootCmd.TableObj.AppendHeader(table.Row{"uid", "title", "nestedPath"}) - } else { - rootCmd.TableObj.AppendHeader(table.Row{"uid", "title"}) - } + rootCmd.TableObj.AppendHeader(table.Row{"uid", "title", "nestedPath"}) folders := rootCmd.GrafanaSvc().ListFolders(getFolderFilter()) if len(folders) == 0 { diff --git a/cli/test/backup/alerting_contactpoints_test.go b/cli/test/backup/alerting_contactpoints_test.go index 1734f962..06a2a084 100644 --- a/cli/test/backup/alerting_contactpoints_test.go +++ b/cli/test/backup/alerting_contactpoints_test.go @@ -169,7 +169,8 @@ func TestListContactPoints(t *testing.T) { name: "ListingTest", validateFn: func(t *testing.T, output string) { assert.True(t, strings.Contains(output, "WRN GDG does not manage the 'email receiver' entity.")) - assert.True(t, strings.Contains(output, "PROVENANCE")) + assert.True(t, strings.Contains(output, "discordUid")) + assert.True(t, strings.Contains(output, "slackUid")) assert.True(t, strings.Contains(output, "Discord")) assert.True(t, strings.Contains(output, "Slack")) // validate Type @@ -180,11 +181,13 @@ func TestListContactPoints(t *testing.T) { testSvc.EXPECT().InitOrganizations().Return() resp := []*models.EmbeddedContactPoint{ { + UID: "discordUid", Name: "Discord", Type: ptr.Of("discordType"), Settings: map[string]any{"token": "secret", "someValue": "result"}, }, { + UID: "slackUid", Name: "Slack", Type: ptr.Of("slackType"), Settings: map[string]any{"token": "secret", "slack": "rocks"}, diff --git a/config/importer-example.yml b/config/importer-example.yml index 669f1487..52c7d021 100644 --- a/config/importer-example.yml +++ b/config/importer-example.yml @@ -58,7 +58,7 @@ contexts: password: admin dashboard_settings: ignore_filters: false # When set to true all Watched filtered folders will be ignored and ALL folders will be acted on - nested_folders: true # If true, the dashboard-download will create the proper nesting of (sub)folders - WARN: not (yet) compatible with the upload-feature!!! + nested_folders: true watched: - General - Other diff --git a/docker-compose.yml b/docker-compose.yml index 9bdd57e5..7275c594 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.8' services: minio: image: bitnami/minio:latest diff --git a/internal/service/common_test.go b/internal/service/common_test.go index 2aea26ec..df263baf 100644 --- a/internal/service/common_test.go +++ b/internal/service/common_test.go @@ -44,8 +44,8 @@ func TestSlug(t *testing.T) { func TestUserPath(t *testing.T) { fixEnvironment(t) - path := BuildResourceFolder("", config.UserResource) - assert.Equal(t, "test/data/users/", path) + userPath := BuildResourceFolder("", config.UserResource) + assert.Equal(t, "test/data/users/", userPath) } func TestBuildDashboardPath(t *testing.T) { diff --git a/internal/service/dashboards.go b/internal/service/dashboards.go index b0108b70..bf07c184 100644 --- a/internal/service/dashboards.go +++ b/internal/service/dashboards.go @@ -91,41 +91,12 @@ func NewDashboardFilter(entries ...string) filters.Filter { } // nestedFoldersSanityCheck returns an error if minimum Version does not match, if grafana settings has nested folders enabled -// but configuration does not match +// but minimum version is not met. func (s *DashNGoImpl) nestedFoldersSanityCheck() error { if s.grafanaConf.GetDashboardSettings().NestedFolders && !tools.ValidateMinimumVersion(minimumNestedDashboardVersion, s) { log.Fatalf("Minimum version required for nested folders is %s", minimumNestedDashboardVersion) } - handleError := func() error { - slog.Warn("Either you don't have admin access or we're unable to retrieve grafana settings. " + - "Unable to perform a sanity check on the grafana settings. Continuing with best effort") - return nil - } - displayGdgMisconfigured := func() error { - slog.Warn("You have nested folders configured for grafana, but your gdg settings do not match. Some Dashboards backup and restore may not work as intended.") - return nil - } - if s.grafanaConf.IsGrafanaAdmin() { - payload, err := s.GetAdminClient().Admin.AdminGetSettings() - if err != nil { - return handleError() - } - preferences := payload.GetPayload() - if val, ok := preferences["feature_toggles"]["enable"]; ok { - if strings.Contains(val, "nestedFolders") && !s.grafanaConf.GetDashboardSettings().NestedFolders /* grafana has nested folders but GDG is configured to ignore those */ { - slog.Warn("You have nested folders enabled on your grafana instance and the setting disabled in your settings. Some Dashboards backup and restore may not work as intended.") - return nil - } else if !strings.Contains(val, "nestedFolders") && s.grafanaConf.GetDashboardSettings().NestedFolders /* and grafana has nested folder disabled */ { - return displayGdgMisconfigured() - } else { - return nil - } - } else if s.grafanaConf.GetDashboardSettings().NestedFolders { //"feature_toggles" no set, which currently means nested_folders is disabled. - return displayGdgMisconfigured() - } - } - - return handleError() + return nil } func (s *DashNGoImpl) LintDashboards(req types.LintRequest) []string { diff --git a/test/dashboard_integration_test.go b/test/dashboard_integration_test.go index 2b7ec8eb..2d8cc95f 100644 --- a/test/dashboard_integration_test.go +++ b/test/dashboard_integration_test.go @@ -57,7 +57,7 @@ func TestDashboardNestedFolderCRUD(t *testing.T) { var generalBoard *models.Hit var nestedFolder *models.Hit for ndx, board := range boards { - slog.Info(board.Slug) + if board.Slug == "rabbitmq-overview" { generalBoard = boards[ndx] } diff --git a/test/data/org_main-org/alerting/contacts.json b/test/data/org_main-org/alerting/contacts.json index 2bc299a4..03da8c6a 100644 --- a/test/data/org_main-org/alerting/contacts.json +++ b/test/data/org_main-org/alerting/contacts.json @@ -12,5 +12,17 @@ "uid": "fdxmqkyb5gl4xb" } ] + }, + { + "name": "email receiver", + "orgId": 1, + "receivers": [ + { + "settings": { + "addresses": "\u003cexample@email.com\u003e" + }, + "type": "email" + } + ] } ] \ No newline at end of file diff --git a/website/content/docs/gdg/configuration.md b/website/content/docs/gdg/configuration.md index 7d59b790..1a253bc4 100644 --- a/website/content/docs/gdg/configuration.md +++ b/website/content/docs/gdg/configuration.md @@ -268,9 +268,33 @@ Watched folders under grafana is a white list of folders that are being managed ## Dashboards -By default ONLY the General folder is inspected. You may override this behavior by +By default, ONLY the General folder is inspected. You may override this behavior by setting a list of watched folder +example: +```yaml + watched: + - Folder1 + - Folder2 + - Prod*[a-zA-Z0-9]+$ +``` +You can use a combination of folder names and regex patterns. If you need to monitor a different set for various +organization you can additionally set the watched_folders_override as detailed below. + +If you do not watch to set an approved watch list, then you can configure gdg to ignore all filters. +```yaml + dashboard_settings: + ignore_filters: true +``` + +Starting with V11 of grafana, nested_folders are supported. To enable that feature use: + +```yaml + dashboard_settings: + nested_folders: true +``` + +This will likely be become a default behavior once grafana 12 is released. ## Organization The organization is set for a given context via the `orgnization_name`. If the org is not set, gdg will fallback on the default value that grafana starts out with `Main Org.` diff --git a/website/content/docs/releases/gdg_0.7.md b/website/content/docs/releases/gdg_0.7.md index 23361ad0..3a80c949 100644 --- a/website/content/docs/releases/gdg_0.7.md +++ b/website/content/docs/releases/gdg_0.7.md @@ -13,11 +13,18 @@ toc: true **Release Date: TBD** ### Breaking Changes + - [#318](https://github.com/esnet/gdg/pull/318) Removed the default config fall back. For backup and tools functionality +a valid configuration file is now required. A new cli parameter is introduced: `default-config` which will print an example configuration +to stdout. +### Changes + - [#319](https://github.com/esnet/gdg/pull/319) Remove the requirement for GF_FEATURE_TOGGLES_ENABLE for nested folder as it was incorrectly required in 0.7.1 + - [#302](https://github.com/esnet/gdg/pull/302) Adding Contact Points support. + - [#303](https://github.com/esnet/gdg/pull/303) Cleaning up Permission based listings. + - [#274](https://github.com/esnet/gdg/pull/274) Adding Dashboard Permissions, enterprise feature. ### Changes - [#274](https://github.com/esnet/gdg/pull/274) Adding Dashboard Permissions, enterprise feature. - - ## Release Notes for v0.7.1 **Release Date: 09/11/2024** diff --git a/website/content/docs/usage_guide/backup_guide.md b/website/content/docs/usage_guide/backup_guide.md index 3743fb7c..ece42299 100644 --- a/website/content/docs/usage_guide/backup_guide.md +++ b/website/content/docs/usage_guide/backup_guide.md @@ -5,6 +5,22 @@ weight: 16 Every namespace supporting CRUD operations has the functions: list, download, upload, clear operating on only the monitored folders. +### Alerting + +Alerting is made up of several type of entities: ContactPoints, Alert Rules, Notification Policy and finally Templates. +Currently only Contact Points is supported. + +#### Contact Points + +{{< callout note >}} Grafana has a contact point type named 'grafana-default-email' that has an inconsistent behavior. +Unless it has been modified, GDG will ignore it on listing, download and upload. If it has been modified, it will not be able to clear it due to grafana restriction {{< /callout >}} + +```sh +./bin/gdg backup alerting contactpoints list -- Lists all current contact points +./bin/gdg backup alerting contactpoints download -- Download all known contact points +./bin/gdg backup alerting contactpoints upload -- Upload all contact points +./bin/gdg backup alerting contactpoints clear -- Clear all contact points +``` ### Connections