From f1d025d249b8892fca7fe16214d661fd742378f5 Mon Sep 17 00:00:00 2001 From: Samir Faci Date: Fri, 12 Apr 2024 08:00:34 -0500 Subject: [PATCH] Adding Dashboard Folder Permissions --- .github/workflows/release.yml | 2 +- cli/backup/connection_permissions.go | 2 +- cli/backup/dashboard.go | 32 ++- cli/backup/dashboard_permissions.go | 198 ++++++++++++++ cli/tools/org_users.go | 6 +- go.sum | 77 ------ internal/config/resource_type.go | 2 + internal/service/common.go | 4 +- internal/service/contracts.go | 8 + internal/service/dashboard_permissions.go | 181 ++++++++++++ internal/service/dashboards.go | 2 +- .../service/mocks/DashboardPermissionsApi.go | 257 ++++++++++++++++++ internal/service/mocks/GrafanaService.go | 222 +++++++++++++++ internal/service/mocks/Storage.go | 45 +++ 14 files changed, 940 insertions(+), 98 deletions(-) create mode 100644 cli/backup/dashboard_permissions.go create mode 100644 internal/service/dashboard_permissions.go create mode 100644 internal/service/mocks/DashboardPermissionsApi.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8946e6f3..b98c59c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.22.1' + go-version: '1.22.2' - name: Log in to Docker Hub uses: docker/login-action@v2 with: diff --git a/cli/backup/connection_permissions.go b/cli/backup/connection_permissions.go index c93514de..62d90f42 100644 --- a/cli/backup/connection_permissions.go +++ b/cli/backup/connection_permissions.go @@ -22,7 +22,7 @@ func newConnectionsPermissionCmd() simplecobra.Commander { Short: description, Long: description, WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { - cmd.Aliases = []string{"l", "permissions"} + cmd.Aliases = []string{"p", "permissions"} }, CommandsList: []simplecobra.Commander{ newConnectionsPermissionListCmd(), diff --git a/cli/backup/dashboard.go b/cli/backup/dashboard.go index 6b355a1d..fa738ea6 100644 --- a/cli/backup/dashboard.go +++ b/cli/backup/dashboard.go @@ -8,6 +8,8 @@ import ( "net/url" "strings" + "github.com/grafana/grafana-openapi-client-go/models" + "github.com/bep/simplecobra" "github.com/esnet/gdg/cli/support" "github.com/esnet/gdg/internal/config" @@ -50,6 +52,8 @@ func newDashboardCommand() simplecobra.Commander { newDownloadDashboardsCmd(), newUploadDashboardsCmd(), newClearDashboardsCmd(), + // Permissions + newDashboardPermissionCmd(), }, RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { return cd.CobraCommand.Help() @@ -151,6 +155,19 @@ func newDownloadDashboardsCmd() simplecobra.Commander { } } +func getDashboardUrl(link *models.Hit) string { + base, err := url.Parse(config.Config().GetDefaultGrafanaConfig().URL) + var baseHost string + if err != nil { + baseHost = "http://unknown/" + slog.Warn("unable to determine grafana base host for dashboard", slog.String("dashboard-uid", link.UID)) + } else { + base.Path = "" + baseHost = base.String() + } + return fmt.Sprintf("%s%s", baseHost, link.URL) +} + func newListDashboardsCmd() simplecobra.Commander { description := "List all dashboards from grafana" return &support.SimpleCommand{ @@ -181,21 +198,8 @@ func newListDashboardsCmd() simplecobra.Commander { count := 0 for _, link := range boards { - base, err := url.Parse(config.Config().GetDefaultGrafanaConfig().URL) - var baseHost string - if err != nil { - baseHost = "http://unknown/" - slog.Warn("unable to determine grafana base host for dashboard", slog.String("dashboard-uid", link.UID)) - } else { - base.Path = "" - baseHost = base.String() - } - if string(link.Type) == service.SearchTypeFolder { - slog.Debug("skipping entry for", slog.Any("slug", link.Slug), slog.Any("url", fmt.Sprintf("%s/%s", baseHost, link.UID)), slog.Any("type", link.Type)) - continue - } + urlValue := getDashboardUrl(link) count++ - urlValue := fmt.Sprintf("%s%s", baseHost, link.URL) var tagVal string if len(link.Tags) > 0 { tagValByte, err := json.Marshal(link.Tags) diff --git a/cli/backup/dashboard_permissions.go b/cli/backup/dashboard_permissions.go new file mode 100644 index 00000000..6dd0e0cd --- /dev/null +++ b/cli/backup/dashboard_permissions.go @@ -0,0 +1,198 @@ +package backup + +import ( + "context" + "fmt" + "log/slog" + "os" + "strings" + + "github.com/bep/simplecobra" + "github.com/esnet/gdg/cli/support" + "github.com/esnet/gdg/internal/config" + "github.com/esnet/gdg/internal/service" + "github.com/esnet/gdg/internal/tools" + "github.com/jedib0t/go-pretty/v6/table" + + "github.com/spf13/cobra" +) + +func newDashboardPermissionCmd() simplecobra.Commander { + description := "Dashboard Permission" + return &support.SimpleCommand{ + NameP: "permission", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"p", "permissions"} + }, + CommandsList: []simplecobra.Commander{ + newDashboardPermissionListCmd(), + newDashboardPermissionDownloadCmd(), + newDashboardPermissionUploadCmd(), + newDashboardPermissionClearCmd(), + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + return cd.CobraCommand.Help() + }, + } +} + +// getConnectionTbWriter returns a table object for use with newConnectionsPermissionListCmd +func getDashboardPermTblWriter() table.Writer { + writer := table.NewWriter() + writer.SetOutputMirror(os.Stdout) + writer.SetStyle(table.StyleLight) + writer.AppendHeader(table.Row{"id", "name", "slug", "type", "uid", "url"}, table.RowConfig{AutoMerge: true}) + return writer +} + +func newDashboardPermissionListCmd() simplecobra.Commander { + description := "List Dashboard Permissions" + 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 { + slog.Info("Listing Dashboard Permissions for context", "context", config.Config().GetGDGConfig().GetContext()) + filters := service.NewDashboardFilter(parseDashboardGlobalFlags(cd.CobraCommand)...) + permissions, err := rootCmd.GrafanaSvc().ListDashboardPermissions(filters) + if err != nil { + slog.Error("Failed to retrieve Dashboard Permissions", "error", err) + os.Exit(1) + } + + if len(permissions) == 0 { + slog.Info("No Dashboards found") + } else { + for _, perms := range permissions { + writer := getDashboardPermTblWriter() + urlValue := getDashboardUrl(perms.Dashboard) + link := perms.Dashboard + writer.AppendRow(table.Row{ + link.ID, link.Title, link.Slug, link.FolderTitle, + link.UID, urlValue, + }) + writer.Render() + if perms.Permissions != nil { + twConfigs := table.NewWriter() + twConfigs.SetOutputMirror(os.Stdout) + twConfigs.SetStyle(table.StyleDouble) + twConfigs.AppendHeader(table.Row{"Dashboard UID", "Dashboard Title", "UserLogin", "Team", "RoleName", "Permission"}) + for _, dashPerm := range perms.Permissions { + var userLogin string + if len(dashPerm.UserLogin) > 0 { + if strings.HasPrefix(dashPerm.UserEmail, "sa-") && !strings.Contains(dashPerm.UserEmail, "@") { + userLogin = fmt.Sprintf("service:%s", dashPerm.UserLogin) + } else { + userLogin = fmt.Sprintf("user:%s", dashPerm.UserLogin) + } + } + twConfigs.AppendRow(table.Row{link.UID, link.Title, userLogin, dashPerm.Team, dashPerm.Role, dashPerm.PermissionName}) + } + if len(perms.Permissions) > 0 { + twConfigs.Render() + } + } + } + } + return nil + }, + } +} + +func newDashboardPermissionClearCmd() simplecobra.Commander { + description := "Clear Connection Permissions" + return &support.SimpleCommand{ + NameP: "clear", + Short: description, + Long: description, + WithCFunc: func(cmd *cobra.Command, r *support.RootCommand) { + cmd.Aliases = []string{"c"} + }, + RunFunc: func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *support.RootCommand, args []string) error { + slog.Info("Clear all Dashboard permissions") + tools.GetUserConfirmation(fmt.Sprintf("WARNING: this will clear all permission from all Dashboards on: '%s' "+ + "(Or all permission matching your filters). Do you wish to continue (y/n) ", config.Config().GetGDGConfig().ContextName, + ), "", true) + rootCmd.TableObj.AppendHeader(table.Row{"cleared Dashboard permissions"}) + filters := service.NewDashboardFilter(parseDashboardGlobalFlags(cd.CobraCommand)...) + err := rootCmd.GrafanaSvc().ClearDashboardPermissions(filters) + if err != nil { + slog.Error("Failed to retrieve Dashboard Permissions", "error", err) + } else { + slog.Info("All dashboard permissions have been cleared") + } + return nil + }, + } +} + +func newDashboardPermissionDownloadCmd() simplecobra.Commander { + description := "Download Connection Permissions" + 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 { + slog.Info("Download Connections for context", + "context", config.Config().GetGDGConfig().GetContext()) + rootCmd.TableObj.AppendHeader(table.Row{"filename"}) + filters := service.NewDashboardFilter(parseDashboardGlobalFlags(cd.CobraCommand)...) + permissions, err := rootCmd.GrafanaSvc().DownloadDashboardPermissions(filters) + if err != nil { + slog.Error("Failed to retrieve Dashboard Permissions", "error", err) + os.Exit(1) + } + slog.Info("Downloading Dashboard permissions") + + if len(permissions) == 0 { + slog.Info("No Dashboard permissions") + } else { + for _, perm := range permissions { + rootCmd.TableObj.AppendRow(table.Row{perm}) + } + rootCmd.Render(cd.CobraCommand, permissions) + } + return nil + }, + } +} + +func newDashboardPermissionUploadCmd() simplecobra.Commander { + description := "Upload Connection Permissions" + 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 { + slog.Info("Uploading dashboard permissions") + rootCmd.TableObj.AppendHeader(table.Row{"dashboard permission"}) + filters := service.NewDashboardFilter(parseDashboardGlobalFlags(cd.CobraCommand)...) + permissions, err := rootCmd.GrafanaSvc().UploadDashboardPermissions(filters) + if err != nil { + slog.Error("Failed to retrieve Dashboard Permissions", "error", err) + os.Exit(1) + } + + if len(permissions) == 0 { + slog.Info("No permissions found") + } else { + for _, perm := range permissions { + rootCmd.TableObj.AppendRow(table.Row{perm}) + } + rootCmd.Render(cd.CobraCommand, permissions) + } + return nil + }, + } +} diff --git a/cli/tools/org_users.go b/cli/tools/org_users.go index 9be1161b..a9f6d57f 100644 --- a/cli/tools/org_users.go +++ b/cli/tools/org_users.go @@ -110,7 +110,7 @@ func newUpdateUserRoleCmd() simplecobra.Commander { if err != nil { log.Fatal("unable to parse userId to numeric value") } - slog.Info("Listing org users for context", "context", config.Config().GetGDGConfig().GetContext()) + slog.Info("Updating User role for context", slog.Any("context", config.Config().GetGDGConfig().GetContext())) rootCmd.TableObj.AppendHeader(table.Row{"login", "orgId", "name", "email", "role"}) err = rootCmd.GrafanaSvc().UpdateUserInOrg(roleName, orgSlug, userId) if err != nil { @@ -143,6 +143,8 @@ func newAddUserRoleCmd() simplecobra.Commander { log.Fatal("unable to parse userId to numeric value") } slog.Info("Add user to org for context", + slog.Any("userId", userId), + slog.Any("orgSlug", orgSlug), slog.Any("context", config.Config().GetGDGConfig().GetContext()), slog.Any("organization", config.Config().GetDefaultGrafanaConfig().OrganizationName), ) @@ -152,7 +154,7 @@ func newAddUserRoleCmd() simplecobra.Commander { rootCmd.TableObj.AppendHeader(table.Row{"login", "orgId", "name", "email", "role"}) err = rootCmd.GrafanaSvc().AddUserToOrg(role, orgSlug, userId) if err != nil { - slog.Error("Unable to add user to Org", slog.Any("err", err.Error())) + slog.Error("Unable to add user to Org, already exists", slog.Any("err", err.Error())) } else { slog.Info("User has been add to Org", slog.Any("userId", userId), slog.String("organization", orgSlug)) } diff --git a/go.sum b/go.sum index 0ca61cea..fff2da0a 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= -cloud.google.com/go/auth v0.9.2 h1:I+Rq388FYU8QdbVB1IiPd+6KNdrqtAPE/asiKHShBLM= -cloud.google.com/go/auth v0.9.2/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= cloud.google.com/go/auth v0.9.4 h1:DxF7imbEbiFu9+zdKC6cKBko1e8XeJnipNqIbWZ+kDI= cloud.google.com/go/auth v0.9.4/go.mod h1:SHia8n6//Ya940F1rLimhJCjjx7KE17t0ctFEci3HkA= cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= -cloud.google.com/go/compute v1.28.0 h1:OPtBxMcheSS+DWfci803qvPly3d4w7Eu5ztKBcFfzwk= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/iam v1.2.0 h1:kZKMKVNk/IsSSc/udOb83K0hL/Yh/Gcqpz+oAkoIFN8= @@ -61,70 +58,40 @@ github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHS github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= -github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw= -github.com/aws/aws-sdk-go-v2/config v1.27.31 h1:kxBoRsjhT3pq0cKthgj6RU6bXTm/2SgdoUMyrVw0rAI= -github.com/aws/aws-sdk-go-v2/config v1.27.31/go.mod h1:z04nZdSWFPaDwK3DdJOG2r+scLQzMYuJeW0CujEm9FM= github.com/aws/aws-sdk-go-v2/config v1.27.33 h1:Nof9o/MsmH4oa0s2q9a0k7tMz5x/Yj5k06lDODWz3BU= github.com/aws/aws-sdk-go-v2/config v1.27.33/go.mod h1:kEqdYzRb8dd8Sy2pOdEbExTTF5v7ozEXX0McgPE7xks= -github.com/aws/aws-sdk-go-v2/credentials v1.17.30 h1:aau/oYFtibVovr2rDt8FHlU17BTicFEMAi29V1U+L5Q= -github.com/aws/aws-sdk-go-v2/credentials v1.17.30/go.mod h1:BPJ/yXV92ZVq6G8uYvbU0gSl8q94UB63nMT5ctNO38g= github.com/aws/aws-sdk-go-v2/credentials v1.17.32 h1:7Cxhp/BnT2RcGy4VisJ9miUPecY+lyE9I8JvcZofn9I= github.com/aws/aws-sdk-go-v2/credentials v1.17.32/go.mod h1:P5/QMF3/DCHbXGEGkdbilXHsyTBX5D3HSwcrSc9p20I= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.16 h1:1FWqcOnvnO0lRsv0kLACwwQquoZIoS5tD0MtfoNdnkk= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.16/go.mod h1:+E8OuB446P/5Swajo40TqenLMzm6aYDEEz6FZDn/u1E= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.18 h1:9DIp7vhmOPmueCDwpXa45bEbLHHTt1kcxChdTJWWxvI= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.18/go.mod h1:aJv/Fwz8r56ozwYFRC4bzoeL1L17GYQYemfblOBux1M= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 h1:mimdLQkIX1zr8GIPY1ZtALdBQGxcASiBd2MOp8m/dMc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16/go.mod h1:YHk6owoSwrIsok+cAH9PENCOGoH5PU2EllX4vLtSrsY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 h1:GckUnpm4EJOAio1c8o25a+b3lVfwVzC9gnSBqiiNmZM= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18/go.mod h1:Br6+bxfG33Dk3ynmkhsW2Z/t9D4+lRqdLDNCKi85w0U= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 h1:FLMkfEiRjhgeDTCjjLoc3URo/TBkgeQbocA78lfkzSI= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19/go.mod h1:Vx+GucNSsdhaxs3aZIKfSUjKVGsxN25nX2SRcdhuw08= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 h1:jg16PhLPUiHIj8zYIW6bqzeQSuHVEiWnGA0Brz5Xv2I= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16/go.mod h1:Uyk1zE1VVdsHSU7096h/rwnXDzOzYQVl+FNPhPw7ShY= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.61.0 h1:Wb544Wh+xfSXqJ/j3R4aX9wrKUoZsJNmilBYZb3mKQ4= -github.com/aws/aws-sdk-go-v2/service/s3 v1.61.0/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI= github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg= github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc= github.com/aws/aws-sdk-go-v2/service/sso v1.22.7/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 h1:/Cfdu0XV3mONYKaOt1Gr0k1KvQzkzPyiKUdlWJqy+J4= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wINh+4UK+k/0Yo/q8= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 h1:NKTa1eqZYw8tiHSRGpP0VtTdub/8KNk8sDkNPFaOKDE= github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o= github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= @@ -162,8 +129,6 @@ github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= -github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.2.1+incompatible h1:fQdiLfW7VLscyoeYEBz7/J8soYFDZV1u6VW6gJEjNMI= github.com/docker/docker v27.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -258,8 +223,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= -github.com/googleapis/enterprise-certificate-proxy v0.3.3 h1:QRje2j5GZimBzlbhGA2V2QlGNgL8G6e+wGo/+/2bWI0= -github.com/googleapis/enterprise-certificate-proxy v0.3.3/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= @@ -311,8 +274,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw= github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= -github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7 h1:5RK988zAqB3/AN3opGfRpoQgAVqr6/A5+qRTi67VUZY= -github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -374,15 +335,11 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= -github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.57.0 h1:Ro/rKjwdq9mZn1K5QPctzh+MA4Lp0BuYk5ZZEVhoNcY= -github.com/prometheus/common v0.57.0/go.mod h1:7uRPFSUTbfZWsJ7MHY56sqt7hLQu3bxXHDnNhl8E9qI= github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= @@ -477,22 +434,16 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.5 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -511,13 +462,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= -golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -529,8 +476,6 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -548,13 +493,9 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -588,8 +529,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -598,8 +537,6 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -610,8 +547,6 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= @@ -632,12 +567,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= -golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.195.0 h1:Ude4N8FvTKnnQJHU48RFI40jOBgIrL8Zqr3/QeST6yU= -google.golang.org/api v0.195.0/go.mod h1:DOGRWuv3P8TU8Lnz7uQc4hyNqrBpMtD9ppW3wBJurgc= google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -645,16 +576,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240827150818-7e3bb234dfed h1:4C4dbrVFtfIp3GXJdMX1Sj25mahfn5DywOo65/2ISQ8= -google.golang.org/genproto v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:ICjniACoWvcDz8c8bOsHVKuuSGDJy1z5M4G0DM3HzTc= google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= -google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0= -google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -662,8 +587,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/internal/config/resource_type.go b/internal/config/resource_type.go index 1489721c..371f17a5 100644 --- a/internal/config/resource_type.go +++ b/internal/config/resource_type.go @@ -12,6 +12,7 @@ type ResourceType string const ( ConnectionPermissionResource ResourceType = "connections-permissions" ConnectionResource ResourceType = "connections" + DashboardPermissionsResource ResourceType = "dashboards-permissions" DashboardResource ResourceType = "dashboards" FolderPermissionResource ResourceType = "folders-permissions" FolderResource ResourceType = "folders" @@ -26,6 +27,7 @@ const ( var orgNamespacedResource = map[ResourceType]bool{ ConnectionPermissionResource: true, + DashboardPermissionsResource: true, ConnectionResource: true, DashboardResource: true, FolderPermissionResource: true, diff --git a/internal/service/common.go b/internal/service/common.go index 04d19426..a7441ae7 100644 --- a/internal/service/common.go +++ b/internal/service/common.go @@ -34,8 +34,8 @@ func updateSlug(board string) string { // getFolderFromResourcePath if a use encodes a path separator in path, we can't determine the folder name. This strips away // all the known components of a resource type leaving only the folder name. -func getFolderFromResourcePath(storageEngine string, filePath string) (string, error) { - basePath := fmt.Sprintf("%s/", config.Config().GetDefaultGrafanaConfig().GetPath(config.DashboardResource)) +func getFolderFromResourcePath(storageEngine string, filePath string, resourceType config.ResourceType) (string, error) { + basePath := fmt.Sprintf("%s/", config.Config().GetDefaultGrafanaConfig().GetPath(resourceType)) // Take into account cloud prefix is enabled if storageEngine != "" { cloudType, data := config.Config().GetCloudConfiguration(storageEngine) diff --git a/internal/service/contracts.go b/internal/service/contracts.go index 86dc79b8..d23e0775 100644 --- a/internal/service/contracts.go +++ b/internal/service/contracts.go @@ -15,6 +15,7 @@ type ServerInfoApi interface { type GrafanaService interface { OrganizationsApi DashboardsApi + DashboardPermissionsApi ConnectionsApi UsersApi FoldersApi @@ -57,6 +58,13 @@ type DashboardsApi interface { LintDashboards(req types.LintRequest) []string } +type DashboardPermissionsApi interface { + ListDashboardPermissions(filterReq filters.Filter) ([]DashboardAndPermissions, error) + DownloadDashboardPermissions(filterReq filters.Filter) ([]string, error) + ClearDashboardPermissions(filterReq filters.Filter) error + UploadDashboardPermissions(filterReq filters.Filter) ([]string, error) +} + // FoldersApi Contract definition type FoldersApi interface { ListFolders(filter filters.Filter) []*customModels.FolderDetails diff --git a/internal/service/dashboard_permissions.go b/internal/service/dashboard_permissions.go new file mode 100644 index 00000000..12860369 --- /dev/null +++ b/internal/service/dashboard_permissions.go @@ -0,0 +1,181 @@ +package service + +import ( + "encoding/json" + "fmt" + "log" + "log/slog" + "path/filepath" + "slices" + "strings" + + "github.com/esnet/gdg/internal/config" + "github.com/esnet/gdg/internal/service/filters" + "github.com/gosimple/slug" + "github.com/grafana/grafana-openapi-client-go/models" +) + +type DashboardAndPermissions struct { + Dashboard *models.Hit + Permissions []*models.DashboardACLInfoDTO +} + +func (s *DashNGoImpl) ListDashboardPermissions(filterReq filters.Filter) ([]DashboardAndPermissions, error) { + validateDashboardEnterpriseSupport(s) + dashboards := s.ListDashboards(filterReq) + var result []DashboardAndPermissions + for _, dashboard := range dashboards { + item := DashboardAndPermissions{Dashboard: dashboard} + perms, err := s.GetClient().DashboardPermissions.GetDashboardPermissionsListByUID(dashboard.UID) + if err != nil { + slog.Warn("Unable to retrieve permissions for dashboard", + slog.String("uid", dashboard.UID), + slog.String("Name", dashboard.Title)) + continue + } else { + item.Permissions = perms.GetPayload() + } + result = append(result, item) + } + + return result, nil +} + +func (s *DashNGoImpl) DownloadDashboardPermissions(filterReq filters.Filter) ([]string, error) { + var ( + dsPacked []byte + err error + dataFiles []string + ) + validateDashboardEnterpriseSupport(s) + boardLinks, err := s.ListDashboardPermissions(filterReq) + if err != nil { + return nil, err + } + + for _, link := range boardLinks { + if len(link.Permissions) == 0 { + continue + } + if dsPacked, err = json.MarshalIndent(link.Permissions, "", " "); err != nil { + slog.Error("unable to marshall json ", "err", err.Error(), "dashboard", link.Dashboard.Title) + continue + } + + dsPath := fmt.Sprintf("%s/%s.json", BuildResourceFolder(link.Dashboard.FolderTitle, config.DashboardPermissionsResource), slug.Make(link.Dashboard.Title)) + if err = s.storage.WriteFile(dsPath, dsPacked); err != nil { + slog.Error("unable to write file. ", "filename", slug.Make(link.Dashboard.Title), "error", err.Error()) + } else { + dataFiles = append(dataFiles, dsPath) + } + } + + return dataFiles, nil +} + +func validateDashboardEnterpriseSupport(s *DashNGoImpl) { + if !s.IsEnterprise() { + log.Fatalf("Enterprise support is required for Dashboard Permissions") + } +} + +func (s *DashNGoImpl) UploadDashboardPermissions(filterReq filters.Filter) ([]string, error) { + if !s.IsEnterprise() { + log.Fatalf("Enterprise support is required for Dashboard Permissions") + } + validateDashboardEnterpriseSupport(s) + var ( + rawFolder []byte + dataFiles []string + folderName string + ) + // Fallback on defaults + if filterReq == nil { + filterReq = NewDashboardFilter("", "", "") + } + validFolders := filterReq.GetEntity(filters.FolderFilter) + + path := config.Config().GetDefaultGrafanaConfig().GetPath(config.DashboardPermissionsResource) + filesInDir, err := s.storage.FindAllFiles(path, true) + if err != nil { + log.Fatalf("Failed to read folders permission imports: %s", err.Error()) + } + for _, file := range filesInDir { + // TODO: add validation of dashboard + baseFile := filepath.Base(file) + baseFile = strings.ReplaceAll(baseFile, ".json", "") + if !strings.HasSuffix(file, ".json") { + slog.Warn("Only json files are supported, skipping", "filename", file) + continue + } + // Extract Folder Name based on path + folderName, err = getFolderFromResourcePath(s.grafanaConf.Storage, file, config.DashboardPermissionsResource) + if err != nil { + slog.Warn("unable to determine dashboard folder name, falling back on default") + } + if folderName == "" { + folderName = DefaultFolderName + } + if !slices.Contains(validFolders, folderName) && !config.Config().GetDefaultGrafanaConfig().GetDashboardSettings().IgnoreFilters { + slog.Debug("Skipping file since it doesn't match any valid folders", "filename", file) + continue + } + validateMap := map[filters.FilterType]string{filters.FolderFilter: folderName, filters.DashFilter: baseFile} + // If folder OR slug is filtered, then skip if it doesn't match + if !filterReq.ValidateAll(validateMap) { + continue + } + + if err != nil { + slog.Warn("unable to determine dashboard folder name, falling back on default") + } + if rawFolder, err = s.storage.ReadFile(file); err != nil { + slog.Warn("Unable to read file", "filename", file, "err", err) + continue + } + + var permissions []*models.DashboardACLInfoDTO + err = json.Unmarshal(rawFolder, &permissions) + if err != nil || len(permissions) == 0 { + slog.Error("failed to unmarshall permissions for file.", slog.String("filename", file), "err", err) + continue + } + dashboardId := permissions[0].UID + request := &models.UpdateDashboardACLCommand{Items: make([]*models.DashboardACLUpdateItem, 0)} + for _, permission := range permissions { + item := &models.DashboardACLUpdateItem{ + Permission: permission.Permission, + Role: permission.Role, + TeamID: permission.TeamID, + UserID: permission.UserID, + } + request.Items = append(request.Items, item) + } + _, err = s.GetClient().DashboardPermissions.UpdateDashboardPermissionsByUID(dashboardId, request) + if err != nil { + slog.Error("Failed to process file", slog.String("filename", file)) + } else { + dataFiles = append(dataFiles, file) + } + } + return dataFiles, nil +} + +func (s *DashNGoImpl) ClearDashboardPermissions(filterReq filters.Filter) error { + validateDashboardEnterpriseSupport(s) + boardLinks, err := s.ListDashboardPermissions(filterReq) + if err != nil { + slog.Error("unable to retrieve dashboards", slog.Any("err", err)) + return err + } + for _, link := range boardLinks { + request := &models.UpdateDashboardACLCommand{} + request.Items = make([]*models.DashboardACLUpdateItem, 0) + _, err := s.GetClient().DashboardPermissions.UpdateDashboardPermissionsByUID(link.Dashboard.UID, request) + if err != nil { + slog.Error("Failed clear permissions fir dashboard", + slog.String("dashboard", fmt.Sprintf("%s%s", link.Dashboard.FolderTitle, link.Dashboard.Title))) + } + } + return nil +} diff --git a/internal/service/dashboards.go b/internal/service/dashboards.go index 9a558ea8..b8f2258e 100644 --- a/internal/service/dashboards.go +++ b/internal/service/dashboards.go @@ -600,7 +600,7 @@ func (s *DashNGoImpl) UploadDashboards(filterReq filters.Filter) { } // Extract Folder Name based on path - folderName, err = getFolderFromResourcePath(s.grafanaConf.Storage, file) + folderName, err = getFolderFromResourcePath(s.grafanaConf.Storage, file, config.DashboardResource) if err != nil { slog.Warn("unable to determine dashboard folder name, falling back on default") } diff --git a/internal/service/mocks/DashboardPermissionsApi.go b/internal/service/mocks/DashboardPermissionsApi.go new file mode 100644 index 00000000..f1e53d14 --- /dev/null +++ b/internal/service/mocks/DashboardPermissionsApi.go @@ -0,0 +1,257 @@ +// Code generated by mockery v2.42.0. DO NOT EDIT. + +package mocks + +import ( + filters "github.com/esnet/gdg/internal/service/filters" + mock "github.com/stretchr/testify/mock" + + service "github.com/esnet/gdg/internal/service" +) + +// DashboardPermissionsApi is an autogenerated mock type for the DashboardPermissionsApi type +type DashboardPermissionsApi struct { + mock.Mock +} + +type DashboardPermissionsApi_Expecter struct { + mock *mock.Mock +} + +func (_m *DashboardPermissionsApi) EXPECT() *DashboardPermissionsApi_Expecter { + return &DashboardPermissionsApi_Expecter{mock: &_m.Mock} +} + +// ClearDashboardPermissions provides a mock function with given fields: filterReq +func (_m *DashboardPermissionsApi) ClearDashboardPermissions(filterReq filters.Filter) error { + ret := _m.Called(filterReq) + + if len(ret) == 0 { + panic("no return value specified for ClearDashboardPermissions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(filters.Filter) error); ok { + r0 = rf(filterReq) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DashboardPermissionsApi_ClearDashboardPermissions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearDashboardPermissions' +type DashboardPermissionsApi_ClearDashboardPermissions_Call struct { + *mock.Call +} + +// ClearDashboardPermissions is a helper method to define mock.On call +// - filterReq filters.Filter +func (_e *DashboardPermissionsApi_Expecter) ClearDashboardPermissions(filterReq interface{}) *DashboardPermissionsApi_ClearDashboardPermissions_Call { + return &DashboardPermissionsApi_ClearDashboardPermissions_Call{Call: _e.mock.On("ClearDashboardPermissions", filterReq)} +} + +func (_c *DashboardPermissionsApi_ClearDashboardPermissions_Call) Run(run func(filterReq filters.Filter)) *DashboardPermissionsApi_ClearDashboardPermissions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(filters.Filter)) + }) + return _c +} + +func (_c *DashboardPermissionsApi_ClearDashboardPermissions_Call) Return(_a0 error) *DashboardPermissionsApi_ClearDashboardPermissions_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DashboardPermissionsApi_ClearDashboardPermissions_Call) RunAndReturn(run func(filters.Filter) error) *DashboardPermissionsApi_ClearDashboardPermissions_Call { + _c.Call.Return(run) + return _c +} + +// DownloadDashboardPermissions provides a mock function with given fields: filterReq +func (_m *DashboardPermissionsApi) DownloadDashboardPermissions(filterReq filters.Filter) ([]string, error) { + ret := _m.Called(filterReq) + + if len(ret) == 0 { + panic("no return value specified for DownloadDashboardPermissions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(filters.Filter) ([]string, error)); ok { + return rf(filterReq) + } + if rf, ok := ret.Get(0).(func(filters.Filter) []string); ok { + r0 = rf(filterReq) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(filters.Filter) error); ok { + r1 = rf(filterReq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DashboardPermissionsApi_DownloadDashboardPermissions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DownloadDashboardPermissions' +type DashboardPermissionsApi_DownloadDashboardPermissions_Call struct { + *mock.Call +} + +// DownloadDashboardPermissions is a helper method to define mock.On call +// - filterReq filters.Filter +func (_e *DashboardPermissionsApi_Expecter) DownloadDashboardPermissions(filterReq interface{}) *DashboardPermissionsApi_DownloadDashboardPermissions_Call { + return &DashboardPermissionsApi_DownloadDashboardPermissions_Call{Call: _e.mock.On("DownloadDashboardPermissions", filterReq)} +} + +func (_c *DashboardPermissionsApi_DownloadDashboardPermissions_Call) Run(run func(filterReq filters.Filter)) *DashboardPermissionsApi_DownloadDashboardPermissions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(filters.Filter)) + }) + return _c +} + +func (_c *DashboardPermissionsApi_DownloadDashboardPermissions_Call) Return(_a0 []string, _a1 error) *DashboardPermissionsApi_DownloadDashboardPermissions_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DashboardPermissionsApi_DownloadDashboardPermissions_Call) RunAndReturn(run func(filters.Filter) ([]string, error)) *DashboardPermissionsApi_DownloadDashboardPermissions_Call { + _c.Call.Return(run) + return _c +} + +// ListDashboardPermissions provides a mock function with given fields: filterReq +func (_m *DashboardPermissionsApi) ListDashboardPermissions(filterReq filters.Filter) ([]service.DashboardAndPermissions, error) { + ret := _m.Called(filterReq) + + if len(ret) == 0 { + panic("no return value specified for ListDashboardPermissions") + } + + var r0 []service.DashboardAndPermissions + var r1 error + if rf, ok := ret.Get(0).(func(filters.Filter) ([]service.DashboardAndPermissions, error)); ok { + return rf(filterReq) + } + if rf, ok := ret.Get(0).(func(filters.Filter) []service.DashboardAndPermissions); ok { + r0 = rf(filterReq) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]service.DashboardAndPermissions) + } + } + + if rf, ok := ret.Get(1).(func(filters.Filter) error); ok { + r1 = rf(filterReq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DashboardPermissionsApi_ListDashboardPermissions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListDashboardPermissions' +type DashboardPermissionsApi_ListDashboardPermissions_Call struct { + *mock.Call +} + +// ListDashboardPermissions is a helper method to define mock.On call +// - filterReq filters.Filter +func (_e *DashboardPermissionsApi_Expecter) ListDashboardPermissions(filterReq interface{}) *DashboardPermissionsApi_ListDashboardPermissions_Call { + return &DashboardPermissionsApi_ListDashboardPermissions_Call{Call: _e.mock.On("ListDashboardPermissions", filterReq)} +} + +func (_c *DashboardPermissionsApi_ListDashboardPermissions_Call) Run(run func(filterReq filters.Filter)) *DashboardPermissionsApi_ListDashboardPermissions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(filters.Filter)) + }) + return _c +} + +func (_c *DashboardPermissionsApi_ListDashboardPermissions_Call) Return(_a0 []service.DashboardAndPermissions, _a1 error) *DashboardPermissionsApi_ListDashboardPermissions_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DashboardPermissionsApi_ListDashboardPermissions_Call) RunAndReturn(run func(filters.Filter) ([]service.DashboardAndPermissions, error)) *DashboardPermissionsApi_ListDashboardPermissions_Call { + _c.Call.Return(run) + return _c +} + +// UploadDashboardPermissions provides a mock function with given fields: filterReq +func (_m *DashboardPermissionsApi) UploadDashboardPermissions(filterReq filters.Filter) ([]string, error) { + ret := _m.Called(filterReq) + + if len(ret) == 0 { + panic("no return value specified for UploadDashboardPermissions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(filters.Filter) ([]string, error)); ok { + return rf(filterReq) + } + if rf, ok := ret.Get(0).(func(filters.Filter) []string); ok { + r0 = rf(filterReq) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(filters.Filter) error); ok { + r1 = rf(filterReq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DashboardPermissionsApi_UploadDashboardPermissions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UploadDashboardPermissions' +type DashboardPermissionsApi_UploadDashboardPermissions_Call struct { + *mock.Call +} + +// UploadDashboardPermissions is a helper method to define mock.On call +// - filterReq filters.Filter +func (_e *DashboardPermissionsApi_Expecter) UploadDashboardPermissions(filterReq interface{}) *DashboardPermissionsApi_UploadDashboardPermissions_Call { + return &DashboardPermissionsApi_UploadDashboardPermissions_Call{Call: _e.mock.On("UploadDashboardPermissions", filterReq)} +} + +func (_c *DashboardPermissionsApi_UploadDashboardPermissions_Call) Run(run func(filterReq filters.Filter)) *DashboardPermissionsApi_UploadDashboardPermissions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(filters.Filter)) + }) + return _c +} + +func (_c *DashboardPermissionsApi_UploadDashboardPermissions_Call) Return(_a0 []string, _a1 error) *DashboardPermissionsApi_UploadDashboardPermissions_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DashboardPermissionsApi_UploadDashboardPermissions_Call) RunAndReturn(run func(filters.Filter) ([]string, error)) *DashboardPermissionsApi_UploadDashboardPermissions_Call { + _c.Call.Return(run) + return _c +} + +// NewDashboardPermissionsApi creates a new instance of DashboardPermissionsApi. 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 NewDashboardPermissionsApi(t interface { + mock.TestingT + Cleanup(func()) +}) *DashboardPermissionsApi { + mock := &DashboardPermissionsApi{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/service/mocks/GrafanaService.go b/internal/service/mocks/GrafanaService.go index f88154bd..aac52f37 100644 --- a/internal/service/mocks/GrafanaService.go +++ b/internal/service/mocks/GrafanaService.go @@ -10,6 +10,8 @@ import ( models "github.com/grafana/grafana-openapi-client-go/models" + service "github.com/esnet/gdg/internal/service" + types "github.com/esnet/gdg/internal/service/types" ) @@ -74,6 +76,52 @@ func (_c *GrafanaService_AddUserToOrg_Call) RunAndReturn(run func(string, string return _c } +// ClearDashboardPermissions provides a mock function with given fields: filterReq +func (_m *GrafanaService) ClearDashboardPermissions(filterReq filters.Filter) error { + ret := _m.Called(filterReq) + + if len(ret) == 0 { + panic("no return value specified for ClearDashboardPermissions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(filters.Filter) error); ok { + r0 = rf(filterReq) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GrafanaService_ClearDashboardPermissions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearDashboardPermissions' +type GrafanaService_ClearDashboardPermissions_Call struct { + *mock.Call +} + +// ClearDashboardPermissions is a helper method to define mock.On call +// - filterReq filters.Filter +func (_e *GrafanaService_Expecter) ClearDashboardPermissions(filterReq interface{}) *GrafanaService_ClearDashboardPermissions_Call { + return &GrafanaService_ClearDashboardPermissions_Call{Call: _e.mock.On("ClearDashboardPermissions", filterReq)} +} + +func (_c *GrafanaService_ClearDashboardPermissions_Call) Run(run func(filterReq filters.Filter)) *GrafanaService_ClearDashboardPermissions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(filters.Filter)) + }) + return _c +} + +func (_c *GrafanaService_ClearDashboardPermissions_Call) Return(_a0 error) *GrafanaService_ClearDashboardPermissions_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *GrafanaService_ClearDashboardPermissions_Call) RunAndReturn(run func(filters.Filter) error) *GrafanaService_ClearDashboardPermissions_Call { + _c.Call.Return(run) + return _c +} + // CreateAPIKey provides a mock function with given fields: name, role, expiration func (_m *GrafanaService) CreateAPIKey(name string, role string, expiration int64) (*models.NewAPIKeyResult, error) { ret := _m.Called(name, role, expiration) @@ -885,6 +933,64 @@ func (_c *GrafanaService_DownloadConnections_Call) RunAndReturn(run func(filters return _c } +// DownloadDashboardPermissions provides a mock function with given fields: filterReq +func (_m *GrafanaService) DownloadDashboardPermissions(filterReq filters.Filter) ([]string, error) { + ret := _m.Called(filterReq) + + if len(ret) == 0 { + panic("no return value specified for DownloadDashboardPermissions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(filters.Filter) ([]string, error)); ok { + return rf(filterReq) + } + if rf, ok := ret.Get(0).(func(filters.Filter) []string); ok { + r0 = rf(filterReq) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(filters.Filter) error); ok { + r1 = rf(filterReq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GrafanaService_DownloadDashboardPermissions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DownloadDashboardPermissions' +type GrafanaService_DownloadDashboardPermissions_Call struct { + *mock.Call +} + +// DownloadDashboardPermissions is a helper method to define mock.On call +// - filterReq filters.Filter +func (_e *GrafanaService_Expecter) DownloadDashboardPermissions(filterReq interface{}) *GrafanaService_DownloadDashboardPermissions_Call { + return &GrafanaService_DownloadDashboardPermissions_Call{Call: _e.mock.On("DownloadDashboardPermissions", filterReq)} +} + +func (_c *GrafanaService_DownloadDashboardPermissions_Call) Run(run func(filterReq filters.Filter)) *GrafanaService_DownloadDashboardPermissions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(filters.Filter)) + }) + return _c +} + +func (_c *GrafanaService_DownloadDashboardPermissions_Call) Return(_a0 []string, _a1 error) *GrafanaService_DownloadDashboardPermissions_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *GrafanaService_DownloadDashboardPermissions_Call) RunAndReturn(run func(filters.Filter) ([]string, error)) *GrafanaService_DownloadDashboardPermissions_Call { + _c.Call.Return(run) + return _c +} + // DownloadDashboards provides a mock function with given fields: filter func (_m *GrafanaService) DownloadDashboards(filter filters.Filter) []string { ret := _m.Called(filter) @@ -1745,6 +1851,64 @@ func (_c *GrafanaService_ListConnections_Call) RunAndReturn(run func(filters.Fil return _c } +// ListDashboardPermissions provides a mock function with given fields: filterReq +func (_m *GrafanaService) ListDashboardPermissions(filterReq filters.Filter) ([]service.DashboardAndPermissions, error) { + ret := _m.Called(filterReq) + + if len(ret) == 0 { + panic("no return value specified for ListDashboardPermissions") + } + + var r0 []service.DashboardAndPermissions + var r1 error + if rf, ok := ret.Get(0).(func(filters.Filter) ([]service.DashboardAndPermissions, error)); ok { + return rf(filterReq) + } + if rf, ok := ret.Get(0).(func(filters.Filter) []service.DashboardAndPermissions); ok { + r0 = rf(filterReq) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]service.DashboardAndPermissions) + } + } + + if rf, ok := ret.Get(1).(func(filters.Filter) error); ok { + r1 = rf(filterReq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GrafanaService_ListDashboardPermissions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListDashboardPermissions' +type GrafanaService_ListDashboardPermissions_Call struct { + *mock.Call +} + +// ListDashboardPermissions is a helper method to define mock.On call +// - filterReq filters.Filter +func (_e *GrafanaService_Expecter) ListDashboardPermissions(filterReq interface{}) *GrafanaService_ListDashboardPermissions_Call { + return &GrafanaService_ListDashboardPermissions_Call{Call: _e.mock.On("ListDashboardPermissions", filterReq)} +} + +func (_c *GrafanaService_ListDashboardPermissions_Call) Run(run func(filterReq filters.Filter)) *GrafanaService_ListDashboardPermissions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(filters.Filter)) + }) + return _c +} + +func (_c *GrafanaService_ListDashboardPermissions_Call) Return(_a0 []service.DashboardAndPermissions, _a1 error) *GrafanaService_ListDashboardPermissions_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *GrafanaService_ListDashboardPermissions_Call) RunAndReturn(run func(filters.Filter) ([]service.DashboardAndPermissions, error)) *GrafanaService_ListDashboardPermissions_Call { + _c.Call.Return(run) + return _c +} + // ListDashboards provides a mock function with given fields: filter func (_m *GrafanaService) ListDashboards(filter filters.Filter) []*models.Hit { ret := _m.Called(filter) @@ -2666,6 +2830,64 @@ func (_c *GrafanaService_UploadConnections_Call) RunAndReturn(run func(filters.F return _c } +// UploadDashboardPermissions provides a mock function with given fields: filterReq +func (_m *GrafanaService) UploadDashboardPermissions(filterReq filters.Filter) ([]string, error) { + ret := _m.Called(filterReq) + + if len(ret) == 0 { + panic("no return value specified for UploadDashboardPermissions") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(filters.Filter) ([]string, error)); ok { + return rf(filterReq) + } + if rf, ok := ret.Get(0).(func(filters.Filter) []string); ok { + r0 = rf(filterReq) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(filters.Filter) error); ok { + r1 = rf(filterReq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GrafanaService_UploadDashboardPermissions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UploadDashboardPermissions' +type GrafanaService_UploadDashboardPermissions_Call struct { + *mock.Call +} + +// UploadDashboardPermissions is a helper method to define mock.On call +// - filterReq filters.Filter +func (_e *GrafanaService_Expecter) UploadDashboardPermissions(filterReq interface{}) *GrafanaService_UploadDashboardPermissions_Call { + return &GrafanaService_UploadDashboardPermissions_Call{Call: _e.mock.On("UploadDashboardPermissions", filterReq)} +} + +func (_c *GrafanaService_UploadDashboardPermissions_Call) Run(run func(filterReq filters.Filter)) *GrafanaService_UploadDashboardPermissions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(filters.Filter)) + }) + return _c +} + +func (_c *GrafanaService_UploadDashboardPermissions_Call) Return(_a0 []string, _a1 error) *GrafanaService_UploadDashboardPermissions_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *GrafanaService_UploadDashboardPermissions_Call) RunAndReturn(run func(filters.Filter) ([]string, error)) *GrafanaService_UploadDashboardPermissions_Call { + _c.Call.Return(run) + return _c +} + // UploadDashboards provides a mock function with given fields: filter func (_m *GrafanaService) UploadDashboards(filter filters.Filter) { _m.Called(filter) diff --git a/internal/service/mocks/Storage.go b/internal/service/mocks/Storage.go index 920d189c..ee524f0b 100644 --- a/internal/service/mocks/Storage.go +++ b/internal/service/mocks/Storage.go @@ -76,6 +76,51 @@ func (_c *Storage_FindAllFiles_Call) RunAndReturn(run func(string, bool) ([]stri return _c } +// GetPrefix provides a mock function with given fields: +func (_m *Storage) GetPrefix() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetPrefix") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Storage_GetPrefix_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPrefix' +type Storage_GetPrefix_Call struct { + *mock.Call +} + +// GetPrefix is a helper method to define mock.On call +func (_e *Storage_Expecter) GetPrefix() *Storage_GetPrefix_Call { + return &Storage_GetPrefix_Call{Call: _e.mock.On("GetPrefix")} +} + +func (_c *Storage_GetPrefix_Call) Run(run func()) *Storage_GetPrefix_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Storage_GetPrefix_Call) Return(_a0 string) *Storage_GetPrefix_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Storage_GetPrefix_Call) RunAndReturn(run func() string) *Storage_GetPrefix_Call { + _c.Call.Return(run) + return _c +} + // Name provides a mock function with given fields: func (_m *Storage) Name() string { ret := _m.Called()