From 7886b3d7ae56fd477a1e01fc63f331cc86efdfbd Mon Sep 17 00:00:00 2001 From: Samir Faci Date: Thu, 12 Sep 2024 10:18:30 -0400 Subject: [PATCH] Add Alerting Contact Points --- cli/backup/alerting.go | 34 +++ cli/backup/alerting_contacts.go | 158 +++++++++++ cli/backup/backup.go | 1 + cli/commandeer.go | 4 +- .../backup/alerting_contactpoints_test.go | 92 ++++++ cli/test/{ => backup}/conections_test.go | 6 +- cli/test/backup/main_test.go | 18 ++ cli/test/{ => tools}/devel_test.go | 12 +- cli/test/tools/main_test.go | 18 ++ cli/test/version_test.go | 9 +- internal/config/resource_type.go | 2 + internal/service/alerting.go | 161 +++++++++++ internal/service/contracts.go | 8 + internal/service/dashboards.go | 8 +- internal/service/libraryelements.go | 5 +- internal/service/mocks/AlertingApi.go | 261 ++++++++++++++++++ internal/service/mocks/AuthenticationApi.go | 2 +- .../service/mocks/ConnectionPermissions.go | 2 +- internal/service/mocks/ConnectionsApi.go | 2 +- .../service/mocks/DashboardPermissionsApi.go | 2 +- internal/service/mocks/DashboardsApi.go | 2 +- internal/service/mocks/FoldersApi.go | 2 +- internal/service/mocks/GrafanaService.go | 228 ++++++++++++++- internal/service/mocks/LibraryElementsApi.go | 2 +- internal/service/mocks/LicenseApi.go | 2 +- internal/service/mocks/NewClientOpts.go | 2 +- internal/service/mocks/OrgPreferencesApi.go | 2 +- internal/service/mocks/OrganizationsApi.go | 2 +- internal/service/mocks/ServerInfoApi.go | 2 +- internal/service/mocks/ServiceAccountApi.go | 2 +- internal/service/mocks/Storage.go | 2 +- internal/service/mocks/TeamsApi.go | 2 +- internal/service/mocks/TokenApi.go | 2 +- internal/service/mocks/UsersApi.go | 2 +- internal/service/mocks/organizationCrudApi.go | 2 +- .../service/mocks/organizationToolsApi.go | 2 +- .../service/mocks/organizationUserCrudApi.go | 2 +- internal/service/serviceaccounts.go | 7 +- internal/service/teams.go | 14 - internal/service/user.go | 7 +- internal/tools/generics_tooling.go | 4 - internal/tools/ptr/ptr.go | 5 + {cli/test => pkg/test_tooling}/support.go | 8 +- test/alerting_contacts_test.go | 42 +++ test/dashboard_integration_test.go | 7 +- test/data/org_main-org/alerting/contacts.json | 16 ++ 46 files changed, 1108 insertions(+), 67 deletions(-) create mode 100644 cli/backup/alerting.go create mode 100644 cli/backup/alerting_contacts.go create mode 100644 cli/test/backup/alerting_contactpoints_test.go rename cli/test/{ => backup}/conections_test.go (92%) create mode 100644 cli/test/backup/main_test.go rename cli/test/{ => tools}/devel_test.go (75%) create mode 100644 cli/test/tools/main_test.go create mode 100644 internal/service/alerting.go create mode 100644 internal/service/mocks/AlertingApi.go create mode 100644 internal/tools/ptr/ptr.go rename {cli/test => pkg/test_tooling}/support.go (89%) create mode 100644 test/alerting_contacts_test.go create mode 100644 test/data/org_main-org/alerting/contacts.json diff --git a/cli/backup/alerting.go b/cli/backup/alerting.go new file mode 100644 index 00000000..1f4b54fd --- /dev/null +++ b/cli/backup/alerting.go @@ -0,0 +1,34 @@ +package backup + +import ( + "context" + + "github.com/bep/simplecobra" + "github.com/esnet/gdg/cli/support" + "github.com/spf13/cobra" +) + +func newAlertingCommand() simplecobra.Commander { + description := "Manage Alerting resources" + return &support.SimpleCommand{ + NameP: "alerting", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"alert"} + // connections := cmd + // connections.PersistentFlags().StringP("connection", "", "", "filter by connection slug") + }, + CommandsList: []simplecobra.Commander{ + newAlertingContactCommand(), + // newClearConnectionsCmd(), + // newUploadConnectionsCmd(), + // newDownloadConnectionsCmd(), + // newListConnectionsCmd(), + // newConnectionsPermissionCmd(), + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + return cd.CobraCommand.Help() + }, + } +} diff --git a/cli/backup/alerting_contacts.go b/cli/backup/alerting_contacts.go new file mode 100644 index 00000000..e185ad0b --- /dev/null +++ b/cli/backup/alerting_contacts.go @@ -0,0 +1,158 @@ +package backup + +import ( + "context" + "encoding/json" + "log" + "log/slog" + + "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" +) + +func newAlertingContactCommand() simplecobra.Commander { + description := "Manage Alerting ContactPoints " + return &support.SimpleCommand{ + NameP: "contactpoint", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"contact", "contacts", "contactpoints"} + }, + CommandsList: []simplecobra.Commander{ + newListContactPointsCmd(), + newClearContactPointsCmd(), + newUploadContactPointsCmd(), + newDownloadContactPointsCmd(), + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + return cd.CobraCommand.Help() + }, + } +} + +func newListContactPointsCmd() simplecobra.Commander { + description := "List all contact points for the given Organization" + return &support.SimpleCommand{ + NameP: "list", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + 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"}) + contactPoints, err := rootCmd.GrafanaSvc().ListContactPoints() + slog.Info("Listing contact points for context", + slog.String("Organization", GetOrganizationName()), + slog.String("context", GetContext())) + slog.Warn("GDG does not manage the 'email receiver' entity. It has a very odd behavior compared to all " + + "other entities. If you need to manage email contacts, please create a new contact. GDG will ignore the default contact.") + if err != nil { + log.Fatal("unable to retrieve Orgs contact points", slog.Any("err", err)) + } + if len(contactPoints) == 0 { + slog.Info("No contact points found") + } else { + for _, link := range contactPoints { + rawBytes, err := json.Marshal(link.Settings) + if err != nil { + slog.Warn("unable to marshall settings to valid JSON") + } + typeVal := "" + 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.Render(cd.CobraCommand, contactPoints) + } + return nil + }, + } +} + +func newDownloadContactPointsCmd() simplecobra.Commander { + description := "Download all contact points for the given Organization" + return &support.SimpleCommand{ + NameP: "download", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"d"} + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + file, err := rootCmd.GrafanaSvc().DownloadContactPoints() + slog.Info("Download contact points for context", + slog.String("Organization", GetOrganizationName()), + slog.String("context", GetContext())) + if err != nil { + slog.Error("unable to contact point") + } else { + slog.Info("contact points successfully downloaded", slog.Any("file", file)) + } + return nil + }, + } +} + +func newUploadContactPointsCmd() simplecobra.Commander { + description := "Upload all contact points for the given Organization" + return &support.SimpleCommand{ + NameP: "upload", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"u"} + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + newItems, err := rootCmd.GrafanaSvc().UploadContactPoints() + slog.Info("Upload contact points for context", + slog.String("Organization", GetOrganizationName()), + slog.String("context", GetContext())) + if err != nil { + slog.Error("unable to upload contact points", slog.Any("err", err)) + } else { + slog.Info("contact points successfully uploaded") + rootCmd.TableObj.AppendHeader(table.Row{"name"}) + for _, item := range newItems { + rootCmd.TableObj.AppendRow(table.Row{item}) + } + + rootCmd.Render(cd.CobraCommand, newItems) + } + return nil + }, + } +} + +func newClearContactPointsCmd() simplecobra.Commander { + description := "Clear all contact points for the given Organization" + return &support.SimpleCommand{ + NameP: "clear", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"l"} + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + removedItems, err := rootCmd.GrafanaSvc().ClearContactPoints() + slog.Info("Clear contact points for context", + slog.String("Organization", GetOrganizationName()), + slog.String("context", GetContext())) + if err != nil { + slog.Error("unable to contact point") + } else { + slog.Info("contact points successfully removed") + rootCmd.TableObj.AppendHeader(table.Row{"name"}) + for _, item := range removedItems { + rootCmd.TableObj.AppendRow(table.Row{item}) + } + } + return nil + }, + } +} diff --git a/cli/backup/backup.go b/cli/backup/backup.go index 8d1caf3d..bb5c7332 100644 --- a/cli/backup/backup.go +++ b/cli/backup/backup.go @@ -35,6 +35,7 @@ limited to clear/delete, list, download and upload. Any other functionality wil newOrganizationsCommand(), newTeamsCommand(), newUsersCommand(), + newAlertingCommand(), }, } } diff --git a/cli/commandeer.go b/cli/commandeer.go index 7f42d7b9..ac2f92a3 100644 --- a/cli/commandeer.go +++ b/cli/commandeer.go @@ -27,7 +27,9 @@ func Execute(defaultCfg string, args []string, options ...support.RootOption) er cd, err := x.Execute(context.Background(), args) if err != nil || len(args) == 0 { - _ = cd.CobraCommand.Help() + if cd != nil { + _ = cd.CobraCommand.Help() + } return err } diff --git a/cli/test/backup/alerting_contactpoints_test.go b/cli/test/backup/alerting_contactpoints_test.go new file mode 100644 index 00000000..b076dc91 --- /dev/null +++ b/cli/test/backup/alerting_contactpoints_test.go @@ -0,0 +1,92 @@ +package backup + +import ( + "io" + "strings" + "testing" + + "github.com/esnet/gdg/cli" + "github.com/esnet/gdg/cli/support" + "github.com/esnet/gdg/internal/service" + "github.com/esnet/gdg/internal/service/mocks" + "github.com/esnet/gdg/internal/tools/ptr" + "github.com/esnet/gdg/pkg/test_tooling" + "github.com/esnet/gdg/pkg/test_tooling/common" + "github.com/grafana/grafana-openapi-client-go/models" + "github.com/stretchr/testify/assert" +) + +func TestContactPoints(t *testing.T) { + getOptionMockSvc := func(testSvc *mocks.GrafanaService) func() support.RootOption { + return func() support.RootOption { + return func(response *support.RootCommand) { + response.GrafanaSvc = func() service.GrafanaService { + return testSvc + } + } + } + } + + t.Run("NoDataTest", func(t *testing.T) { + testSvc := new(mocks.GrafanaService) + testSvc.EXPECT().InitOrganizations().Return() + testSvc.EXPECT().ListContactPoints().Return(nil, nil) + optionMockSvc := getOptionMockSvc(testSvc) + r, w, cleanup := test_tooling.InterceptStdout() + defer cleanup() + + err := cli.Execute(common.DefaultTestConfig, []string{"backup", "alerting", "contactpoint", "list"}, optionMockSvc()) + assert.Nil(t, err) + defer cleanup() + w.Close() + + out, _ := io.ReadAll(r) + outStr := string(out) + assert.True(t, strings.Contains(outStr, "WRN GDG does not manage the 'email receiver' entity.")) + assert.True(t, strings.Contains(outStr, "No contact points found")) + }) + t.Run("ListingTest", func(t *testing.T) { + testSvc := new(mocks.GrafanaService) + getMockSvc := func() service.GrafanaService { + return testSvc + } + resp := []*models.EmbeddedContactPoint{ + { + Name: "Discord", + Type: ptr.Of("discordType"), + Settings: map[string]any{"token": "secret", "someValue": "result"}, + }, + { + Name: "Slack", + Type: ptr.Of("slackType"), + Settings: map[string]any{"token": "secret", "slack": "rocks"}, + }, + } + + testSvc.EXPECT().InitOrganizations().Return() + testSvc.EXPECT().ListContactPoints().Return(resp, nil) + + optionMockSvc := func() support.RootOption { + return func(response *support.RootCommand) { + response.GrafanaSvc = getMockSvc + } + } + r, w, cleanup := test_tooling.InterceptStdout() + defer cleanup() + + err := cli.Execute(common.DefaultTestConfig, []string{"backup", "alerting", "contactpoint", "list"}, optionMockSvc()) + assert.Nil(t, err) + defer cleanup() + w.Close() + + out, _ := io.ReadAll(r) + outStr := string(out) + assert.True(t, strings.Contains(outStr, "WRN GDG does not manage the 'email receiver' entity.")) + assert.True(t, strings.Contains(outStr, "PROVENANCE")) + assert.True(t, strings.Contains(outStr, "Discord")) + assert.True(t, strings.Contains(outStr, "Slack")) + // validate Type + assert.True(t, strings.Contains(outStr, "discordType")) + assert.True(t, strings.Contains(outStr, "slackType")) + }) +} diff --git a/cli/test/conections_test.go b/cli/test/backup/conections_test.go similarity index 92% rename from cli/test/conections_test.go rename to cli/test/backup/conections_test.go index b5bfb809..b0956a65 100644 --- a/cli/test/conections_test.go +++ b/cli/test/backup/conections_test.go @@ -1,10 +1,12 @@ -package test +package backup import ( "io" "strings" "testing" + "github.com/esnet/gdg/pkg/test_tooling" + "github.com/esnet/gdg/cli" "github.com/esnet/gdg/cli/support" "github.com/esnet/gdg/internal/service" @@ -38,7 +40,7 @@ func TestConnectionCommand(t *testing.T) { response.GrafanaSvc = getMockSvc } } - r, w, cleanup := InterceptStdout() + r, w, cleanup := test_tooling.InterceptStdout() err := cli.Execute(common.DefaultTestConfig, []string{"backup", "connections", "list"}, optionMockSvc()) assert.Nil(t, err) diff --git a/cli/test/backup/main_test.go b/cli/test/backup/main_test.go new file mode 100644 index 00000000..99850640 --- /dev/null +++ b/cli/test/backup/main_test.go @@ -0,0 +1,18 @@ +package backup + +import ( + "log" + "os" + "testing" + + "github.com/esnet/gdg/pkg/test_tooling/path" +) + +func TestMain(m *testing.M) { + err := path.FixTestDir("backup", "../../..") + if err != nil { + log.Fatal(err.Error()) + } + code := m.Run() + os.Exit(code) +} diff --git a/cli/test/devel_test.go b/cli/test/tools/devel_test.go similarity index 75% rename from cli/test/devel_test.go rename to cli/test/tools/devel_test.go index aef7d0a1..82010609 100644 --- a/cli/test/devel_test.go +++ b/cli/test/tools/devel_test.go @@ -1,9 +1,11 @@ -package test +package tools import ( "strings" "testing" + "github.com/esnet/gdg/pkg/test_tooling" + "github.com/esnet/gdg/cli" "github.com/esnet/gdg/cli/support" "github.com/esnet/gdg/internal/service/mocks" @@ -21,7 +23,7 @@ func TestDevelSrvInfo(t *testing.T) { err := cli.Execute(string(data), []string{"tools", "devel", "srvinfo"}, optionMockSvc()) return err } - outStr, closeReader := setupAndExecuteMockingServices(t, execMe) + outStr, closeReader := test_tooling.SetupAndExecuteMockingServices(t, execMe) defer closeReader() assert.True(t, strings.Contains(outStr, "Version=")) @@ -37,15 +39,15 @@ func TestDevelSrvCompletion(t *testing.T) { } } - outStr, closeReader := setupAndExecuteMockingServices(t, fn([]string{"tools", "devel", "completion", "fish"})) + outStr, closeReader := test_tooling.SetupAndExecuteMockingServices(t, fn([]string{"tools", "devel", "completion", "fish"})) assert.True(t, strings.Contains(outStr, "fish")) assert.True(t, strings.Contains(outStr, "__completion_prepare_completions")) closeReader() - outStr, closeReader = setupAndExecuteMockingServices(t, fn([]string{"tools", "devel", "completion", "bash"})) + outStr, closeReader = test_tooling.SetupAndExecuteMockingServices(t, fn([]string{"tools", "devel", "completion", "bash"})) assert.True(t, strings.Contains(outStr, "bash")) assert.True(t, strings.Contains(outStr, "flag_parsing_disabled")) closeReader() - outStr, closeReader = setupAndExecuteMockingServices(t, fn([]string{"tools", "devel", "completion", "zsh"})) + outStr, closeReader = test_tooling.SetupAndExecuteMockingServices(t, fn([]string{"tools", "devel", "completion", "zsh"})) assert.True(t, strings.Contains(outStr, "shellCompDirectiveKeepOrder")) closeReader() } diff --git a/cli/test/tools/main_test.go b/cli/test/tools/main_test.go new file mode 100644 index 00000000..c5233411 --- /dev/null +++ b/cli/test/tools/main_test.go @@ -0,0 +1,18 @@ +package tools + +import ( + "log" + "os" + "testing" + + "github.com/esnet/gdg/pkg/test_tooling/path" +) + +func TestMain(m *testing.M) { + err := path.FixTestDir("tools", "../../..") + if err != nil { + log.Fatal(err.Error()) + } + code := m.Run() + os.Exit(code) +} diff --git a/cli/test/version_test.go b/cli/test/version_test.go index 44590de2..a4149c2d 100644 --- a/cli/test/version_test.go +++ b/cli/test/version_test.go @@ -6,6 +6,9 @@ import ( "strings" "testing" + "github.com/esnet/gdg/pkg/test_tooling" + "github.com/esnet/gdg/pkg/test_tooling/path" + "github.com/esnet/gdg/cli" "github.com/esnet/gdg/cli/support" "github.com/esnet/gdg/internal/service" @@ -16,11 +19,12 @@ import ( ) func TestVersionCommand(t *testing.T) { + assert.NoError(t, path.FixTestDir("test", "../..")) execMe := func(mock *mocks.GrafanaService, data []byte, optionMockSvc func() support.RootOption) error { err := cli.Execute(string(data), []string{"version"}, optionMockSvc()) return err } - outStr, closeReader := setupAndExecuteMockingServices(t, execMe) + outStr, closeReader := test_tooling.SetupAndExecuteMockingServices(t, execMe) defer closeReader() assert.True(t, strings.Contains(outStr, "Build Date:")) @@ -33,6 +37,7 @@ func TestVersionCommand(t *testing.T) { } func TestVersionErrCommand(t *testing.T) { + assert.NoError(t, path.FixTestDir("test", "../..")) testSvc := new(mocks.GrafanaService) getMockSvc := func() service.GrafanaService { return testSvc @@ -45,7 +50,7 @@ func TestVersionErrCommand(t *testing.T) { } path, _ := os.Getwd() fmt.Println(path) - data, err := os.ReadFile("../../config/" + common.DefaultTestConfig) + data, err := os.ReadFile("config/" + common.DefaultTestConfig) assert.Nil(t, err) err = cli.Execute(string(data), []string{"dumb", "dumb"}, optionMockSvc()) diff --git a/internal/config/resource_type.go b/internal/config/resource_type.go index 371f17a5..34b86214 100644 --- a/internal/config/resource_type.go +++ b/internal/config/resource_type.go @@ -23,6 +23,7 @@ const ( UserResource ResourceType = "users" TemplatesResource ResourceType = "templates" SecureSecretsResource ResourceType = "secure" + AlertingResource ResourceType = "alerting" ) var orgNamespacedResource = map[ResourceType]bool{ @@ -34,6 +35,7 @@ var orgNamespacedResource = map[ResourceType]bool{ FolderResource: true, LibraryElementResource: true, TeamResource: true, + AlertingResource: true, } // isNamespaced returns true if the resource type is namespaced diff --git a/internal/service/alerting.go b/internal/service/alerting.go new file mode 100644 index 00000000..b722efee --- /dev/null +++ b/internal/service/alerting.go @@ -0,0 +1,161 @@ +package service + +import ( + "encoding/json" + "fmt" + "log" + "log/slog" + + "github.com/samber/lo" + + "github.com/esnet/gdg/internal/tools/ptr" + + "github.com/esnet/gdg/internal/config" + "github.com/grafana/grafana-openapi-client-go/client/provisioning" + "github.com/grafana/grafana-openapi-client-go/models" +) + +const ( + emailReceiver = "email receiver" +) + +func (s *DashNGoImpl) ListContactPoints() ([]*models.EmbeddedContactPoint, error) { + p := provisioning.NewGetContactpointsParams() + result, err := s.GetClient().Provisioning.GetContactpoints(p) + if err != nil { + return nil, err + } + slog.Warn("removing any contacts with no uid such as 'email receiver' that cannot be managed by GDG") + data := lo.Filter(result.GetPayload(), func(item *models.EmbeddedContactPoint, index int) bool { + return item.UID != "" + }) + + return data, nil +} + +func (s *DashNGoImpl) DownloadContactPoints() (string, error) { + var ( + dsPacked []byte + err error + ) + p := provisioning.NewGetContactpointsExportParams() + p.Download = ptr.Of(true) + p.Decrypt = ptr.Of(true) + p.Format = ptr.Of("json") + data, err := s.GetClient().Provisioning.GetContactpointsExport(p) + if err != nil { + log.Fatalf("unable to retrieve Contact Points, err: %s", err.Error()) + } + // filter default contactPoints + payload := data.GetPayload() + payload.ContactPoints = lo.Filter(payload.ContactPoints, func(item *models.ContactPointExport, index int) bool { + return item.Name != emailReceiver + }) + + dsPath := buildResourcePath("contacts", config.AlertingResource) + if dsPacked, err = json.MarshalIndent(payload.ContactPoints, "", " "); err != nil { + return "", fmt.Errorf("unable to serialize data to JSON. %w", err) + } + if err = s.storage.WriteFile(dsPath, dsPacked); err != nil { + return "", fmt.Errorf("unable to write file. %w", err) + } + + return dsPath, nil +} + +func (s *DashNGoImpl) UploadContactPoints() ([]string, error) { + var ( + err error + rawDS []byte + result []string + ) + var data []models.ContactPointExport + currentContacts, err := s.ListContactPoints() + if err != nil { + return nil, err + } + m := make(map[string]*models.EmbeddedContactPoint) + for ndx, i := range currentContacts { + m[i.UID] = currentContacts[ndx] + } + + fileLocation := buildResourcePath("contacts", config.AlertingResource) + if rawDS, err = s.storage.ReadFile(fileLocation); err != nil { + return nil, fmt.Errorf("failed to read file. file: %s, err: %w", fileLocation, err) + } + if err = json.Unmarshal(rawDS, &data); err != nil { + return nil, fmt.Errorf("failed to unmarshall file, file:%s, err: %w", fileLocation, err) + } + for _, i := range data { + for _, r := range i.Receivers { + if r.UID == "" { + slog.Info("No valid UID found for record, skipping", slog.Any("type", r.Type)) + continue + } + if _, ok := m[r.UID]; ok { + // do update + p := provisioning.NewPutContactpointParams() + p.UID = r.UID + p.Body = &models.EmbeddedContactPoint{ + DisableResolveMessage: false, + Name: i.Name, + Provenance: "", + Settings: r.Settings, + Type: ptr.Of(r.Type), + UID: r.UID, + } + _, err := s.GetClient().Provisioning.PutContactpoint(p) + if err != nil { + slog.Error("failed to update contact point", slog.Any("uid", r.UID)) + continue + } + result = append(result, i.Name) + + } else { + p := provisioning.NewPostContactpointsParams() + p.Body = &models.EmbeddedContactPoint{ + DisableResolveMessage: false, + Name: i.Name, + UID: r.UID, + Provenance: "", + Settings: r.Settings, + Type: ptr.Of(r.Type), + } + _, err = s.GetClient().Provisioning.PostContactpoints(p) + if err != nil { + slog.Error("failed to create contact point", slog.Any("uid", r.UID)) + continue + } + + result = append(result, i.Name) + } + } + } + + return result, nil +} + +func (s *DashNGoImpl) ClearContactPoints() ([]string, error) { + var ( + err error + results []string + ) + contacts, err := s.ListContactPoints() + if err != nil { + return nil, err + } + + for _, contact := range contacts { + _, err = s.GetClient().Provisioning.DeleteContactpoints(contact.UID) + if err != nil { + slog.Error("unable to delete contact point", + slog.Any("name", contact.Name), + slog.Any("uid", contact.UID), + ) + continue + } + results = append(results, contact.Name) + } + + return results, nil +} diff --git a/internal/service/contracts.go b/internal/service/contracts.go index 11fdbb8f..9959378a 100644 --- a/internal/service/contracts.go +++ b/internal/service/contracts.go @@ -21,6 +21,7 @@ type GrafanaService interface { FoldersApi LibraryElementsApi TeamsApi + AlertingApi AuthenticationApi // MetaData @@ -58,6 +59,13 @@ type DashboardsApi interface { LintDashboards(req types.LintRequest) []string } +type AlertingApi interface { + ListContactPoints() ([]*models.EmbeddedContactPoint, error) + DownloadContactPoints() (string, error) + ClearContactPoints() ([]string, error) + UploadContactPoints() ([]string, error) +} + type DashboardPermissionsApi interface { ListDashboardPermissions(filterReq filters.Filter) ([]gdgType.DashboardAndPermissions, error) DownloadDashboardPermissions(filterReq filters.Filter) ([]string, error) diff --git a/internal/service/dashboards.go b/internal/service/dashboards.go index b8f2258e..b0108b70 100644 --- a/internal/service/dashboards.go +++ b/internal/service/dashboards.go @@ -12,6 +12,8 @@ import ( "sort" "strings" + "github.com/esnet/gdg/internal/tools/ptr" + "github.com/esnet/gdg/internal/config" "github.com/esnet/gdg/internal/service/filters" "github.com/esnet/gdg/internal/service/types" @@ -279,9 +281,9 @@ func (s *DashNGoImpl) ListDashboards(filterReq filters.Filter) []*models.Hit { if tag != "" { searchParams.Tag = []string{tag} } - searchParams.Limit = tools.PtrOf(limit) - searchParams.Page = tools.PtrOf(page) - searchParams.Type = tools.PtrOf(searchTypeDashboard) + searchParams.Limit = ptr.Of(limit) + searchParams.Page = ptr.Of(page) + searchParams.Type = ptr.Of(searchTypeDashboard) pageBoardLinks, err := s.GetClient().Search.Search(searchParams) if err != nil { diff --git a/internal/service/libraryelements.go b/internal/service/libraryelements.go index 3d21821a..279dc356 100644 --- a/internal/service/libraryelements.go +++ b/internal/service/libraryelements.go @@ -8,9 +8,10 @@ import ( "log/slog" "strings" + "github.com/esnet/gdg/internal/tools/ptr" + "github.com/esnet/gdg/internal/config" "github.com/esnet/gdg/internal/service/filters" - "github.com/esnet/gdg/internal/tools" "github.com/gosimple/slug" "github.com/grafana/grafana-openapi-client-go/client/library_elements" "github.com/grafana/grafana-openapi-client-go/models" @@ -66,7 +67,7 @@ func (s *DashNGoImpl) ListLibraryElements(filter filters.Filter) []*models.Libra params := library_elements.NewGetLibraryElementsParams() params.FolderFilter = &folderList - params.Kind = tools.PtrOf(listLibraryPanels) + params.Kind = ptr.Of(listLibraryPanels) libraryElements, err := s.GetClient().LibraryElements.GetLibraryElements(params) if err != nil { log.Fatalf("Unable to list Library Elements %v", err) diff --git a/internal/service/mocks/AlertingApi.go b/internal/service/mocks/AlertingApi.go new file mode 100644 index 00000000..03fcf134 --- /dev/null +++ b/internal/service/mocks/AlertingApi.go @@ -0,0 +1,261 @@ +// Code generated by mockery v2.44.1. DO NOT EDIT. + +package mocks + +import ( + models "github.com/grafana/grafana-openapi-client-go/models" + mock "github.com/stretchr/testify/mock" +) + +// AlertingApi is an autogenerated mock type for the AlertingApi type +type AlertingApi struct { + mock.Mock +} + +type AlertingApi_Expecter struct { + mock *mock.Mock +} + +func (_m *AlertingApi) EXPECT() *AlertingApi_Expecter { + return &AlertingApi_Expecter{mock: &_m.Mock} +} + +// ClearContactPoints provides a mock function with given fields: +func (_m *AlertingApi) ClearContactPoints() ([]string, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ClearContactPoints") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func() ([]string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AlertingApi_ClearContactPoints_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearContactPoints' +type AlertingApi_ClearContactPoints_Call struct { + *mock.Call +} + +// ClearContactPoints is a helper method to define mock.On call +func (_e *AlertingApi_Expecter) ClearContactPoints() *AlertingApi_ClearContactPoints_Call { + return &AlertingApi_ClearContactPoints_Call{Call: _e.mock.On("ClearContactPoints")} +} + +func (_c *AlertingApi_ClearContactPoints_Call) Run(run func()) *AlertingApi_ClearContactPoints_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *AlertingApi_ClearContactPoints_Call) Return(_a0 []string, _a1 error) *AlertingApi_ClearContactPoints_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AlertingApi_ClearContactPoints_Call) RunAndReturn(run func() ([]string, error)) *AlertingApi_ClearContactPoints_Call { + _c.Call.Return(run) + return _c +} + +// DownloadContactPoints provides a mock function with given fields: +func (_m *AlertingApi) DownloadContactPoints() (string, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for DownloadContactPoints") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AlertingApi_DownloadContactPoints_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DownloadContactPoints' +type AlertingApi_DownloadContactPoints_Call struct { + *mock.Call +} + +// DownloadContactPoints is a helper method to define mock.On call +func (_e *AlertingApi_Expecter) DownloadContactPoints() *AlertingApi_DownloadContactPoints_Call { + return &AlertingApi_DownloadContactPoints_Call{Call: _e.mock.On("DownloadContactPoints")} +} + +func (_c *AlertingApi_DownloadContactPoints_Call) Run(run func()) *AlertingApi_DownloadContactPoints_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *AlertingApi_DownloadContactPoints_Call) Return(_a0 string, _a1 error) *AlertingApi_DownloadContactPoints_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AlertingApi_DownloadContactPoints_Call) RunAndReturn(run func() (string, error)) *AlertingApi_DownloadContactPoints_Call { + _c.Call.Return(run) + return _c +} + +// ListContactPoints provides a mock function with given fields: +func (_m *AlertingApi) ListContactPoints() ([]*models.EmbeddedContactPoint, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ListContactPoints") + } + + var r0 []*models.EmbeddedContactPoint + var r1 error + if rf, ok := ret.Get(0).(func() ([]*models.EmbeddedContactPoint, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []*models.EmbeddedContactPoint); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.EmbeddedContactPoint) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AlertingApi_ListContactPoints_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListContactPoints' +type AlertingApi_ListContactPoints_Call struct { + *mock.Call +} + +// ListContactPoints is a helper method to define mock.On call +func (_e *AlertingApi_Expecter) ListContactPoints() *AlertingApi_ListContactPoints_Call { + return &AlertingApi_ListContactPoints_Call{Call: _e.mock.On("ListContactPoints")} +} + +func (_c *AlertingApi_ListContactPoints_Call) Run(run func()) *AlertingApi_ListContactPoints_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *AlertingApi_ListContactPoints_Call) Return(_a0 []*models.EmbeddedContactPoint, _a1 error) *AlertingApi_ListContactPoints_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AlertingApi_ListContactPoints_Call) RunAndReturn(run func() ([]*models.EmbeddedContactPoint, error)) *AlertingApi_ListContactPoints_Call { + _c.Call.Return(run) + return _c +} + +// UploadContactPoints provides a mock function with given fields: +func (_m *AlertingApi) UploadContactPoints() ([]string, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for UploadContactPoints") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func() ([]string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AlertingApi_UploadContactPoints_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UploadContactPoints' +type AlertingApi_UploadContactPoints_Call struct { + *mock.Call +} + +// UploadContactPoints is a helper method to define mock.On call +func (_e *AlertingApi_Expecter) UploadContactPoints() *AlertingApi_UploadContactPoints_Call { + return &AlertingApi_UploadContactPoints_Call{Call: _e.mock.On("UploadContactPoints")} +} + +func (_c *AlertingApi_UploadContactPoints_Call) Run(run func()) *AlertingApi_UploadContactPoints_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *AlertingApi_UploadContactPoints_Call) Return(_a0 []string, _a1 error) *AlertingApi_UploadContactPoints_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AlertingApi_UploadContactPoints_Call) RunAndReturn(run func() ([]string, error)) *AlertingApi_UploadContactPoints_Call { + _c.Call.Return(run) + return _c +} + +// NewAlertingApi creates a new instance of AlertingApi. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAlertingApi(t interface { + mock.TestingT + Cleanup(func()) +}) *AlertingApi { + mock := &AlertingApi{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/service/mocks/AuthenticationApi.go b/internal/service/mocks/AuthenticationApi.go index 236566ac..8f44c16c 100644 --- a/internal/service/mocks/AuthenticationApi.go +++ b/internal/service/mocks/AuthenticationApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/ConnectionPermissions.go b/internal/service/mocks/ConnectionPermissions.go index 4bde69a3..42eda9f1 100644 --- a/internal/service/mocks/ConnectionPermissions.go +++ b/internal/service/mocks/ConnectionPermissions.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/ConnectionsApi.go b/internal/service/mocks/ConnectionsApi.go index 3e47bf88..902da856 100644 --- a/internal/service/mocks/ConnectionsApi.go +++ b/internal/service/mocks/ConnectionsApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/DashboardPermissionsApi.go b/internal/service/mocks/DashboardPermissionsApi.go index 00f850ed..35394628 100644 --- a/internal/service/mocks/DashboardPermissionsApi.go +++ b/internal/service/mocks/DashboardPermissionsApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/DashboardsApi.go b/internal/service/mocks/DashboardsApi.go index cf378b21..56aba452 100644 --- a/internal/service/mocks/DashboardsApi.go +++ b/internal/service/mocks/DashboardsApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/FoldersApi.go b/internal/service/mocks/FoldersApi.go index ce209436..5c68ab09 100644 --- a/internal/service/mocks/FoldersApi.go +++ b/internal/service/mocks/FoldersApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/GrafanaService.go b/internal/service/mocks/GrafanaService.go index c1faf0f7..c2145961 100644 --- a/internal/service/mocks/GrafanaService.go +++ b/internal/service/mocks/GrafanaService.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks @@ -74,6 +74,63 @@ func (_c *GrafanaService_AddUserToOrg_Call) RunAndReturn(run func(string, string return _c } +// ClearContactPoints provides a mock function with given fields: +func (_m *GrafanaService) ClearContactPoints() ([]string, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ClearContactPoints") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func() ([]string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GrafanaService_ClearContactPoints_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearContactPoints' +type GrafanaService_ClearContactPoints_Call struct { + *mock.Call +} + +// ClearContactPoints is a helper method to define mock.On call +func (_e *GrafanaService_Expecter) ClearContactPoints() *GrafanaService_ClearContactPoints_Call { + return &GrafanaService_ClearContactPoints_Call{Call: _e.mock.On("ClearContactPoints")} +} + +func (_c *GrafanaService_ClearContactPoints_Call) Run(run func()) *GrafanaService_ClearContactPoints_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GrafanaService_ClearContactPoints_Call) Return(_a0 []string, _a1 error) *GrafanaService_ClearContactPoints_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *GrafanaService_ClearContactPoints_Call) RunAndReturn(run func() ([]string, error)) *GrafanaService_ClearContactPoints_Call { + _c.Call.Return(run) + return _c +} + // ClearDashboardPermissions provides a mock function with given fields: filterReq func (_m *GrafanaService) ClearDashboardPermissions(filterReq filters.Filter) error { ret := _m.Called(filterReq) @@ -931,6 +988,61 @@ func (_c *GrafanaService_DownloadConnections_Call) RunAndReturn(run func(filters return _c } +// DownloadContactPoints provides a mock function with given fields: +func (_m *GrafanaService) DownloadContactPoints() (string, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for DownloadContactPoints") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GrafanaService_DownloadContactPoints_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DownloadContactPoints' +type GrafanaService_DownloadContactPoints_Call struct { + *mock.Call +} + +// DownloadContactPoints is a helper method to define mock.On call +func (_e *GrafanaService_Expecter) DownloadContactPoints() *GrafanaService_DownloadContactPoints_Call { + return &GrafanaService_DownloadContactPoints_Call{Call: _e.mock.On("DownloadContactPoints")} +} + +func (_c *GrafanaService_DownloadContactPoints_Call) Run(run func()) *GrafanaService_DownloadContactPoints_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GrafanaService_DownloadContactPoints_Call) Return(_a0 string, _a1 error) *GrafanaService_DownloadContactPoints_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *GrafanaService_DownloadContactPoints_Call) RunAndReturn(run func() (string, error)) *GrafanaService_DownloadContactPoints_Call { + _c.Call.Return(run) + return _c +} + // DownloadDashboardPermissions provides a mock function with given fields: filterReq func (_m *GrafanaService) DownloadDashboardPermissions(filterReq filters.Filter) ([]string, error) { ret := _m.Called(filterReq) @@ -1849,6 +1961,63 @@ func (_c *GrafanaService_ListConnections_Call) RunAndReturn(run func(filters.Fil return _c } +// ListContactPoints provides a mock function with given fields: +func (_m *GrafanaService) ListContactPoints() ([]*models.EmbeddedContactPoint, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ListContactPoints") + } + + var r0 []*models.EmbeddedContactPoint + var r1 error + if rf, ok := ret.Get(0).(func() ([]*models.EmbeddedContactPoint, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []*models.EmbeddedContactPoint); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.EmbeddedContactPoint) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GrafanaService_ListContactPoints_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListContactPoints' +type GrafanaService_ListContactPoints_Call struct { + *mock.Call +} + +// ListContactPoints is a helper method to define mock.On call +func (_e *GrafanaService_Expecter) ListContactPoints() *GrafanaService_ListContactPoints_Call { + return &GrafanaService_ListContactPoints_Call{Call: _e.mock.On("ListContactPoints")} +} + +func (_c *GrafanaService_ListContactPoints_Call) Run(run func()) *GrafanaService_ListContactPoints_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GrafanaService_ListContactPoints_Call) Return(_a0 []*models.EmbeddedContactPoint, _a1 error) *GrafanaService_ListContactPoints_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *GrafanaService_ListContactPoints_Call) RunAndReturn(run func() ([]*models.EmbeddedContactPoint, error)) *GrafanaService_ListContactPoints_Call { + _c.Call.Return(run) + return _c +} + // ListDashboardPermissions provides a mock function with given fields: filterReq func (_m *GrafanaService) ListDashboardPermissions(filterReq filters.Filter) ([]internaltypes.DashboardAndPermissions, error) { ret := _m.Called(filterReq) @@ -2828,6 +2997,63 @@ func (_c *GrafanaService_UploadConnections_Call) RunAndReturn(run func(filters.F return _c } +// UploadContactPoints provides a mock function with given fields: +func (_m *GrafanaService) UploadContactPoints() ([]string, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for UploadContactPoints") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func() ([]string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GrafanaService_UploadContactPoints_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UploadContactPoints' +type GrafanaService_UploadContactPoints_Call struct { + *mock.Call +} + +// UploadContactPoints is a helper method to define mock.On call +func (_e *GrafanaService_Expecter) UploadContactPoints() *GrafanaService_UploadContactPoints_Call { + return &GrafanaService_UploadContactPoints_Call{Call: _e.mock.On("UploadContactPoints")} +} + +func (_c *GrafanaService_UploadContactPoints_Call) Run(run func()) *GrafanaService_UploadContactPoints_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GrafanaService_UploadContactPoints_Call) Return(_a0 []string, _a1 error) *GrafanaService_UploadContactPoints_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *GrafanaService_UploadContactPoints_Call) RunAndReturn(run func() ([]string, error)) *GrafanaService_UploadContactPoints_Call { + _c.Call.Return(run) + return _c +} + // UploadDashboardPermissions provides a mock function with given fields: filterReq func (_m *GrafanaService) UploadDashboardPermissions(filterReq filters.Filter) ([]string, error) { ret := _m.Called(filterReq) diff --git a/internal/service/mocks/LibraryElementsApi.go b/internal/service/mocks/LibraryElementsApi.go index 30caac58..24f6a1f4 100644 --- a/internal/service/mocks/LibraryElementsApi.go +++ b/internal/service/mocks/LibraryElementsApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/LicenseApi.go b/internal/service/mocks/LicenseApi.go index d68af936..15c75abf 100644 --- a/internal/service/mocks/LicenseApi.go +++ b/internal/service/mocks/LicenseApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/NewClientOpts.go b/internal/service/mocks/NewClientOpts.go index 1fe8ae07..1ad7b91d 100644 --- a/internal/service/mocks/NewClientOpts.go +++ b/internal/service/mocks/NewClientOpts.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/OrgPreferencesApi.go b/internal/service/mocks/OrgPreferencesApi.go index f5360ad1..042108ac 100644 --- a/internal/service/mocks/OrgPreferencesApi.go +++ b/internal/service/mocks/OrgPreferencesApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/OrganizationsApi.go b/internal/service/mocks/OrganizationsApi.go index 2186437b..a2043311 100644 --- a/internal/service/mocks/OrganizationsApi.go +++ b/internal/service/mocks/OrganizationsApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/ServerInfoApi.go b/internal/service/mocks/ServerInfoApi.go index b4e0ffde..18ea0952 100644 --- a/internal/service/mocks/ServerInfoApi.go +++ b/internal/service/mocks/ServerInfoApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/ServiceAccountApi.go b/internal/service/mocks/ServiceAccountApi.go index f7621575..733e9e2c 100644 --- a/internal/service/mocks/ServiceAccountApi.go +++ b/internal/service/mocks/ServiceAccountApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/Storage.go b/internal/service/mocks/Storage.go index ee524f0b..e4d23ac7 100644 --- a/internal/service/mocks/Storage.go +++ b/internal/service/mocks/Storage.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/TeamsApi.go b/internal/service/mocks/TeamsApi.go index 7b309d76..858c737e 100644 --- a/internal/service/mocks/TeamsApi.go +++ b/internal/service/mocks/TeamsApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/TokenApi.go b/internal/service/mocks/TokenApi.go index aa424f85..880a21b1 100644 --- a/internal/service/mocks/TokenApi.go +++ b/internal/service/mocks/TokenApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/UsersApi.go b/internal/service/mocks/UsersApi.go index 7eb583b7..66500b84 100644 --- a/internal/service/mocks/UsersApi.go +++ b/internal/service/mocks/UsersApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/organizationCrudApi.go b/internal/service/mocks/organizationCrudApi.go index ba3cbf9e..c7a3f25f 100644 --- a/internal/service/mocks/organizationCrudApi.go +++ b/internal/service/mocks/organizationCrudApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/organizationToolsApi.go b/internal/service/mocks/organizationToolsApi.go index 1c926192..81404acf 100644 --- a/internal/service/mocks/organizationToolsApi.go +++ b/internal/service/mocks/organizationToolsApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/mocks/organizationUserCrudApi.go b/internal/service/mocks/organizationUserCrudApi.go index dd9e78c6..ea92eaff 100644 --- a/internal/service/mocks/organizationUserCrudApi.go +++ b/internal/service/mocks/organizationUserCrudApi.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks diff --git a/internal/service/serviceaccounts.go b/internal/service/serviceaccounts.go index 43a95e9f..b83c30fb 100644 --- a/internal/service/serviceaccounts.go +++ b/internal/service/serviceaccounts.go @@ -5,9 +5,10 @@ import ( "log" "log/slog" + "github.com/esnet/gdg/internal/tools/ptr" + "github.com/esnet/gdg/internal/types" - "github.com/esnet/gdg/internal/tools" "github.com/grafana/grafana-openapi-client-go/client/service_accounts" "github.com/grafana/grafana-openapi-client-go/models" "github.com/samber/lo" @@ -44,8 +45,8 @@ func (s *DashNGoImpl) CreateServiceAccountToken(serviceAccountId int64, name str func (s *DashNGoImpl) ListServiceAccounts() []*types.ServiceAccountDTOWithTokens { p := service_accounts.NewSearchOrgServiceAccountsWithPagingParams() - p.Disabled = tools.PtrOf(false) - p.Perpage = tools.PtrOf(int64(5000)) + p.Disabled = ptr.Of(false) + p.Perpage = ptr.Of(int64(5000)) resp, err := s.GetClient().ServiceAccounts.SearchOrgServiceAccountsWithPaging(p) if err != nil { diff --git a/internal/service/teams.go b/internal/service/teams.go index 0b4aea06..6b6afe6d 100644 --- a/internal/service/teams.go +++ b/internal/service/teams.go @@ -183,20 +183,6 @@ func (s *DashNGoImpl) ListTeams(filter filters.Filter) map[*models.TeamDTO][]*mo return result } -// Get a specific Team -// Return nil if team cannot be found -//func (s *DashNGoImpl) getTeam(teamName string, filter filters.Filter) *models.TeamDTO { -// teamListing := maps.Keys(s.ListTeams(filter)) -// var team *models.TeamDTO -// for ndx, item := range teamListing { -// if item.Name == teamName { -// team = teamListing[ndx] -// break -// } -// } -// return team -//} - // DeleteTeam removes all Teams func (s *DashNGoImpl) DeleteTeam(filter filters.Filter) ([]*models.TeamDTO, error) { teamListing := maps.Keys(s.ListTeams(filter)) diff --git a/internal/service/user.go b/internal/service/user.go index f90ddfdc..1b7e1e6c 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -10,9 +10,10 @@ import ( "sort" "strings" + "github.com/esnet/gdg/internal/tools/ptr" + "github.com/esnet/gdg/internal/config" "github.com/esnet/gdg/internal/service/filters" - "github.com/esnet/gdg/internal/tools" "github.com/esnet/gdg/internal/types" "github.com/gosimple/slug" "github.com/grafana/grafana-openapi-client-go/client/admin_users" @@ -164,8 +165,8 @@ func (s *DashNGoImpl) ListUsers(filter filters.Filter) []*models.UserSearchHitDT } var filteredUsers []*models.UserSearchHitDTO params := users.NewSearchUsersParams() - params.Page = tools.PtrOf(int64(1)) - params.Perpage = tools.PtrOf(int64(5000)) + params.Page = ptr.Of(int64(1)) + params.Perpage = ptr.Of(int64(5000)) usersList, err := s.GetClient().Users.SearchUsers(params) if err != nil { log.Fatal(err.Error()) diff --git a/internal/tools/generics_tooling.go b/internal/tools/generics_tooling.go index 4db316db..2d259867 100644 --- a/internal/tools/generics_tooling.go +++ b/internal/tools/generics_tooling.go @@ -6,10 +6,6 @@ import ( "os" ) -func PtrOf[T any](value T) *T { - return &value -} - func DeepCopy[T any](value T) (*T, error) { origJSON, err := json.Marshal(value) if err != nil { diff --git a/internal/tools/ptr/ptr.go b/internal/tools/ptr/ptr.go new file mode 100644 index 00000000..c36b2a2d --- /dev/null +++ b/internal/tools/ptr/ptr.go @@ -0,0 +1,5 @@ +package ptr + +func Of[T any](value T) *T { + return &value +} diff --git a/cli/test/support.go b/pkg/test_tooling/support.go similarity index 89% rename from cli/test/support.go rename to pkg/test_tooling/support.go index 7498bec9..775fe1f8 100644 --- a/cli/test/support.go +++ b/pkg/test_tooling/support.go @@ -1,4 +1,4 @@ -package test +package test_tooling import ( "io" @@ -16,9 +16,9 @@ import ( "github.com/stretchr/testify/assert" ) -// setupAndExecuteMockingServices will create a mock for varous required entities allowing to test the CLI flag parsing +// SetupAndExecuteMockingServices will create a mock for varous required entities allowing to test the CLI flag parsing // process: function that setups mocks and invokes the Execute command -func setupAndExecuteMockingServices(t *testing.T, process func(mock *mocks.GrafanaService, data []byte, optionMockSvc func() support.RootOption) error) (string, func()) { +func SetupAndExecuteMockingServices(t *testing.T, process func(mock *mocks.GrafanaService, data []byte, optionMockSvc func() support.RootOption) error) (string, func()) { testSvc := new(mocks.GrafanaService) getMockSvc := func() service.GrafanaService { return testSvc @@ -31,7 +31,7 @@ func setupAndExecuteMockingServices(t *testing.T, process func(mock *mocks.Grafa } r, w, cleanup := InterceptStdout() - data, err := os.ReadFile("../../config/" + common.DefaultTestConfig) + data, err := os.ReadFile("config/" + common.DefaultTestConfig) assert.Nil(t, err) err = process(testSvc, data, optionMockSvc) diff --git a/test/alerting_contacts_test.go b/test/alerting_contacts_test.go new file mode 100644 index 00000000..12df3fed --- /dev/null +++ b/test/alerting_contacts_test.go @@ -0,0 +1,42 @@ +package test + +import ( + "bytes" + "log/slog" + "os" + "slices" + "testing" + + "github.com/esnet/gdg/pkg/test_tooling" + "github.com/stretchr/testify/assert" +) + +func TestContactsCrud(t *testing.T) { + apiClient, _, _, cleanup := test_tooling.InitTest(t, nil, nil) + defer func() { + err := cleanup() + if err != nil { + slog.Warn("Unable to clean up after alerting contacts crud tests") + } + }() + contactPoints, err := apiClient.ListContactPoints() + assert.NoError(t, err) + assert.Equal(t, len(contactPoints), 0, "Validate initial contact list is empty") + contacts, err := apiClient.UploadContactPoints() + assert.NoError(t, err) + assert.Equal(t, len(contacts), 1) + assert.True(t, slices.Contains(contacts, "discord")) + contactPoints, err = apiClient.ListContactPoints() + assert.NoError(t, err) + assert.Equal(t, len(contactPoints), 1) + data, err := apiClient.DownloadContactPoints() + assert.NoError(t, err) + assert.Equal(t, "test/data/org_main-org/alerting/contacts.json", data) + rawData, err := os.ReadFile(data) + assert.NoError(t, err) + assert.True(t, bytes.Contains(rawData, []byte("discord"))) + assert.False(t, bytes.Contains(rawData, []byte("email receiver"))) + contacts, err = apiClient.ClearContactPoints() + assert.NoError(t, err) + assert.Equal(t, len(contacts), 1) +} diff --git a/test/dashboard_integration_test.go b/test/dashboard_integration_test.go index 9a828f0e..2b7ec8eb 100644 --- a/test/dashboard_integration_test.go +++ b/test/dashboard_integration_test.go @@ -8,7 +8,8 @@ import ( "strings" "testing" - "github.com/esnet/gdg/internal/tools" + "github.com/esnet/gdg/internal/tools/ptr" + "github.com/esnet/gdg/internal/types" "github.com/esnet/gdg/pkg/test_tooling/containers" "github.com/samber/lo" @@ -266,7 +267,7 @@ func TestDashboardPermissionsCrud(t *testing.T) { // Get current Permissions currentPerms, err := apiClient.ListDashboardPermissions(nil) assert.Equal(t, len(currentPerms), 16) - entry := tools.PtrOf(lo.FirstOrEmpty(lo.Filter(currentPerms, func(item types.DashboardAndPermissions, index int) bool { + entry := ptr.Of(lo.FirstOrEmpty(lo.Filter(currentPerms, func(item types.DashboardAndPermissions, index int) bool { return item.Dashboard.Title == "Bandwidth Dashboard" }))) assert.NotNil(t, entry) @@ -282,7 +283,7 @@ func TestDashboardPermissionsCrud(t *testing.T) { assert.Equal(t, len(addPerms), 16) currentPerms, err = apiClient.ListDashboardPermissions(nil) entry = nil - entry = tools.PtrOf(lo.FirstOrEmpty(lo.Filter(currentPerms, func(item types.DashboardAndPermissions, index int) bool { + entry = ptr.Of(lo.FirstOrEmpty(lo.Filter(currentPerms, func(item types.DashboardAndPermissions, index int) bool { return item.Dashboard.Title == "Bandwidth Dashboard" }))) assert.NotNil(t, entry) diff --git a/test/data/org_main-org/alerting/contacts.json b/test/data/org_main-org/alerting/contacts.json new file mode 100644 index 00000000..2bc299a4 --- /dev/null +++ b/test/data/org_main-org/alerting/contacts.json @@ -0,0 +1,16 @@ +[ + { + "name": "discord", + "orgId": 1, + "receivers": [ + { + "settings": { + "url": "https://www.discord.com?q=woot", + "use_discord_username": false + }, + "type": "discord", + "uid": "fdxmqkyb5gl4xb" + } + ] + } +] \ No newline at end of file