From cfe8317d45101c194c422b2ac3b87379b980bdf6 Mon Sep 17 00:00:00 2001 From: Jeff Levin Date: Tue, 2 Jul 2024 22:08:57 -0800 Subject: [PATCH 01/39] Add auth spans and remove deduplication code for scopes (#89804) Adds more spans for timing in accesscontrol and remove permission deduplicating code after benchmarking --------- Signed-off-by: Dave Henderson Co-authored-by: Dave Henderson Co-authored-by: Ieva --- pkg/api/common_test.go | 4 +- pkg/api/folder_test.go | 7 +-- pkg/api/org_test.go | 7 +-- pkg/api/plugin_resource_test.go | 8 +-- pkg/api/plugins_test.go | 2 +- pkg/api/quota_test.go | 3 +- pkg/services/accesscontrol/accesscontrol.go | 41 +++++++------- .../accesscontrol/accesscontrol_test.go | 54 +++++++++++++++++++ pkg/services/accesscontrol/acimpl/service.go | 49 +++++++++++++++-- .../accesscontrol/acimpl/service_test.go | 1 + pkg/services/accesscontrol/api/api.go | 2 +- pkg/services/accesscontrol/mock/mock.go | 2 +- .../resourcepermissions/api_test.go | 10 ++-- .../annotationsimpl/annotations_test.go | 2 +- pkg/services/authn/authnimpl/registration.go | 10 ++-- pkg/services/authn/authnimpl/service.go | 9 ++++ .../authn/authnimpl/sync/oauth_token_sync.go | 8 ++- .../authnimpl/sync/oauth_token_sync_test.go | 2 + pkg/services/authn/authnimpl/sync/org_sync.go | 18 +++++-- .../authn/authnimpl/sync/org_sync_test.go | 3 ++ .../authn/authnimpl/sync/rbac_sync.go | 27 +++++++--- .../authn/authnimpl/sync/rbac_sync_test.go | 11 ++-- .../authn/authnimpl/sync/user_sync.go | 33 ++++++++++-- .../authn/authnimpl/sync/user_sync_test.go | 9 ++-- .../database/database_folder_test.go | 2 +- pkg/services/grpcserver/interceptors/auth.go | 2 +- pkg/services/ldap/api/service_test.go | 3 +- .../database/database_test.go | 6 +-- pkg/services/searchV2/service.go | 2 +- pkg/services/serviceaccounts/api/api_test.go | 11 ++-- .../serviceaccounts/api/token_test.go | 7 +-- .../sqlstore/permissions/dashboard_test.go | 12 ++--- .../permissions/dashboards_bench_test.go | 2 +- .../sqlstore/searchstore/search_test.go | 2 +- pkg/services/ssosettings/api/api_test.go | 3 +- .../team/teamapi/team_members_test.go | 2 +- 36 files changed, 279 insertions(+), 97 deletions(-) diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 9e7c2f6756c..91efe95161a 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -250,12 +250,12 @@ func setupScenarioContextSamlLogout(t *testing.T, url string) *scenarioContext { // FIXME: This user should not be anonymous func authedUserWithPermissions(userID, orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser { - return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}} + return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}} } // FIXME: This user should not be anonymous func userWithPermissions(orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser { - return &user.SignedInUser{IsAnonymous: true, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}} + return &user.SignedInUser{IsAnonymous: true, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}} } func setupSimpleHTTPServer(features featuremgmt.FeatureToggles) *HTTPServer { diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index dc5b2df4458..752e649a030 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -1,6 +1,7 @@ package api import ( + "context" "encoding/json" "fmt" "net/http" @@ -282,7 +283,7 @@ func TestHTTPServer_FolderMetadata(t *testing.T) { req := server.NewGetRequest("/api/folders/folderUid?accesscontrol=true") webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ - 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + 1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{ {Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll}, {Action: dashboards.ActionFoldersWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("folderUid")}, }), @@ -311,7 +312,7 @@ func TestHTTPServer_FolderMetadata(t *testing.T) { req := server.NewGetRequest("/api/folders/folderUid?accesscontrol=true") webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ - 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + 1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{ {Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll}, {Action: dashboards.ActionFoldersWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("parentUid")}, {Action: dashboards.ActionDashboardsCreate, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("folderUid")}, @@ -336,7 +337,7 @@ func TestHTTPServer_FolderMetadata(t *testing.T) { req := server.NewGetRequest("/api/folders/folderUid") webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ - 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + 1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{ {Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll}, {Action: dashboards.ActionFoldersWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID("folderUid")}, }), diff --git a/pkg/api/org_test.go b/pkg/api/org_test.go index fcbc7f79bbf..faeeb4bdbb7 100644 --- a/pkg/api/org_test.go +++ b/pkg/api/org_test.go @@ -1,6 +1,7 @@ package api import ( + "context" "net/http" "strings" "testing" @@ -220,7 +221,7 @@ func TestAPIEndpoint_DeleteOrgs(t *testing.T) { expectedIdentity := &authn.Identity{ OrgID: 1, Permissions: map[int64]map[string][]string{ - 1: accesscontrol.GroupScopesByAction(tt.permission), + 1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permission), }, } @@ -269,8 +270,8 @@ func TestAPIEndpoint_GetOrg(t *testing.T) { ID: authn.MustParseNamespaceID("user:1"), OrgID: 1, Permissions: map[int64]map[string][]string{ - 0: accesscontrol.GroupScopesByAction(tt.permissions), - 1: accesscontrol.GroupScopesByAction(tt.permissions), + 0: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions), + 1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions), }, } diff --git a/pkg/api/plugin_resource_test.go b/pkg/api/plugin_resource_test.go index ed6db916c04..c966ae8e97e 100644 --- a/pkg/api/plugin_resource_test.go +++ b/pkg/api/plugin_resource_test.go @@ -70,7 +70,7 @@ func TestCallResource(t *testing.T) { t.Run("Test successful response is received for valid request", func(t *testing.T) { req := srv.NewPostRequest("/api/plugins/grafana-testdata-datasource/resources/test", strings.NewReader(`{"test": "true"}`)) webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ - 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + 1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{ {Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, }), }}) @@ -92,7 +92,7 @@ func TestCallResource(t *testing.T) { t.Run("Test successful response is received for valid request with the colon character", func(t *testing.T) { req := srv.NewPostRequest("/api/plugins/grafana-testdata-datasource/resources/test-*,*:test-*/_mapping", strings.NewReader(`{"test": "true"}`)) webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ - 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + 1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{ {Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, }), }}) @@ -146,7 +146,7 @@ func TestCallResource(t *testing.T) { t.Run(tc.name, func(t *testing.T) { req := srv.NewPostRequest(tc.url, strings.NewReader(`{"test": "true"}`)) webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ - 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + 1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{ {Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, }), }}) @@ -192,7 +192,7 @@ func TestCallResource(t *testing.T) { t.Run("Test error is properly propagated to API response", func(t *testing.T) { req := srv.NewGetRequest("/api/plugins/grafana-testdata-datasource/resources/scenarios") webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ - 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + 1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{ {Action: pluginaccesscontrol.ActionAppAccess, Scope: pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, }), }}) diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index 5adf5440a7d..e47f1ec9bb6 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -104,7 +104,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) { Permissions: map[int64]map[string][]string{}, OrgRoles: map[int64]org.RoleType{}, } - expectedIdentity.Permissions[tc.permissionOrg] = ac.GroupScopesByAction(tc.permissions) + expectedIdentity.Permissions[tc.permissionOrg] = ac.GroupScopesByActionContext(context.Background(), tc.permissions) hs.authnService = &authntest.FakeService{ ExpectedIdentity: expectedIdentity, } diff --git a/pkg/api/quota_test.go b/pkg/api/quota_test.go index 09609b127cf..3feb79c31ff 100644 --- a/pkg/api/quota_test.go +++ b/pkg/api/quota_test.go @@ -1,6 +1,7 @@ package api import ( + "context" "fmt" "net/http" "strings" @@ -156,7 +157,7 @@ func TestAPIEndpoint_PutOrgQuotas(t *testing.T) { Permissions: map[int64]map[string][]string{}, } for orgID, permissions := range tt.permissions { - expectedIdentity.Permissions[orgID] = accesscontrol.GroupScopesByAction(permissions) + expectedIdentity.Permissions[orgID] = accesscontrol.GroupScopesByActionContext(context.Background(), permissions) } server := SetupAPITestServer(t, func(hs *HTTPServer) { diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index 66593e0335b..0c2b31efe42 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -12,8 +12,13 @@ import ( contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/accesscontrol") + type AccessControl interface { // Evaluate evaluates access to the given resources. Evaluate(ctx context.Context, user identity.Requester, evaluator Evaluator) (bool, error) @@ -232,30 +237,24 @@ func BuildPermissionsMap(permissions []Permission) map[string]bool { } // GroupScopesByAction will group scopes on action +// +// Deprecated: use GroupScopesByActionContext instead func GroupScopesByAction(permissions []Permission) map[string][]string { - // Use a map to deduplicate scopes. - // User can have the same permission from multiple sources (e.g. team, basic role, directly assigned etc). - // User will also have duplicate permissions if action sets are used, as we will be double writing permissions for a while. - m := make(map[string]map[string]struct{}) + return GroupScopesByActionContext(context.Background(), permissions) +} + +// GroupScopesByAction will group scopes on action +func GroupScopesByActionContext(ctx context.Context, permissions []Permission) map[string][]string { + _, span := tracer.Start(ctx, "accesscontrol.GroupScopesByActionContext", trace.WithAttributes( + attribute.Int("permissions_count", len(permissions)), + )) + defer span.End() + + m := make(map[string][]string) for i := range permissions { - if _, ok := m[permissions[i].Action]; !ok { - m[permissions[i].Action] = make(map[string]struct{}) - } - m[permissions[i].Action][permissions[i].Scope] = struct{}{} + m[permissions[i].Action] = append(m[permissions[i].Action], permissions[i].Scope) } - - res := make(map[string][]string, len(m)) - for action, scopes := range m { - scopeList := make([]string, len(scopes)) - i := 0 - for scope := range scopes { - scopeList[i] = scope - i++ - } - res[action] = scopeList - } - - return res + return m } // Reduce will reduce a list of permissions to its minimal form, grouping scopes by action diff --git a/pkg/services/accesscontrol/accesscontrol_test.go b/pkg/services/accesscontrol/accesscontrol_test.go index b39ca3b8a80..f56dff456a0 100644 --- a/pkg/services/accesscontrol/accesscontrol_test.go +++ b/pkg/services/accesscontrol/accesscontrol_test.go @@ -1,8 +1,11 @@ package accesscontrol import ( + "context" + "fmt" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" // this import is needed for github.com/grafana/grafana/pkg/web hack_wrap to work @@ -125,3 +128,54 @@ func TestReduce(t *testing.T) { }) } } + +func TestGroupScopesByActionContext(t *testing.T) { + // test data = 3 actions with 2+i scopes each, including a duplicate + permissions := []Permission{} + for i := 0; i < 3; i++ { + for j := 0; j < 2+i; j++ { + permissions = append(permissions, Permission{ + Action: fmt.Sprintf("action:%d", i), + Scope: fmt.Sprintf("scope:%d_%d", i, j), + }) + } + } + + expected := map[string][]string{} + for i := 0; i < 3; i++ { + action := fmt.Sprintf("action:%d", i) + scopes := []string{} + for j := 0; j < 2+i; j++ { + scopes = append(scopes, fmt.Sprintf("scope:%d_%d", i, j)) + } + expected[action] = scopes + } + + assert.EqualValues(t, expected, GroupScopesByActionContext(context.Background(), permissions)) +} + +func BenchmarkGroupScopesByAction(b *testing.B) { + // create a big list of permissions with a bunch of duplicates + permissions := []Permission{} + for i := 0; i < 100; i++ { + for j := 0; j < 500+i; j++ { + permissions = append(permissions, Permission{ + Action: fmt.Sprintf("action:%d", i), + Scope: fmt.Sprintf("scope:%d_%d", i, j), + }) + } + // add duplicate scopes + for j := 0; j < 10; j++ { + permissions = append(permissions, Permission{ + Action: fmt.Sprintf("action:%d", i), + Scope: fmt.Sprintf("scope:%d_%d", i, 0), + }) + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + GroupScopesByActionContext(context.Background(), permissions) + } +} diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index 610d8528858..69f392bb40f 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -114,6 +114,7 @@ func (s *Service) GetUsageStats(_ context.Context) map[string]any { func (s *Service) GetUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) { ctx, span := s.tracer.Start(ctx, "authz.GetUserPermissionsOSS") defer span.End() + timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary) defer timer.ObserveDuration() @@ -125,6 +126,9 @@ func (s *Service) GetUserPermissions(ctx context.Context, user identity.Requeste } func (s *Service) getUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) { + ctx, span := s.tracer.Start(ctx, "authz.getUserPermissions") + defer span.End() + permissions := make([]accesscontrol.Permission, 0) for _, builtin := range accesscontrol.GetOrgRoles(user) { if basicRole, ok := s.roles[builtin]; ok { @@ -265,8 +269,10 @@ func (s *Service) getCachedBasicRolesPermissions(ctx context.Context, user ident defer span.End() basicRoles := accesscontrol.GetOrgRoles(user) + span.SetAttributes(attribute.Int("roles", len(basicRoles))) for _, role := range basicRoles { perms, err := s.getCachedBasicRolePermissions(ctx, role, user.GetOrgID(), options) + span.SetAttributes(attribute.Int(fmt.Sprintf("role_%s_permissions", role), len(perms))) if err != nil { return nil, err } @@ -301,12 +307,13 @@ type getPermissionsFunc = func(ctx context.Context) ([]accesscontrol.Permission, // Generic method for getting various permissions from cache func (s *Service) getCachedPermissions(ctx context.Context, key string, getPermissionsFn getPermissionsFunc, options accesscontrol.Options) ([]accesscontrol.Permission, error) { - _, span := s.tracer.Start(ctx, "authz.getCachedPermissions") + ctx, span := s.tracer.Start(ctx, "authz.getCachedPermissions") defer span.End() if !options.ReloadCache { permissions, ok := s.cache.Get(key) if ok { + span.SetAttributes(attribute.Int("num_permissions_cached", len(permissions.([]accesscontrol.Permission)))) metrics.MAccessPermissionsCacheUsage.WithLabelValues(accesscontrol.CacheHit).Inc() return permissions.([]accesscontrol.Permission), nil } @@ -315,6 +322,7 @@ func (s *Service) getCachedPermissions(ctx context.Context, key string, getPermi span.AddEvent("cache miss") metrics.MAccessPermissionsCacheUsage.WithLabelValues(accesscontrol.CacheMiss).Inc() permissions, err := getPermissionsFn(ctx) + span.SetAttributes(attribute.Int("num_permissions_fetched", len(permissions))) if err != nil { return nil, err } @@ -338,6 +346,7 @@ func (s *Service) getCachedTeamsPermissions(ctx context.Context, user identity.R teamPermissions, ok := s.cache.Get(key) if ok { metrics.MAccessPermissionsCacheUsage.WithLabelValues(accesscontrol.CacheHit).Inc() + span.SetAttributes(attribute.Int("num_permissions_cached", len(teamPermissions.([]accesscontrol.Permission)))) permissions = append(permissions, teamPermissions.([]accesscontrol.Permission)...) } else { miss = append(miss, teamID) @@ -349,6 +358,7 @@ func (s *Service) getCachedTeamsPermissions(ctx context.Context, user identity.R span.AddEvent("cache miss") metrics.MAccessPermissionsCacheUsage.WithLabelValues(accesscontrol.CacheMiss).Inc() teamsPermissions, err := s.getTeamsPermissions(ctx, miss, orgID) + span.SetAttributes(attribute.Int("num_permissions_fetched", len(teamsPermissions))) if err != nil { return nil, err } @@ -369,10 +379,16 @@ func (s *Service) ClearUserPermissionCache(user identity.Requester) { } func (s *Service) DeleteUserPermissions(ctx context.Context, orgID int64, userID int64) error { + ctx, span := s.tracer.Start(ctx, "authz.DeleteUserPermissions") + defer span.End() + return s.store.DeleteUserPermissions(ctx, orgID, userID) } func (s *Service) DeleteTeamPermissions(ctx context.Context, orgID int64, teamID int64) error { + ctx, span := s.tracer.Start(ctx, "authz.DeleteTeamPermissions") + defer span.End() + return s.store.DeleteTeamPermissions(ctx, orgID, teamID) } @@ -398,6 +414,9 @@ func (s *Service) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistrat // RegisterFixedRoles registers all declared roles in RAM func (s *Service) RegisterFixedRoles(ctx context.Context) error { + _, span := s.tracer.Start(ctx, "authz.RegisterFixedRoles") + defer span.End() + s.registrations.Range(func(registration accesscontrol.RoleRegistration) bool { for br := range accesscontrol.BuiltInRolesWithParents(registration.Grants) { if basicRole, ok := s.roles[br]; ok { @@ -421,6 +440,9 @@ func (s *Service) RegisterFixedRoles(ctx context.Context) error { // DeclarePluginRoles allow the caller to declare, to the service, plugin roles and their assignments // to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin" func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs []plugins.RoleRegistration) error { + ctx, span := s.tracer.Start(ctx, "authz.DeclarePluginRoles") + defer span.End() + // Protect behind feature toggle if !s.features.IsEnabled(ctx, featuremgmt.FlagAccessControlOnCall) { return nil @@ -455,6 +477,9 @@ func GetActionFilter(options accesscontrol.SearchOptions) func(action string) bo // SearchUsersPermissions returns all users' permissions filtered by action prefixes func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Requester, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) { + ctx, span := s.tracer.Start(ctx, "authz.SearchUsersPermissions") + defer span.End() + // Limit roles to available in OSS options.RolePrefixes = OSSRolesPrefixes if options.NamespacedID != "" { @@ -566,6 +591,9 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque } func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) { + ctx, span := s.tracer.Start(ctx, "authz.SearchUserPermissions") + defer span.End() + timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary) defer timer.ObserveDuration() @@ -573,13 +601,16 @@ func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, search return nil, fmt.Errorf("expected namespaced ID to be specified") } - if permissions, success := s.searchUserPermissionsFromCache(orgID, searchOptions); success { + if permissions, success := s.searchUserPermissionsFromCache(ctx, orgID, searchOptions); success { return permissions, nil } return s.searchUserPermissions(ctx, orgID, searchOptions) } func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) { + ctx, span := s.tracer.Start(ctx, "authz.searchUserPermissions") + defer span.End() + userID, err := searchOptions.ComputeUserID() if err != nil { return nil, err @@ -629,7 +660,10 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search return permissions, nil } -func (s *Service) searchUserPermissionsFromCache(orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, bool) { +func (s *Service) searchUserPermissionsFromCache(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, bool) { + _, span := s.tracer.Start(ctx, "authz.searchUserPermissionsFromCache") + defer span.End() + userID, err := searchOptions.ComputeUserID() if err != nil { return nil, false @@ -669,6 +703,9 @@ func PermissionMatchesSearchOptions(permission accesscontrol.Permission, searchO } func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { + ctx, span := s.tracer.Start(ctx, "authz.SaveExternalServiceRole") + defer span.End() + if !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { s.log.Debug("Registering an external service role is behind a feature flag, enable it to use this feature.") return nil @@ -682,6 +719,9 @@ func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol } func (s *Service) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error { + ctx, span := s.tracer.Start(ctx, "authz.DeleteExternalServiceRole") + defer span.End() + if !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { s.log.Debug("Deleting an external service role is behind a feature flag, enable it to use this feature.") return nil @@ -697,6 +737,9 @@ func (*Service) SyncUserRoles(ctx context.Context, orgID int64, cmd accesscontro } func (s *Service) GetRoleByName(ctx context.Context, orgID int64, roleName string) (*accesscontrol.RoleDTO, error) { + _, span := s.tracer.Start(ctx, "authz.GetRoleByName") + defer span.End() + err := accesscontrol.ErrRoleNotFound if _, ok := s.roles[roleName]; ok { return nil, err diff --git a/pkg/services/accesscontrol/acimpl/service_test.go b/pkg/services/accesscontrol/acimpl/service_test.go index 96a87accd30..7e7a6cdd43c 100644 --- a/pkg/services/accesscontrol/acimpl/service_test.go +++ b/pkg/services/accesscontrol/acimpl/service_test.go @@ -42,6 +42,7 @@ func setupTestEnv(t testing.TB) *Service { registrations: accesscontrol.RegistrationList{}, roles: accesscontrol.BuildBasicRoleDefinitions(), store: database.ProvideService(db.InitTestDB(t)), + tracer: tracing.InitializeTracerForTest(), } require.NoError(t, ac.RegisterFixedRoles(context.Background())) return ac diff --git a/pkg/services/accesscontrol/api/api.go b/pkg/services/accesscontrol/api/api.go index 67e7951267b..bd7caa6e35c 100644 --- a/pkg/services/accesscontrol/api/api.go +++ b/pkg/services/accesscontrol/api/api.go @@ -62,7 +62,7 @@ func (api *AccessControlAPI) getUserPermissions(c *contextmodel.ReqContext) resp return response.JSON(http.StatusInternalServerError, err) } - return response.JSON(http.StatusOK, ac.GroupScopesByAction(permissions)) + return response.JSON(http.StatusOK, ac.GroupScopesByActionContext(c.Req.Context(), permissions)) } // GET /api/access-control/users/permissions/search diff --git a/pkg/services/accesscontrol/mock/mock.go b/pkg/services/accesscontrol/mock/mock.go index 6ceda3a6700..4bd9f1279b2 100644 --- a/pkg/services/accesscontrol/mock/mock.go +++ b/pkg/services/accesscontrol/mock/mock.go @@ -120,7 +120,7 @@ func (m *Mock) Evaluate(ctx context.Context, usr identity.Requester, evaluator a if err != nil { return false, err } - permissions = accesscontrol.GroupScopesByAction(userPermissions) + permissions = accesscontrol.GroupScopesByActionContext(ctx, userPermissions) } if evaluator.Evaluate(permissions) { diff --git a/pkg/services/accesscontrol/resourcepermissions/api_test.go b/pkg/services/accesscontrol/resourcepermissions/api_test.go index 42732a3388a..8719451294b 100644 --- a/pkg/services/accesscontrol/resourcepermissions/api_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/api_test.go @@ -111,7 +111,7 @@ func TestApi_getDescription(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { service, _, _ := setupTestEnvironment(t, tt.options) - server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) + server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}, service) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/access-control/%s/description", tt.options.Resource), nil) require.NoError(t, err) @@ -158,7 +158,7 @@ func TestApi_getPermissions(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { service, usrSvc, teamSvc := setupTestEnvironment(t, testOptions) - server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) + server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}, service) seedPermissions(t, tt.resourceID, usrSvc, teamSvc, service) @@ -235,7 +235,7 @@ func TestApi_setBuiltinRolePermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { service, _, _ := setupTestEnvironment(t, testOptions) - server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) + server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}, service) recorder := setPermission(t, server, testOptions.Resource, tt.resourceID, tt.permission, "builtInRoles", tt.builtInRole) assert.Equal(t, tt.expectedStatus, recorder.Code) @@ -313,7 +313,7 @@ func TestApi_setTeamPermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { service, _, teamSvc := setupTestEnvironment(t, testOptions) - server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) + server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}, service) // seed team _, err := teamSvc.CreateTeam(context.Background(), "test", "test@test.com", 1) @@ -398,7 +398,7 @@ func TestApi_setUserPermission(t *testing.T) { service, usrSvc, _ := setupTestEnvironment(t, testOptions) server := setupTestServer(t, &user.SignedInUser{ OrgID: 1, - Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}, + Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}, }, service) _, err := usrSvc.Create(context.Background(), &user.CreateUserCommand{Login: "test", OrgID: 1}) diff --git a/pkg/services/annotations/annotationsimpl/annotations_test.go b/pkg/services/annotations/annotationsimpl/annotations_test.go index 5cbc5ccb30a..eb603a14664 100644 --- a/pkg/services/annotations/annotationsimpl/annotations_test.go +++ b/pkg/services/annotations/annotationsimpl/annotations_test.go @@ -195,7 +195,7 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) { usr := &user.SignedInUser{ UserID: 1, OrgID: orgID, - Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}, + Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}, } var role *accesscontrol.Role diff --git a/pkg/services/authn/authnimpl/registration.go b/pkg/services/authn/authnimpl/registration.go index c9b168de657..878fa59518e 100644 --- a/pkg/services/authn/authnimpl/registration.go +++ b/pkg/services/authn/authnimpl/registration.go @@ -3,6 +3,7 @@ package authnimpl import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey" @@ -36,6 +37,7 @@ func ProvideRegistration( features *featuremgmt.FeatureManager, oauthTokenService oauthtoken.OAuthTokenService, socialService social.Service, cache *remotecache.RemoteCache, ldapService service.LDAP, settingsProviderService setting.Provider, + tracer tracing.Tracer, ) Registration { logger := log.New("authn.registration") @@ -95,16 +97,16 @@ func ProvideRegistration( } // FIXME (jguer): move to User package - userSync := sync.ProvideUserSync(userService, userProtectionService, authInfoService, quotaService) - orgSync := sync.ProvideOrgSync(userService, orgService, accessControlService, cfg) + userSync := sync.ProvideUserSync(userService, userProtectionService, authInfoService, quotaService, tracer) + orgSync := sync.ProvideOrgSync(userService, orgService, accessControlService, cfg, tracer) authnSvc.RegisterPostAuthHook(userSync.SyncUserHook, 10) authnSvc.RegisterPostAuthHook(userSync.EnableUserHook, 20) authnSvc.RegisterPostAuthHook(orgSync.SyncOrgRolesHook, 30) authnSvc.RegisterPostAuthHook(userSync.SyncLastSeenHook, 130) - authnSvc.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService).SyncOauthTokenHook, 60) + authnSvc.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService, tracer).SyncOauthTokenHook, 60) authnSvc.RegisterPostAuthHook(userSync.FetchSyncedUserHook, 100) - rbacSync := sync.ProvideRBACSync(accessControlService) + rbacSync := sync.ProvideRBACSync(accessControlService, tracer) if features.IsEnabledGlobally(featuremgmt.FlagCloudRBACRoles) { authnSvc.RegisterPostAuthHook(rbacSync.SyncCloudRoles, 110) authnSvc.RegisterPreLogoutHook(gcomsso.ProvideGComSSOService(cfg).LogoutHook, 50) diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 27aab40a4f0..847d61fcf9a 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -323,6 +323,9 @@ Default: } func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) { + ctx, span := s.tracer.Start(ctx, "authn.ResolveIdentity") + defer span.End() + r := &authn.Request{} r.OrgID = orgID // hack to not update last seen @@ -358,6 +361,9 @@ func (s *Service) IsClientEnabled(name string) bool { } func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) error { + ctx, span := s.tracer.Start(ctx, "authn.SyncIdentity") + defer span.End() + r := &authn.Request{OrgID: identity.OrgID} // hack to not update last seen on external syncs r.SetMeta(authn.MetaKeyIsLogin, "true") @@ -365,6 +371,9 @@ func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) er } func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) { + ctx, span := s.tracer.Start(ctx, "authn.resolveIdentity") + defer span.End() + if namespaceID.IsNamespace(authn.NamespaceUser) { return &authn.Identity{ OrgID: orgID, diff --git a/pkg/services/authn/authnimpl/sync/oauth_token_sync.go b/pkg/services/authn/authnimpl/sync/oauth_token_sync.go index 165613d45df..2b4220fdfd0 100644 --- a/pkg/services/authn/authnimpl/sync/oauth_token_sync.go +++ b/pkg/services/authn/authnimpl/sync/oauth_token_sync.go @@ -9,19 +9,21 @@ import ( "golang.org/x/sync/singleflight" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/oauthtoken" ) -func ProvideOAuthTokenSync(service oauthtoken.OAuthTokenService, sessionService auth.UserTokenService, socialService social.Service) *OAuthTokenSync { +func ProvideOAuthTokenSync(service oauthtoken.OAuthTokenService, sessionService auth.UserTokenService, socialService social.Service, tracer tracing.Tracer) *OAuthTokenSync { return &OAuthTokenSync{ log.New("oauth_token.sync"), service, sessionService, socialService, new(singleflight.Group), + tracer, } } @@ -31,9 +33,13 @@ type OAuthTokenSync struct { sessionService auth.UserTokenService socialService social.Service singleflightGroup *singleflight.Group + tracer tracing.Tracer } func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error { + ctx, span := s.tracer.Start(ctx, "oauth.sync.SyncOauthTokenHook") + defer span.End() + // only perform oauth token check if identity is a user if !identity.ID.IsNamespace(authn.NamespaceUser) { return nil diff --git a/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go b/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go index 56145f55804..41722d4344e 100644 --- a/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go @@ -10,6 +10,7 @@ import ( "golang.org/x/sync/singleflight" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social/socialtest" "github.com/grafana/grafana/pkg/services/auth" @@ -128,6 +129,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { sessionService: sessionService, socialService: socialService, singleflightGroup: new(singleflight.Group), + tracer: tracing.InitializeTracerForTest(), } err := sync.SyncOauthTokenHook(context.Background(), tt.identity, nil) diff --git a/pkg/services/authn/authnimpl/sync/org_sync.go b/pkg/services/authn/authnimpl/sync/org_sync.go index 38429a07704..c3ef49343a6 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync.go +++ b/pkg/services/authn/authnimpl/sync/org_sync.go @@ -7,6 +7,7 @@ import ( "sort" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/org" @@ -14,8 +15,8 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -func ProvideOrgSync(userService user.Service, orgService org.Service, accessControl accesscontrol.Service, cfg *setting.Cfg) *OrgSync { - return &OrgSync{userService, orgService, accessControl, cfg, log.New("org.sync")} +func ProvideOrgSync(userService user.Service, orgService org.Service, accessControl accesscontrol.Service, cfg *setting.Cfg, tracer tracing.Tracer) *OrgSync { + return &OrgSync{userService, orgService, accessControl, cfg, log.New("org.sync"), tracer} } type OrgSync struct { @@ -23,11 +24,14 @@ type OrgSync struct { orgService org.Service accessControl accesscontrol.Service cfg *setting.Cfg - - log log.Logger + log log.Logger + tracer tracing.Tracer } func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error { + ctx, span := s.tracer.Start(ctx, "org.sync.SyncOrgRolesHook") + defer span.End() + if !id.ClientParams.SyncOrgRoles { return nil } @@ -131,6 +135,9 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a } func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn.Identity, r *authn.Request, err error) { + ctx, span := s.tracer.Start(ctx, "org.sync.SetDefaultOrgHook") + defer span.End() + if s.cfg.LoginDefaultOrgId < 1 || currentIdentity == nil || err != nil { return } @@ -166,6 +173,9 @@ func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn. } func (s *OrgSync) validateUsingOrg(ctx context.Context, userID int64, orgID int64) (bool, error) { + ctx, span := s.tracer.Start(ctx, "org.sync.validateUsingOrg") + defer span.End() + query := org.GetUserOrgListQuery{UserID: userID} result, err := s.orgService.GetUserOrgList(ctx, &query) diff --git a/pkg/services/authn/authnimpl/sync/org_sync_test.go b/pkg/services/authn/authnimpl/sync/org_sync_test.go index d6b6799e881..e03d4b8b0e2 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/org_sync_test.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/authn" @@ -116,6 +117,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { orgService: tt.fields.orgService, accessControl: tt.fields.accessControl, log: tt.fields.log, + tracer: tracing.InitializeTracerForTest(), } if err := s.SyncOrgRolesHook(tt.args.ctx, tt.args.id, nil); (err != nil) != tt.wantErr { t.Errorf("OrgSync.SyncOrgRolesHook() error = %v, wantErr %v", err, tt.wantErr) @@ -214,6 +216,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { accessControl: actest.FakeService{}, log: log.NewNopLogger(), cfg: cfg, + tracer: tracing.InitializeTracerForTest(), } s.SetDefaultOrgHook(context.Background(), tt.identity, nil, tt.inputErr) diff --git a/pkg/services/authn/authnimpl/sync/rbac_sync.go b/pkg/services/authn/authnimpl/sync/rbac_sync.go index 537238b838f..9fc84962282 100644 --- a/pkg/services/authn/authnimpl/sync/rbac_sync.go +++ b/pkg/services/authn/authnimpl/sync/rbac_sync.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" @@ -17,19 +18,24 @@ var ( errSyncPermissionsForbidden = errutil.Forbidden("permissions.sync.forbidden") ) -func ProvideRBACSync(acService accesscontrol.Service) *RBACSync { +func ProvideRBACSync(acService accesscontrol.Service, tracer tracing.Tracer) *RBACSync { return &RBACSync{ - ac: acService, - log: log.New("permissions.sync"), + ac: acService, + log: log.New("permissions.sync"), + tracer: tracer, } } type RBACSync struct { - ac accesscontrol.Service - log log.Logger + ac accesscontrol.Service + log log.Logger + tracer tracing.Tracer } func (s *RBACSync) SyncPermissionsHook(ctx context.Context, ident *authn.Identity, _ *authn.Request) error { + ctx, span := s.tracer.Start(ctx, "rbac.sync.SyncPermissionsHook") + defer span.End() + if !ident.ClientParams.SyncPermissions { return nil } @@ -43,7 +49,8 @@ func (s *RBACSync) SyncPermissionsHook(ctx context.Context, ident *authn.Identit if ident.Permissions == nil { ident.Permissions = make(map[int64]map[string][]string, 1) } - grouped := accesscontrol.GroupScopesByAction(permissions) + + grouped := accesscontrol.GroupScopesByActionContext(ctx, permissions) // Restrict access to the list of actions actionsLookup := ident.ClientParams.FetchPermissionsParams.ActionsLookup @@ -56,12 +63,15 @@ func (s *RBACSync) SyncPermissionsHook(ctx context.Context, ident *authn.Identit } grouped = filtered } - ident.Permissions[ident.OrgID] = grouped + return nil } func (s *RBACSync) fetchPermissions(ctx context.Context, ident *authn.Identity) ([]accesscontrol.Permission, error) { + ctx, span := s.tracer.Start(ctx, "rbac.sync.fetchPermissions") + defer span.End() + permissions := make([]accesscontrol.Permission, 0, 8) roles := ident.ClientParams.FetchPermissionsParams.Roles if len(roles) > 0 { @@ -94,6 +104,9 @@ var fixedCloudRoles = map[org.RoleType]string{ } func (s *RBACSync) SyncCloudRoles(ctx context.Context, ident *authn.Identity, r *authn.Request) error { + ctx, span := s.tracer.Start(ctx, "rbac.sync.SyncCloudRoles") + defer span.End() + // we only want to run this hook during login and if the module used is grafana com if r.GetMeta(authn.MetaKeyAuthModule) != login.GrafanaComAuthModule { return nil diff --git a/pkg/services/authn/authnimpl/sync/rbac_sync_test.go b/pkg/services/authn/authnimpl/sync/rbac_sync_test.go index aab1b95097b..ef860bf4993 100644 --- a/pkg/services/authn/authnimpl/sync/rbac_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/rbac_sync_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/authn" @@ -45,7 +46,7 @@ func TestRBACSync_SyncPermission(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, len(tt.identity.Permissions)) - assert.Equal(t, accesscontrol.GroupScopesByAction(tt.expectedPermissions), tt.identity.Permissions[tt.identity.OrgID]) + assert.Equal(t, accesscontrol.GroupScopesByActionContext(context.Background(), tt.expectedPermissions), tt.identity.Permissions[tt.identity.OrgID]) }) } } @@ -127,7 +128,8 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { return nil }, }, - log: log.NewNopLogger(), + log: log.NewNopLogger(), + tracer: tracing.InitializeTracerForTest(), } req := &authn.Request{} @@ -149,8 +151,9 @@ func setupTestEnv() *RBACSync { }, } s := &RBACSync{ - ac: acMock, - log: log.NewNopLogger(), + ac: acMock, + log: log.NewNopLogger(), + tracer: tracing.InitializeTracerForTest(), } return s } diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index cbc818f6592..524ece3a450 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -47,15 +48,14 @@ var ( errSignupNotAllowed = errors.New("system administrator has disabled signup") ) -func ProvideUserSync(userService user.Service, - userProtectionService login.UserProtectionService, - authInfoService login.AuthInfoService, quotaService quota.Service) *UserSync { +func ProvideUserSync(userService user.Service, userProtectionService login.UserProtectionService, authInfoService login.AuthInfoService, quotaService quota.Service, tracer tracing.Tracer) *UserSync { return &UserSync{ userService: userService, authInfoService: authInfoService, userProtectionService: userProtectionService, quotaService: quotaService, log: log.New("user.sync"), + tracer: tracer, } } @@ -65,10 +65,14 @@ type UserSync struct { userProtectionService login.UserProtectionService quotaService quota.Service log log.Logger + tracer tracing.Tracer } // SyncUserHook syncs a user with the database func (s *UserSync) SyncUserHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error { + ctx, span := s.tracer.Start(ctx, "user.sync.SyncUserHook") + defer span.End() + if !id.ClientParams.SyncUser { return nil } @@ -106,6 +110,9 @@ func (s *UserSync) SyncUserHook(ctx context.Context, id *authn.Identity, _ *auth } func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error { + ctx, span := s.tracer.Start(ctx, "user.sync.FetchSyncedUserHook") + defer span.End() + if !identity.ClientParams.FetchSyncedUser { return nil } @@ -143,6 +150,9 @@ func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Iden } func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error { + ctx, span := s.tracer.Start(ctx, "user.sync.SyncLastSeenHook") + defer span.End() + if r.GetMeta(authn.MetaKeyIsLogin) != "" { // Do not sync last seen for login requests return nil @@ -177,6 +187,9 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identit } func (s *UserSync) EnableUserHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error { + ctx, span := s.tracer.Start(ctx, "user.sync.EnableUserHook") + defer span.End() + if !identity.ClientParams.EnableUser { return nil } @@ -196,6 +209,9 @@ func (s *UserSync) EnableUserHook(ctx context.Context, identity *authn.Identity, } func (s *UserSync) upsertAuthConnection(ctx context.Context, userID int64, identity *authn.Identity, createConnection bool) error { + ctx, span := s.tracer.Start(ctx, "user.sync.upsertAuthConnection") + defer span.End() + if identity.AuthenticatedBy == "" { return nil } @@ -222,6 +238,9 @@ func (s *UserSync) upsertAuthConnection(ctx context.Context, userID int64, ident } func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id *authn.Identity, userAuth *login.UserAuth) error { + ctx, span := s.tracer.Start(ctx, "user.sync.updateUserAttributes") + defer span.End() + if errProtection := s.userProtectionService.AllowUserMapping(usr, id.AuthenticatedBy); errProtection != nil { return errUserProtection.Errorf("user mapping not allowed: %w", errProtection) } @@ -273,6 +292,8 @@ func (s *UserSync) updateUserAttributes(ctx context.Context, usr *user.User, id } func (s *UserSync) createUser(ctx context.Context, id *authn.Identity) (*user.User, error) { + ctx, span := s.tracer.Start(ctx, "user.sync.createUser") + defer span.End() // FIXME(jguer): this should be done in the user service // quota check: we can have quotas on both global and org level // therefore we need to query check quota for both user and org services @@ -312,6 +333,9 @@ func (s *UserSync) createUser(ctx context.Context, id *authn.Identity) (*user.Us } func (s *UserSync) getUser(ctx context.Context, identity *authn.Identity) (*user.User, *login.UserAuth, error) { + ctx, span := s.tracer.Start(ctx, "user.sync.getUser") + defer span.End() + // Check auth info fist if identity.AuthID != "" && identity.AuthenticatedBy != "" { query := &login.GetAuthInfoQuery{AuthId: identity.AuthID, AuthModule: identity.AuthenticatedBy} @@ -361,6 +385,9 @@ func (s *UserSync) getUser(ctx context.Context, identity *authn.Identity) (*user } func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupParams) (*user.User, error) { + ctx, span := s.tracer.Start(ctx, "user.sync.lookupByOneOf") + defer span.End() + var usr *user.User var err error diff --git a/pkg/services/authn/authnimpl/sync/user_sync_test.go b/pkg/services/authn/authnimpl/sync/user_sync_test.go index eb0e7fafd35..1a01b748bd2 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/user_sync_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login/authinfoimpl" @@ -426,7 +427,7 @@ func TestUserSync_SyncUserHook(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := ProvideUserSync(tt.fields.userService, userProtection, tt.fields.authInfoService, tt.fields.quotaService) + s := ProvideUserSync(tt.fields.userService, userProtection, tt.fields.authInfoService, tt.fields.quotaService, tracing.InitializeTracerForTest()) err := s.SyncUserHook(tt.args.ctx, tt.args.id, nil) if tt.wantErr { require.Error(t, err) @@ -462,7 +463,9 @@ func TestUserSync_FetchSyncedUserHook(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - s := UserSync{} + s := UserSync{ + tracer: tracing.InitializeTracerForTest(), + } err := s.FetchSyncedUserHook(context.Background(), tt.identity, tt.req) require.ErrorIs(t, err, tt.expectedErr) }) @@ -515,7 +518,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { return nil } - s := UserSync{userService: userSvc} + s := UserSync{userService: userSvc, tracer: tracing.InitializeTracerForTest()} err := s.EnableUserHook(context.Background(), tt.identity, nil) require.NoError(t, err) assert.Equal(t, tt.enableUser, called) diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index e8fd90273e7..1f4101b190e 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -288,7 +288,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { UserID: u.ID, OrgID: u.OrgID, OrgRole: org.RoleAdmin, - Permissions: map[int64]map[string][]string{u.OrgID: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + Permissions: map[int64]map[string][]string{u.OrgID: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{ { Action: dashboards.ActionFoldersCreate, }, { diff --git a/pkg/services/grpcserver/interceptors/auth.go b/pkg/services/grpcserver/interceptors/auth.go index 455a35184d0..ea81d45153f 100644 --- a/pkg/services/grpcserver/interceptors/auth.go +++ b/pkg/services/grpcserver/interceptors/auth.go @@ -126,7 +126,7 @@ func (a *authenticator) getSignedInUser(ctx context.Context, token string) (*use if err != nil { a.logger.Error("failed fetching permissions for user", "userID", signedInUser.UserID, "error", err) } - signedInUser.Permissions[signedInUser.OrgID] = accesscontrol.GroupScopesByAction(permissions) + signedInUser.Permissions[signedInUser.OrgID] = accesscontrol.GroupScopesByActionContext(context.Background(), permissions) } return signedInUser, nil diff --git a/pkg/services/ldap/api/service_test.go b/pkg/services/ldap/api/service_test.go index 95fd54a0234..0e8ddc3c3dc 100644 --- a/pkg/services/ldap/api/service_test.go +++ b/pkg/services/ldap/api/service_test.go @@ -1,6 +1,7 @@ package api import ( + "context" "encoding/json" "errors" "io" @@ -663,5 +664,5 @@ search_base_dns = ["dc=grafana,dc=org"]`) } func userWithPermissions(orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser { - return &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}} + return &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}} } diff --git a/pkg/services/publicdashboards/database/database_test.go b/pkg/services/publicdashboards/database/database_test.go index fd8273cc198..d2e506063b1 100644 --- a/pkg/services/publicdashboards/database/database_test.go +++ b/pkg/services/publicdashboards/database/database_test.go @@ -91,7 +91,7 @@ func TestIntegrationListPublicDashboard(t *testing.T) { {Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", cDash.UID)}, } - usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}} + usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}} actest.AddUserPermissionToDB(t, sqlStore, usr) @@ -120,7 +120,7 @@ func TestIntegrationListPublicDashboard(t *testing.T) { {Action: dashboards.ActionDashboardsRead, Scope: fmt.Sprintf("dashboards:uid:%s", cDash.UID)}, } - usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}} + usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}} actest.AddUserPermissionToDB(t, sqlStore, usr) @@ -148,7 +148,7 @@ func TestIntegrationListPublicDashboard(t *testing.T) { {Action: dashboards.ActionDashboardsRead, Scope: "dashboards:uid:another-dashboard-2-uid"}, } - usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByAction(permissions)}} + usr := &user.SignedInUser{UserID: 1, OrgID: orgId, Permissions: map[int64]map[string][]string{orgId: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}} actest.AddUserPermissionToDB(t, sqlStore, usr) diff --git a/pkg/services/searchV2/service.go b/pkg/services/searchV2/service.go index 0b2c0e63867..b0ca3096dfc 100644 --- a/pkg/services/searchV2/service.go +++ b/pkg/services/searchV2/service.go @@ -199,7 +199,7 @@ func (s *StandardSearchService) getUser(ctx context.Context, backendUser *backen return nil, errors.New("auth error") } - usr.Permissions[orgId] = accesscontrol.GroupScopesByAction(permissions) + usr.Permissions[orgId] = accesscontrol.GroupScopesByActionContext(ctx, permissions) return usr, nil } diff --git a/pkg/services/serviceaccounts/api/api_test.go b/pkg/services/serviceaccounts/api/api_test.go index 531270e8c6a..144869b390a 100644 --- a/pkg/services/serviceaccounts/api/api_test.go +++ b/pkg/services/serviceaccounts/api/api_test.go @@ -1,6 +1,7 @@ package api import ( + "context" "encoding/json" "fmt" "net/http" @@ -87,7 +88,7 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) { req := server.NewRequest(http.MethodPost, "/api/serviceaccounts/", strings.NewReader(tt.body)) webtest.RequestWithSignedInUser(req, &user.SignedInUser{ OrgRole: tt.basicRole, OrgID: 1, IsAnonymous: true, - Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) + Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}) res, err := server.SendJSON(req) require.NoError(t, err) @@ -124,7 +125,7 @@ func TestServiceAccountsAPI_DeleteServiceAccount(t *testing.T) { t.Run(tt.desc, func(t *testing.T) { server := setupTests(t) req := server.NewRequest(http.MethodDelete, fmt.Sprintf("/api/serviceaccounts/%d", tt.id), nil) - webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}) res, err := server.Send(req) require.NoError(t, err) @@ -165,7 +166,7 @@ func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) { a.service = &satests.FakeServiceAccountService{ExpectedServiceAccountProfile: tt.expectedSA} }) req := server.NewGetRequest(fmt.Sprintf("/api/serviceaccounts/%d", tt.id)) - webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}) res, err := server.Send(req) require.NoError(t, err) assert.Equal(t, tt.expectedCode, res.StatusCode) @@ -228,7 +229,7 @@ func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) { }) req := server.NewRequest(http.MethodPatch, fmt.Sprintf("/api/serviceaccounts/%d", tt.id), strings.NewReader(tt.body)) - webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}) res, err := server.SendJSON(req) require.NoError(t, err) @@ -282,7 +283,7 @@ func TestServiceAccountsAPI_MigrateApiKeysToServiceAccounts(t *testing.T) { }) req := server.NewRequest(http.MethodPost, "/api/serviceaccounts/migrate", nil) - webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: tt.orgId, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: tt.orgId, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}) res, err := server.SendJSON(req) require.NoError(t, err) diff --git a/pkg/services/serviceaccounts/api/token_test.go b/pkg/services/serviceaccounts/api/token_test.go index 5c672c16d86..087d786b723 100644 --- a/pkg/services/serviceaccounts/api/token_test.go +++ b/pkg/services/serviceaccounts/api/token_test.go @@ -1,6 +1,7 @@ package api import ( + "context" "fmt" "net/http" "strings" @@ -47,7 +48,7 @@ func TestServiceAccountsAPI_ListTokens(t *testing.T) { a.service = &satests.FakeServiceAccountService{} }) req := server.NewGetRequest(fmt.Sprintf("/api/serviceaccounts/%d/tokens", tt.id)) - webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}) res, err := server.Send(req) require.NoError(t, err) @@ -116,7 +117,7 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) { } }) req := server.NewRequest(http.MethodPost, fmt.Sprintf("/api/serviceaccounts/%d/tokens", tt.id), strings.NewReader(tt.body)) - webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}) res, err := server.SendJSON(req) require.NoError(t, err) @@ -168,7 +169,7 @@ func TestServiceAccountsAPI_DeleteToken(t *testing.T) { }) req := server.NewRequest(http.MethodDelete, fmt.Sprintf("/api/serviceaccounts/%d/tokens/%d", tt.saID, tt.apikeyID), nil) - webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}}) res, err := server.SendJSON(req) require.NoError(t, err) diff --git a/pkg/services/sqlstore/permissions/dashboard_test.go b/pkg/services/sqlstore/permissions/dashboard_test.go index 640254d5def..caf74dbcad9 100644 --- a/pkg/services/sqlstore/permissions/dashboard_test.go +++ b/pkg/services/sqlstore/permissions/dashboard_test.go @@ -173,7 +173,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) { recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported() require.NoError(t, err) - usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}} + usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}} for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} { m := features.GetEnabled(context.Background()) @@ -345,7 +345,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported() require.NoError(t, err) - usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.signedInUserPermissions)}} + usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.signedInUserPermissions)}} for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} { m := features.GetEnabled(context.Background()) @@ -456,7 +456,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) { Action: dashboards.ActionFoldersWrite, Scope: dashboards.ScopeFoldersAll, }) - usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.permissions)}} + usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), tc.permissions)}} for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagAccessActionSets)...), featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} { m := features.GetEnabled(context.Background()) @@ -564,7 +564,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission for _, tc := range testCases { helperUser := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule, - Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{ { Action: dashboards.ActionFoldersCreate, }, @@ -583,7 +583,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission } t.Run(tc.desc+" with features "+strings.Join(keys, ","), func(t *testing.T) { - usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.signedInUserPermissions)}} + usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), tc.signedInUserPermissions)}} db := setupNestedTest(t, helperUser, []accesscontrol.Permission{}, orgID, features) recursiveQueriesAreSupported, err := db.RecursiveQueriesAreSupported() require.NoError(t, err) @@ -693,7 +693,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T Scope: "folders:uid:unrelated"}, accesscontrol.Permission{ Action: dashboards.ActionDashboardsCreate, Scope: "folders:uid:unrelated"}) - usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.signedInUserPermissions)}} + usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), tc.signedInUserPermissions)}} for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} { m := features.GetEnabled(context.Background()) diff --git a/pkg/services/sqlstore/permissions/dashboards_bench_test.go b/pkg/services/sqlstore/permissions/dashboards_bench_test.go index d84639b6274..ac6bbddeaa4 100644 --- a/pkg/services/sqlstore/permissions/dashboards_bench_test.go +++ b/pkg/services/sqlstore/permissions/dashboards_bench_test.go @@ -34,7 +34,7 @@ import ( func benchmarkDashboardPermissionFilter(b *testing.B, numUsers, numDashboards, numFolders, nestingLevel int) { usr := user.SignedInUser{UserID: 1, OrgID: 1, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{ - 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{ + 1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{ { Action: dashboards.ActionFoldersCreate, }, diff --git a/pkg/services/sqlstore/searchstore/search_test.go b/pkg/services/sqlstore/searchstore/search_test.go index 39732fdadbf..33dcc0f3402 100644 --- a/pkg/services/sqlstore/searchstore/search_test.go +++ b/pkg/services/sqlstore/searchstore/search_test.go @@ -320,7 +320,7 @@ func TestBuilder_RBAC(t *testing.T) { for _, tc := range testsCases { t.Run(tc.desc, func(t *testing.T) { if len(tc.userPermissions) > 0 { - user.Permissions = map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tc.userPermissions)} + user.Permissions = map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tc.userPermissions)} } builder := &searchstore.Builder{ diff --git a/pkg/services/ssosettings/api/api_test.go b/pkg/services/ssosettings/api/api_test.go index 2eb202ea3ac..9084d4d3155 100644 --- a/pkg/services/ssosettings/api/api_test.go +++ b/pkg/services/ssosettings/api/api_test.go @@ -2,6 +2,7 @@ package api import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -550,7 +551,7 @@ func TestSSOSettingsAPI_List(t *testing.T) { func getPermissionsForActionAndScope(action, scope string) map[int64]map[string][]string { return map[int64]map[string][]string{ - 1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{{ + 1: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{{ Action: action, Scope: scope, }}), } diff --git a/pkg/services/team/teamapi/team_members_test.go b/pkg/services/team/teamapi/team_members_test.go index 68065c4d001..9fba37925fa 100644 --- a/pkg/services/team/teamapi/team_members_test.go +++ b/pkg/services/team/teamapi/team_members_test.go @@ -281,5 +281,5 @@ func Test_getTeamMembershipUpdates(t *testing.T) { } func authedUserWithPermissions(userID, orgID int64, permissions []accesscontrol.Permission) *user.SignedInUser { - return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}} + return &user.SignedInUser{UserID: userID, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByActionContext(context.Background(), permissions)}} } From f18da6f4dc9f2cbd7216268970e5a43df1f59ed6 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Wed, 3 Jul 2024 09:11:52 +0200 Subject: [PATCH 02/39] User+team: remove startup migration for uid (#89953) * Remove migration that is performed on startup --- pkg/services/team/teamimpl/store.go | 23 ----------------------- pkg/services/team/teamimpl/team.go | 5 ----- pkg/services/user/userimpl/user.go | 27 --------------------------- 3 files changed, 55 deletions(-) diff --git a/pkg/services/team/teamimpl/store.go b/pkg/services/team/teamimpl/store.go index 7c422d8abf5..8f3db689fd3 100644 --- a/pkg/services/team/teamimpl/store.go +++ b/pkg/services/team/teamimpl/store.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/infra/db" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" @@ -567,25 +566,3 @@ func (ss *xormStore) getTeamMembers(ctx context.Context, query *team.GetTeamMemb func (ss *xormStore) RegisterDelete(query string) { ss.deletes = append(ss.deletes, query) } - -// This is just to ensure that all teams have a valid uid. -// To protect against upgrade / downgrade we need to run this for a couple of releases. -// FIXME: Remove this migration and make uid field required https://github.com/grafana/identity-access-team/issues/552 -func (ss *xormStore) uidMigration() error { - return ss.db.WithDbSession(context.Background(), func(sess *db.Session) error { - switch ss.db.GetDBType() { - case migrator.SQLite: - _, err := sess.Exec("UPDATE team SET uid=printf('t%09d',id) WHERE uid IS NULL;") - return err - case migrator.Postgres: - _, err := sess.Exec("UPDATE team SET uid='t' || lpad('' || id::text,9,'0') WHERE uid IS NULL;") - return err - case migrator.MySQL: - _, err := sess.Exec("UPDATE team SET uid=concat('t',lpad(id,9,'0')) WHERE uid IS NULL;") - return err - default: - // this branch should be unreachable - return nil - } - }) -} diff --git a/pkg/services/team/teamimpl/team.go b/pkg/services/team/teamimpl/team.go index 1eb8e7c1b2d..e7c9537b751 100644 --- a/pkg/services/team/teamimpl/team.go +++ b/pkg/services/team/teamimpl/team.go @@ -18,11 +18,6 @@ type Service struct { } func ProvideService(db db.DB, cfg *setting.Cfg, tracer tracing.Tracer) (team.Service, error) { - store := &xormStore{db: db, cfg: cfg, deletes: []string{}} - - if err := store.uidMigration(); err != nil { - return nil, err - } return &Service{ store: &xormStore{db: db, cfg: cfg, deletes: []string{}}, tracer: tracer, diff --git a/pkg/services/user/userimpl/user.go b/pkg/services/user/userimpl/user.go index 3e340736498..db911d4c408 100644 --- a/pkg/services/user/userimpl/user.go +++ b/pkg/services/user/userimpl/user.go @@ -17,7 +17,6 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/serviceaccounts" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/supportbundles" "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/user" @@ -65,10 +64,6 @@ func ProvideService( return s, err } - if err := s.uidMigration(db); err != nil { - return nil, err - } - bundleRegistry.RegisterSupportItemCollector(s.supportBundleCollector()) return s, nil } @@ -529,25 +524,3 @@ func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) { limits.Set(globalQuotaTag, cfg.Quota.Global.User) return limits, nil } - -// This is just to ensure that all users have a valid uid. -// To protect against upgrade / downgrade we need to run this for a couple of releases. -// FIXME: Remove this migration and make uid field required https://github.com/grafana/identity-access-team/issues/552 -func (s *Service) uidMigration(store db.DB) error { - return store.WithDbSession(context.Background(), func(sess *db.Session) error { - switch store.GetDBType() { - case migrator.SQLite: - _, err := sess.Exec("UPDATE user SET uid=printf('u%09d',id) WHERE uid IS NULL;") - return err - case migrator.Postgres: - _, err := sess.Exec("UPDATE `user` SET uid='u' || lpad('' || id::text,9,'0') WHERE uid IS NULL;") - return err - case migrator.MySQL: - _, err := sess.Exec("UPDATE user SET uid=concat('u',lpad(id,9,'0')) WHERE uid IS NULL;") - return err - default: - // this branch should be unreachable - return nil - } - }) -} From f41ee615ba7f1f932dc3cbb2060029e063b973cd Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Wed, 3 Jul 2024 09:19:54 +0200 Subject: [PATCH 03/39] Chore: Add basic admission handlers for registered API services (#89824) --- pkg/promlib/admission_handler.go | 77 ++++++++++++++++++++++++ pkg/registry/apis/datasource/register.go | 2 + pkg/tsdb/graphite/admission_handler.go | 77 ++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 pkg/promlib/admission_handler.go create mode 100644 pkg/tsdb/graphite/admission_handler.go diff --git a/pkg/promlib/admission_handler.go b/pkg/promlib/admission_handler.go new file mode 100644 index 00000000000..4cfa68b1d53 --- /dev/null +++ b/pkg/promlib/admission_handler.go @@ -0,0 +1,77 @@ +package promlib + +import ( + "context" + "fmt" + "net/http" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + _ backend.AdmissionHandler = (*Service)(nil) +) + +// ValidateAdmission implements backend.AdmissionHandler. +func (s *Service) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) { + rsp, err := s.MutateAdmission(ctx, req) + if rsp != nil { + return &backend.ValidationResponse{ + Allowed: rsp.Allowed, + Result: rsp.Result, + Warnings: rsp.Warnings, + }, err + } + return nil, err +} + +// MutateAdmission implements backend.AdmissionHandler. +func (s *Service) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) { + expected := (&backend.DataSourceInstanceSettings{}).GVK() + if req.Kind.Kind != expected.Kind && req.Kind.Group != expected.Group { + return getBadRequest("expected DataSourceInstanceSettings protobuf payload"), nil + } + + // Convert the payload from protobuf to an SDK struct + settings, err := backend.DataSourceInstanceSettingsFromProto(req.ObjectBytes, "") + if err != nil { + return nil, err + } + if settings == nil { + return getBadRequest("missing datasource settings"), nil + } + + switch settings.APIVersion { + case "", "v0alpha1": + // OK! + default: + return getBadRequest(fmt.Sprintf("expected apiVersion: v0alpha1, found: %s", settings.APIVersion)), nil + } + if settings.URL != "" { + return getBadRequest("unsupported URL value"), nil + } + + pb, err := backend.DataSourceInstanceSettingsToProtoBytes(settings) + return &backend.MutationResponse{ + Allowed: true, + ObjectBytes: pb, + }, err +} + +// ConvertObject implements backend.AdmissionHandler. +func (s *Service) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) { + return nil, fmt.Errorf("not implemented") +} + +func getBadRequest(msg string) *backend.MutationResponse { + return &backend.MutationResponse{ + Allowed: false, + Result: &backend.StatusResult{ + Status: "Failure", + Message: msg, + Reason: string(metav1.StatusReasonBadRequest), + Code: http.StatusBadRequest, + }, + } +} diff --git a/pkg/registry/apis/datasource/register.go b/pkg/registry/apis/datasource/register.go index 9ee6741720c..a938631a2ee 100644 --- a/pkg/registry/apis/datasource/register.go +++ b/pkg/registry/apis/datasource/register.go @@ -67,6 +67,8 @@ func RegisterAPIService( var err error var builder *DataSourceAPIBuilder all := pluginStore.Plugins(context.Background(), plugins.TypeDataSource) + // ATTENTION: Adding a datasource here requires the plugin to implement + // an AdmissionHandler to validate the datasource settings. ids := []string{ "grafana-testdata-datasource", "prometheus", diff --git a/pkg/tsdb/graphite/admission_handler.go b/pkg/tsdb/graphite/admission_handler.go new file mode 100644 index 00000000000..829324d844b --- /dev/null +++ b/pkg/tsdb/graphite/admission_handler.go @@ -0,0 +1,77 @@ +package graphite + +import ( + "context" + "fmt" + "net/http" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + _ backend.AdmissionHandler = (*Service)(nil) +) + +// ValidateAdmission implements backend.AdmissionHandler. +func (s *Service) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) { + rsp, err := s.MutateAdmission(ctx, req) + if rsp != nil { + return &backend.ValidationResponse{ + Allowed: rsp.Allowed, + Result: rsp.Result, + Warnings: rsp.Warnings, + }, err + } + return nil, err +} + +// MutateAdmission implements backend.AdmissionHandler. +func (s *Service) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) { + expected := (&backend.DataSourceInstanceSettings{}).GVK() + if req.Kind.Kind != expected.Kind && req.Kind.Group != expected.Group { + return getBadRequest("expected DataSourceInstanceSettings protobuf payload"), nil + } + + // Convert the payload from protobuf to an SDK struct + settings, err := backend.DataSourceInstanceSettingsFromProto(req.ObjectBytes, "") + if err != nil { + return nil, err + } + if settings == nil { + return getBadRequest("missing datasource settings"), nil + } + + switch settings.APIVersion { + case "", "v0alpha1": + // OK! + default: + return getBadRequest(fmt.Sprintf("expected apiVersion: v0alpha1, found: %s", settings.APIVersion)), nil + } + if settings.URL != "" { + return getBadRequest("unsupported URL value"), nil + } + + pb, err := backend.DataSourceInstanceSettingsToProtoBytes(settings) + return &backend.MutationResponse{ + Allowed: true, + ObjectBytes: pb, + }, err +} + +// ConvertObject implements backend.AdmissionHandler. +func (s *Service) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) { + return nil, fmt.Errorf("not implemented") +} + +func getBadRequest(msg string) *backend.MutationResponse { + return &backend.MutationResponse{ + Allowed: false, + Result: &backend.StatusResult{ + Status: "Failure", + Message: msg, + Reason: string(metav1.StatusReasonBadRequest), + Code: http.StatusBadRequest, + }, + } +} From 1de3e4be2941dca6ed3a5832c2d02eb1bd5d2995 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Wed, 3 Jul 2024 09:38:57 +0200 Subject: [PATCH 04/39] Scenes: Upgrade to v5.3.4 (#89978) --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 095cb2274da..cccce011e3b 100644 --- a/package.json +++ b/package.json @@ -261,7 +261,7 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "5.3.2", + "@grafana/scenes": "^5.3.4", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/yarn.lock b/yarn.lock index 9b14c407e32..9f1bc4c5284 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3580,9 +3580,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:5.3.2": - version: 5.3.2 - resolution: "@grafana/scenes@npm:5.3.2" +"@grafana/scenes@npm:^5.3.4": + version: 5.3.4 + resolution: "@grafana/scenes@npm:5.3.4" dependencies: "@grafana/e2e-selectors": "npm:^11.0.0" "@leeoniya/ufuzzy": "npm:^1.0.14" @@ -3597,7 +3597,7 @@ __metadata: "@grafana/ui": ^10.4.1 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/90d2dd3b6daa293589a8933de8169d51814956ab4b425d2b5f750b3d504080944373345bce84e6a18a28260cc6fd9bf58c4e4046092a87d8ff0104b085f63e6d + checksum: 10/a312258eeb22c9d78f2d17404bba2a50e749e113fc0ad1af1d0e42f647ff823097c3065da2fc1efe35b55fcff6ebd26f306fa121a226817ccc9edd8a12400301 languageName: node linkType: hard @@ -17145,7 +17145,7 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:5.3.2" + "@grafana/scenes": "npm:^5.3.4" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^1.3.0-rc1" From 68e30e2b4b41f43044cf3385aaa4072671f5d4dc Mon Sep 17 00:00:00 2001 From: Brandon D <42697737+BrandonDalton@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:20:59 +1000 Subject: [PATCH 05/39] Updating spelling within Anonymous Page (#88757) * Updating Placeholder * Fix --- public/app/features/admin/UserListAnonymousPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/admin/UserListAnonymousPage.tsx b/public/app/features/admin/UserListAnonymousPage.tsx index fe7405c0fbe..5f4a9423a29 100644 --- a/public/app/features/admin/UserListAnonymousPage.tsx +++ b/public/app/features/admin/UserListAnonymousPage.tsx @@ -59,7 +59,7 @@ const UserListAnonymousDevicesPageUnConnected = ({
Date: Wed, 3 Jul 2024 10:40:51 +0100 Subject: [PATCH 06/39] Navigation: Backend to save navigation customization into preferences (#89783) --- .../developers/http_api/preferences.md | 4 ++-- kinds/preferences/preferences_kind.cue | 7 +++++++ packages/grafana-schema/src/index.gen.ts | 6 +++++- .../preferences/x/preferences_types.gen.ts | 12 +++++++++++ pkg/api/dtos/prefs.go | 2 ++ pkg/api/preferences.go | 1 + pkg/kinds/preferences/preferences_spec_gen.go | 6 ++++++ pkg/services/preference/model.go | 7 +++++++ pkg/services/preference/prefapi/api.go | 8 +++++++ pkg/services/preference/prefimpl/pref.go | 14 +++++++++++++ public/api-merged.json | 21 +++++++++++++++++++ public/openapi3.json | 21 +++++++++++++++++++ 12 files changed, 106 insertions(+), 3 deletions(-) diff --git a/docs/sources/developers/http_api/preferences.md b/docs/sources/developers/http_api/preferences.md index a3f5b6b1afd..0d869d3c353 100644 --- a/docs/sources/developers/http_api/preferences.md +++ b/docs/sources/developers/http_api/preferences.md @@ -53,7 +53,7 @@ Content-Type: application/json "timezone": "utc", "weekStart": "", "navbar": { - "savedItems": null + "savedItemIds": null }, "queryHistory": { "homeTab": "" @@ -142,7 +142,7 @@ Content-Type: application/json "timezone": "", "weekStart": "", "navbar": { - "savedItems": null + "savedItemIds": null }, "queryHistory": { "homeTab": "" diff --git a/kinds/preferences/preferences_kind.cue b/kinds/preferences/preferences_kind.cue index 0eddc16cbef..dd97beb4b0c 100644 --- a/kinds/preferences/preferences_kind.cue +++ b/kinds/preferences/preferences_kind.cue @@ -32,6 +32,9 @@ lineage: schemas: [{ // Cookie preferences cookiePreferences?: #CookiePreferences + + // Navigation preferences + navbar?: #NavbarPreference } @cuetsy(kind="interface") #QueryHistoryPreference: { @@ -44,5 +47,9 @@ lineage: schemas: [{ performance?: {} functional?: {} } @cuetsy(kind="interface") + + #NavbarPreference: { + savedItemIds: [...string] + } @cuetsy(kind="interface") } }] diff --git a/packages/grafana-schema/src/index.gen.ts b/packages/grafana-schema/src/index.gen.ts index 88e4a174071..215c19e1098 100644 --- a/packages/grafana-schema/src/index.gen.ts +++ b/packages/grafana-schema/src/index.gen.ts @@ -125,9 +125,13 @@ export type { LibraryPanel } from './veneer/librarypanel.types'; export type { Preferences, QueryHistoryPreference, - CookiePreferences + CookiePreferences, + NavbarPreference } from './raw/preferences/x/preferences_types.gen'; +// Raw generated enums and default consts from preferences kind. +export { defaultNavbarPreference } from './raw/preferences/x/preferences_types.gen'; + // Raw generated types from PublicDashboard kind. export type { PublicDashboard } from './raw/publicdashboard/x/publicdashboard_types.gen'; diff --git a/packages/grafana-schema/src/raw/preferences/x/preferences_types.gen.ts b/packages/grafana-schema/src/raw/preferences/x/preferences_types.gen.ts index b685f299f2e..9d9953e10d9 100644 --- a/packages/grafana-schema/src/raw/preferences/x/preferences_types.gen.ts +++ b/packages/grafana-schema/src/raw/preferences/x/preferences_types.gen.ts @@ -21,6 +21,14 @@ export interface CookiePreferences { performance?: Record; } +export interface NavbarPreference { + savedItemIds: Array; +} + +export const defaultNavbarPreference: Partial = { + savedItemIds: [], +}; + /** * Spec defines user, team or org Grafana preferences * swagger:model Preferences @@ -38,6 +46,10 @@ export interface Preferences { * Selected language (beta) */ language?: string; + /** + * Navigation preferences + */ + navbar?: NavbarPreference; /** * Explore query history preferences */ diff --git a/pkg/api/dtos/prefs.go b/pkg/api/dtos/prefs.go index c5318217164..14c131eb7dd 100644 --- a/pkg/api/dtos/prefs.go +++ b/pkg/api/dtos/prefs.go @@ -18,6 +18,7 @@ type UpdatePrefsCmd struct { QueryHistory *pref.QueryHistoryPreference `json:"queryHistory,omitempty"` Language string `json:"language"` Cookies []pref.CookieType `json:"cookies,omitempty"` + Navbar *pref.NavbarPreference `json:"navbar,omitempty"` } // swagger:model @@ -34,4 +35,5 @@ type PatchPrefsCmd struct { QueryHistory *pref.QueryHistoryPreference `json:"queryHistory,omitempty"` HomeDashboardUID *string `json:"homeDashboardUID,omitempty"` Cookies []pref.CookieType `json:"cookies,omitempty"` + Navbar *pref.NavbarPreference `json:"navbar,omitempty"` } diff --git a/pkg/api/preferences.go b/pkg/api/preferences.go index 7df46bb25c1..d5c2d216204 100644 --- a/pkg/api/preferences.go +++ b/pkg/api/preferences.go @@ -155,6 +155,7 @@ func (hs *HTTPServer) patchPreferencesFor(ctx context.Context, orgID, userID, te Language: dtoCmd.Language, QueryHistory: dtoCmd.QueryHistory, CookiePreferences: dtoCmd.Cookies, + Navbar: dtoCmd.Navbar, } if err := hs.preferenceService.Patch(ctx, &patchCmd); err != nil { diff --git a/pkg/kinds/preferences/preferences_spec_gen.go b/pkg/kinds/preferences/preferences_spec_gen.go index 1c28568cfbc..7d0dd39cfc1 100644 --- a/pkg/kinds/preferences/preferences_spec_gen.go +++ b/pkg/kinds/preferences/preferences_spec_gen.go @@ -16,6 +16,11 @@ type CookiePreferences struct { Performance map[string]any `json:"performance,omitempty"` } +// NavbarPreference defines model for NavbarPreference. +type NavbarPreference struct { + SavedItemIds []string `json:"savedItemIds"` +} + // QueryHistoryPreference defines model for QueryHistoryPreference. type QueryHistoryPreference struct { // HomeTab one of: '' | 'query' | 'starred'; @@ -32,6 +37,7 @@ type Spec struct { // Selected language (beta) Language *string `json:"language,omitempty"` + Navbar *NavbarPreference `json:"navbar,omitempty"` QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"` // Theme light, dark, empty is default diff --git a/pkg/services/preference/model.go b/pkg/services/preference/model.go index 576ead990d5..674f5895472 100644 --- a/pkg/services/preference/model.go +++ b/pkg/services/preference/model.go @@ -67,6 +67,7 @@ type SavePreferenceCommand struct { Language string `json:"language,omitempty"` QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"` CookiePreferences []CookieType `json:"cookiePreferences,omitempty"` + Navbar *NavbarPreference `json:"navbar,omitempty"` } type PatchPreferenceCommand struct { @@ -82,18 +83,24 @@ type PatchPreferenceCommand struct { Language *string `json:"language,omitempty"` QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"` CookiePreferences []CookieType `json:"cookiePreferences,omitempty"` + Navbar *NavbarPreference `json:"navbar,omitempty"` } type PreferenceJSONData struct { Language string `json:"language"` QueryHistory QueryHistoryPreference `json:"queryHistory"` CookiePreferences map[string]struct{} `json:"cookiePreferences"` + Navbar NavbarPreference `json:"navbar"` } type QueryHistoryPreference struct { HomeTab string `json:"homeTab"` } +type NavbarPreference struct { + SavedItemIds []string `json:"savedItemIds"` +} + func (j *PreferenceJSONData) FromDB(data []byte) error { dec := json.NewDecoder(bytes.NewBuffer(data)) dec.UseNumber() diff --git a/pkg/services/preference/prefapi/api.go b/pkg/services/preference/prefapi/api.go index a77639571eb..51d2718f419 100644 --- a/pkg/services/preference/prefapi/api.go +++ b/pkg/services/preference/prefapi/api.go @@ -46,6 +46,7 @@ func UpdatePreferencesFor(ctx context.Context, HomeDashboardID: dtoCmd.HomeDashboardID, QueryHistory: dtoCmd.QueryHistory, CookiePreferences: dtoCmd.Cookies, + Navbar: dtoCmd.Navbar, } if err := preferenceService.Save(ctx, &saveCmd); err != nil { @@ -96,6 +97,13 @@ func GetPreferencesFor(ctx context.Context, dto.Language = &preference.JSONData.Language } + if preference.JSONData.Navbar.SavedItemIds != nil { + dto.Navbar = &preferences.NavbarPreference{ + SavedItemIds: []string{}, + } + dto.Navbar.SavedItemIds = preference.JSONData.Navbar.SavedItemIds + } + if preference.JSONData.QueryHistory.HomeTab != "" { dto.QueryHistory = &preferences.QueryHistoryPreference{ HomeTab: &preference.JSONData.QueryHistory.HomeTab, diff --git a/pkg/services/preference/prefimpl/pref.go b/pkg/services/preference/prefimpl/pref.go index cf3bc80a9dc..0de2069efed 100644 --- a/pkg/services/preference/prefimpl/pref.go +++ b/pkg/services/preference/prefimpl/pref.go @@ -71,6 +71,10 @@ func (s *Service) GetWithDefaults(ctx context.Context, query *pref.GetPreference res.JSONData.QueryHistory.HomeTab = p.JSONData.QueryHistory.HomeTab } + if p.JSONData.Navbar.SavedItemIds != nil { + res.JSONData.Navbar.SavedItemIds = p.JSONData.Navbar.SavedItemIds + } + if p.JSONData.CookiePreferences != nil { res.JSONData.CookiePreferences = p.JSONData.CookiePreferences } @@ -170,6 +174,13 @@ func (s *Service) Patch(ctx context.Context, cmd *pref.PatchPreferenceCommand) e preference.JSONData.Language = *cmd.Language } + if cmd.Navbar != nil && cmd.Navbar.SavedItemIds != nil { + if preference.JSONData == nil { + preference.JSONData = &pref.PreferenceJSONData{} + } + preference.JSONData.Navbar.SavedItemIds = cmd.Navbar.SavedItemIds + } + if cmd.QueryHistory != nil { if preference.JSONData == nil { preference.JSONData = &pref.PreferenceJSONData{} @@ -257,6 +268,9 @@ func preferenceData(cmd *pref.SavePreferenceCommand) (*pref.PreferenceJSONData, Language: cmd.Language, } + if cmd.Navbar != nil { + jsonData.Navbar = *cmd.Navbar + } if cmd.QueryHistory != nil { jsonData.QueryHistory = *cmd.QueryHistory } diff --git a/public/api-merged.json b/public/api-merged.json index e4aa026504a..2fe5f57a88f 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -17014,6 +17014,18 @@ } } }, + "NavbarPreference": { + "type": "object", + "title": "NavbarPreference defines model for NavbarPreference.", + "properties": { + "savedItemIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "NewApiKeyResult": { "type": "object", "properties": { @@ -17577,6 +17589,9 @@ "language": { "type": "string" }, + "navbar": { + "$ref": "#/definitions/NavbarPreference" + }, "queryHistory": { "$ref": "#/definitions/QueryHistoryPreference" }, @@ -18168,6 +18183,9 @@ "description": "Selected language (beta)", "type": "string" }, + "navbar": { + "$ref": "#/definitions/NavbarPreference" + }, "queryHistory": { "$ref": "#/definitions/QueryHistoryPreference" }, @@ -21419,6 +21437,9 @@ "language": { "type": "string" }, + "navbar": { + "$ref": "#/definitions/NavbarPreference" + }, "queryHistory": { "$ref": "#/definitions/QueryHistoryPreference" }, diff --git a/public/openapi3.json b/public/openapi3.json index 715243edd83..46d1e24a1c3 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -7140,6 +7140,18 @@ }, "type": "object" }, + "NavbarPreference": { + "properties": { + "savedItemIds": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "title": "NavbarPreference defines model for NavbarPreference.", + "type": "object" + }, "NewApiKeyResult": { "properties": { "id": { @@ -7702,6 +7714,9 @@ "language": { "type": "string" }, + "navbar": { + "$ref": "#/components/schemas/NavbarPreference" + }, "queryHistory": { "$ref": "#/components/schemas/QueryHistoryPreference" }, @@ -8293,6 +8308,9 @@ "description": "Selected language (beta)", "type": "string" }, + "navbar": { + "$ref": "#/components/schemas/NavbarPreference" + }, "queryHistory": { "$ref": "#/components/schemas/QueryHistoryPreference" }, @@ -11543,6 +11561,9 @@ "language": { "type": "string" }, + "navbar": { + "$ref": "#/components/schemas/NavbarPreference" + }, "queryHistory": { "$ref": "#/components/schemas/QueryHistoryPreference" }, From bfe77ab53038fa8a89d4c5021f92aff3e25cdd7b Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Wed, 3 Jul 2024 12:06:10 +0200 Subject: [PATCH 07/39] Users: Ensure default admin is created with a valid uid (#89981) Users: Ensure default admin has a valid uid --- pkg/services/sqlstore/user.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 00861d9bc39..991643b73ae 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -64,6 +64,7 @@ func (ss *SQLStore) createUser(ctx context.Context, sess *DBSession, args user.C // create user usr = user.User{ + UID: util.GenerateShortUID(), Email: args.Email, Login: args.Login, IsAdmin: args.IsAdmin, From 7448f22f91946ab240f33e5f188c710962d778e0 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Wed, 3 Jul 2024 11:42:00 +0100 Subject: [PATCH 08/39] E2C: Create Snapshot frontend (#89901) * First pass at using new async apis * async api tweaks * clean up async api usage * Update public/app/features/migrate-to-cloud/onprem/Page.tsx Co-authored-by: Alex Khomenko * Update public/app/features/migrate-to-cloud/onprem/Page.tsx Co-authored-by: Alex Khomenko * fix syntax --------- Co-authored-by: Alex Khomenko --- .../migrate-to-cloud/api/endpoints.gen.ts | 150 ++++++++++++++---- .../features/migrate-to-cloud/api/index.ts | 29 ++-- .../features/migrate-to-cloud/onprem/Page.tsx | 124 ++++++++++----- public/locales/en-US/grafana.json | 5 +- public/locales/pseudo-LOCALE/grafana.json | 5 +- scripts/generate-rtk-apis.ts | 17 +- 6 files changed, 237 insertions(+), 93 deletions(-) diff --git a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts index 54beb3847ef..537535a7920 100644 --- a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts +++ b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts @@ -11,24 +11,48 @@ const injectedRtkApi = api.injectEndpoints({ body: queryArg.cloudMigrationSessionRequestDto, }), }), - getCloudMigrationRun: build.query({ - query: (queryArg) => ({ url: `/cloudmigration/migration/run/${queryArg.runUid}` }), - }), deleteSession: build.mutation({ query: (queryArg) => ({ url: `/cloudmigration/migration/${queryArg.uid}`, method: 'DELETE' }), }), getSession: build.query({ query: (queryArg) => ({ url: `/cloudmigration/migration/${queryArg.uid}` }), }), - getCloudMigrationRunList: build.query({ - query: (queryArg) => ({ url: `/cloudmigration/migration/${queryArg.uid}/run` }), + createSnapshot: build.mutation({ + query: (queryArg) => ({ url: `/cloudmigration/migration/${queryArg.uid}/snapshot`, method: 'POST' }), }), - runCloudMigration: build.mutation({ - query: (queryArg) => ({ url: `/cloudmigration/migration/${queryArg.uid}/run`, method: 'POST' }), + getSnapshot: build.query({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot/${queryArg.snapshotUid}`, + params: { resultPage: queryArg.resultPage, resultLimit: queryArg.resultLimit }, + }), + }), + cancelSnapshot: build.mutation({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot/${queryArg.snapshotUid}/cancel`, + method: 'POST', + }), + }), + uploadSnapshot: build.mutation({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot/${queryArg.snapshotUid}/upload`, + method: 'POST', + }), + }), + getShapshotList: build.query({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshots`, + params: { page: queryArg.page, limit: queryArg.limit }, + }), + }), + getCloudMigrationToken: build.query({ + query: () => ({ url: `/cloudmigration/token` }), }), createCloudMigrationToken: build.mutation({ query: () => ({ url: `/cloudmigration/token`, method: 'POST' }), }), + deleteCloudMigrationToken: build.mutation({ + query: (queryArg) => ({ url: `/cloudmigration/token/${queryArg.uid}`, method: 'DELETE' }), + }), getDashboardByUid: build.query({ query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.uid}` }), }), @@ -42,11 +66,6 @@ export type CreateSessionApiResponse = /** status 200 (empty) */ CloudMigrationS export type CreateSessionApiArg = { cloudMigrationSessionRequestDto: CloudMigrationSessionRequestDto; }; -export type GetCloudMigrationRunApiResponse = /** status 200 (empty) */ MigrateDataResponseDto; -export type GetCloudMigrationRunApiArg = { - /** RunUID of a migration run */ - runUid: string; -}; export type DeleteSessionApiResponse = unknown; export type DeleteSessionApiArg = { /** UID of a migration session */ @@ -57,18 +76,54 @@ export type GetSessionApiArg = { /** UID of a migration session */ uid: string; }; -export type GetCloudMigrationRunListApiResponse = /** status 200 (empty) */ SnapshotListDto; -export type GetCloudMigrationRunListApiArg = { - /** UID of a migration */ +export type CreateSnapshotApiResponse = /** status 200 (empty) */ CreateSnapshotResponseDto; +export type CreateSnapshotApiArg = { + /** UID of a session */ uid: string; }; -export type RunCloudMigrationApiResponse = /** status 200 (empty) */ MigrateDataResponseDto; -export type RunCloudMigrationApiArg = { - /** UID of a migration */ +export type GetSnapshotApiResponse = /** status 200 (empty) */ GetSnapshotResponseDto; +export type GetSnapshotApiArg = { + /** ResultPage is used for pagination with ResultLimit */ + resultPage?: number; + /** Max limit for snapshot results returned. */ + resultLimit?: number; + /** Session UID of a session */ + uid: string; + /** UID of a snapshot */ + snapshotUid: string; +}; +export type CancelSnapshotApiResponse = /** status 200 (empty) */ void; +export type CancelSnapshotApiArg = { + /** Session UID of a session */ + uid: string; + /** UID of a snapshot */ + snapshotUid: string; +}; +export type UploadSnapshotApiResponse = /** status 200 (empty) */ void; +export type UploadSnapshotApiArg = { + /** Session UID of a session */ + uid: string; + /** UID of a snapshot */ + snapshotUid: string; +}; +export type GetShapshotListApiResponse = /** status 200 (empty) */ SnapshotListResponseDto; +export type GetShapshotListApiArg = { + /** Page is used for pagination with limit */ + page?: number; + /** Max limit for results returned. */ + limit?: number; + /** Session UID of a session */ uid: string; }; +export type GetCloudMigrationTokenApiResponse = /** status 200 (empty) */ GetAccessTokenResponseDto; +export type GetCloudMigrationTokenApiArg = void; export type CreateCloudMigrationTokenApiResponse = /** status 200 (empty) */ CreateAccessTokenResponseDto; export type CreateCloudMigrationTokenApiArg = void; +export type DeleteCloudMigrationTokenApiResponse = /** status 204 (empty) */ void; +export type DeleteCloudMigrationTokenApiArg = { + /** UID of a cloud migration token */ + uid: string; +}; export type GetDashboardByUidApiResponse = /** status 200 (empty) */ DashboardFullWithMeta; export type GetDashboardByUidApiArg = { uid: string; @@ -95,21 +150,58 @@ export type ErrorResponseBody = { export type CloudMigrationSessionRequestDto = { authToken?: string; }; +export type CreateSnapshotResponseDto = { + uid?: string; +}; export type MigrateDataResponseItemDto = { error?: string; refId: string; - status: 'OK' | 'ERROR'; + status: 'OK' | 'ERROR' | 'PENDING' | 'UNKNOWN'; type: 'DASHBOARD' | 'DATASOURCE' | 'FOLDER'; }; -export type MigrateDataResponseDto = { - items?: MigrateDataResponseItemDto[]; +export type GetSnapshotResponseDto = { + created?: string; + finished?: string; + results?: MigrateDataResponseItemDto[]; + sessionUid?: string; + status?: + | 'INITIALIZING' + | 'CREATING' + | 'PENDING_UPLOAD' + | 'UPLOADING' + | 'PENDING_PROCESSING' + | 'PROCESSING' + | 'FINISHED' + | 'ERROR' + | 'UNKNOWN'; uid?: string; }; -export type MigrateDataResponseListDto = { +export type SnapshotDto = { + created?: string; + finished?: string; + sessionUid?: string; + status?: + | 'INITIALIZING' + | 'CREATING' + | 'PENDING_UPLOAD' + | 'UPLOADING' + | 'PENDING_PROCESSING' + | 'PROCESSING' + | 'FINISHED' + | 'ERROR' + | 'UNKNOWN'; uid?: string; }; -export type SnapshotListDto = { - runs?: MigrateDataResponseListDto[]; +export type SnapshotListResponseDto = { + snapshots?: SnapshotDto[]; +}; +export type GetAccessTokenResponseDto = { + createdAt?: string; + displayName?: string; + expiresAt?: string; + firstUsedAt?: string; + id?: string; + lastUsedAt?: string; }; export type CreateAccessTokenResponseDto = { token?: string; @@ -160,11 +252,15 @@ export type DashboardFullWithMeta = { export const { useGetSessionListQuery, useCreateSessionMutation, - useGetCloudMigrationRunQuery, useDeleteSessionMutation, useGetSessionQuery, - useGetCloudMigrationRunListQuery, - useRunCloudMigrationMutation, + useCreateSnapshotMutation, + useGetSnapshotQuery, + useCancelSnapshotMutation, + useUploadSnapshotMutation, + useGetShapshotListQuery, + useGetCloudMigrationTokenQuery, useCreateCloudMigrationTokenMutation, + useDeleteCloudMigrationTokenMutation, useGetDashboardByUidQuery, } = injectedRtkApi; diff --git a/public/app/features/migrate-to-cloud/api/index.ts b/public/app/features/migrate-to-cloud/api/index.ts index 696a5a8505b..6b05a867aef 100644 --- a/public/app/features/migrate-to-cloud/api/index.ts +++ b/public/app/features/migrate-to-cloud/api/index.ts @@ -4,42 +4,45 @@ import { BaseQueryFn, EndpointDefinition } from '@reduxjs/toolkit/dist/query'; import { generatedAPI } from './endpoints.gen'; export const cloudMigrationAPI = generatedAPI.enhanceEndpoints({ - addTagTypes: ['cloud-migration-config', 'cloud-migration-run', 'cloud-migration-run-list'], + addTagTypes: ['cloud-migration-session', 'cloud-migration-snapshot'], + endpoints: { // Cloud-side - create token createCloudMigrationToken: suppressErrorsOnQuery, // List Cloud Configs getSessionList: { - providesTags: ['cloud-migration-config'] /* should this be a -list? */, + providesTags: ['cloud-migration-session'] /* should this be a -list? */, }, // Create Cloud Config createSession(endpoint) { suppressErrorsOnQuery(endpoint); - endpoint.invalidatesTags = ['cloud-migration-config']; + endpoint.invalidatesTags = ['cloud-migration-session']; }, // Get one Cloud Config getSession: { - providesTags: ['cloud-migration-config'], + providesTags: ['cloud-migration-session'], }, // Delete one Cloud Config deleteSession: { - invalidatesTags: ['cloud-migration-config'], + invalidatesTags: ['cloud-migration-session', 'cloud-migration-snapshot'], }, - getCloudMigrationRunList: { - providesTags: ['cloud-migration-run-list'], + // Snapshot management + getSnapshot: { + providesTags: ['cloud-migration-snapshot'], }, - - getCloudMigrationRun: { - providesTags: ['cloud-migration-run'], + getShapshotList: { + providesTags: ['cloud-migration-snapshot'], }, - - runCloudMigration: { - invalidatesTags: ['cloud-migration-run-list'], + createSnapshot: { + invalidatesTags: ['cloud-migration-snapshot'], + }, + uploadSnapshot: { + invalidatesTags: ['cloud-migration-snapshot'], }, getDashboardByUid: suppressErrorsOnQuery, diff --git a/public/app/features/migrate-to-cloud/onprem/Page.tsx b/public/app/features/migrate-to-cloud/onprem/Page.tsx index c92a1de23c0..91b59f801a4 100644 --- a/public/app/features/migrate-to-cloud/onprem/Page.tsx +++ b/public/app/features/migrate-to-cloud/onprem/Page.tsx @@ -1,15 +1,17 @@ import { skipToken } from '@reduxjs/toolkit/query/react'; -import { useCallback, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { Alert, Box, Button, Stack } from '@grafana/ui'; import { Trans, t } from 'app/core/internationalization'; import { + SnapshotDto, + useCreateSnapshotMutation, useDeleteSessionMutation, - useGetCloudMigrationRunListQuery, - useGetCloudMigrationRunQuery, useGetSessionListQuery, - useRunCloudMigrationMutation, + useGetShapshotListQuery, + useGetSnapshotQuery, + useUploadSnapshotMutation, } from '../api'; import { DisconnectModal } from './DisconnectModal'; @@ -32,7 +34,7 @@ import { ResourcesTable } from './ResourcesTable'; * 2. call GetCloudMigrationRun with the ID from first step to list the result of that migration */ -function useGetLatestMigrationDestination() { +function useGetLatestSession() { const result = useGetSessionListQuery(); const latestMigration = result.data?.sessions?.at(-1); @@ -42,64 +44,88 @@ function useGetLatestMigrationDestination() { }; } -function useGetLatestMigrationRun(migrationUid?: string) { - const listResult = useGetCloudMigrationRunListQuery(migrationUid ? { uid: migrationUid } : skipToken); - const latestMigrationRun = listResult.data?.runs?.at(-1); +const SHOULD_POLL_STATUSES: Array = [ + 'INITIALIZING', + 'CREATING', + 'UPLOADING', + 'PENDING_PROCESSING', + 'PROCESSING', +]; - const runResult = useGetCloudMigrationRunQuery( - latestMigrationRun?.uid && migrationUid ? { runUid: latestMigrationRun.uid } : skipToken - ); +const STATUS_POLL_INTERVAL = 5 * 1000; + +function useGetLatestSnapshot(sessionUid?: string) { + const [shouldPoll, setShouldPoll] = useState(false); + + const listResult = useGetShapshotListQuery(sessionUid ? { uid: sessionUid } : skipToken); + const lastItem = listResult.data?.snapshots?.at(-1); // TODO: account for pagination and ensure we're truely getting the last one + + const getSnapshotQueryArgs = sessionUid && lastItem?.uid ? { uid: sessionUid, snapshotUid: lastItem.uid } : skipToken; + + const snapshotResult = useGetSnapshotQuery(getSnapshotQueryArgs, { + pollingInterval: shouldPoll ? STATUS_POLL_INTERVAL : 0, + skipPollingIfUnfocused: true, + }); + + useEffect(() => { + const shouldPoll = SHOULD_POLL_STATUSES.includes(snapshotResult.data?.status); + setShouldPoll(shouldPoll); + }, [snapshotResult?.data?.status]); return { - ...runResult, + ...snapshotResult, - data: runResult.data, + error: listResult.error || snapshotResult.error, - error: listResult.error || runResult.error, - - isError: listResult.isError || runResult.isError, - isLoading: listResult.isLoading || runResult.isLoading, - isFetching: listResult.isFetching || runResult.isFetching, + // isSuccess and isUninitialised should always be from snapshotResult + // as only the 'final' values from those are important + isError: listResult.isError || snapshotResult.isError, + isLoading: listResult.isLoading || snapshotResult.isLoading, + isFetching: listResult.isFetching || snapshotResult.isFetching, }; } export const Page = () => { const [disconnectModalOpen, setDisconnectModalOpen] = useState(false); - const migrationDestination = useGetLatestMigrationDestination(); - const lastMigrationRun = useGetLatestMigrationRun(migrationDestination.data?.uid); - const [performRunMigration, runMigrationResult] = useRunCloudMigrationMutation(); + const session = useGetLatestSession(); + const snapshot = useGetLatestSnapshot(session.data?.uid); + const [performCreateSnapshot, createSnapshotResult] = useCreateSnapshotMutation(); + const [performUploadSnapshot, uploadSnapshotResult] = useUploadSnapshotMutation(); const [performDisconnect, disconnectResult] = useDeleteSessionMutation(); + const sessionUid = session.data?.uid; + const snapshotUid = snapshot.data?.uid; + const migrationMeta = session.data; + const isInitialLoading = session.isLoading; + // isBusy is not a loading state, but indicates that the system is doing *something* // and all buttons should be disabled const isBusy = - runMigrationResult.isLoading || - migrationDestination.isFetching || - lastMigrationRun.isFetching || + createSnapshotResult.isLoading || + uploadSnapshotResult.isLoading || + session.isLoading || + snapshot.isLoading || disconnectResult.isLoading; - const resources = lastMigrationRun.data?.items; - const migrationDestUID = migrationDestination.data?.uid; + const resources = snapshot.data?.results; const handleDisconnect = useCallback(async () => { - if (!migrationDestUID) { - return; + if (sessionUid) { + performDisconnect({ uid: sessionUid }); } + }, [performDisconnect, sessionUid]); - const resp = await performDisconnect({ uid: migrationDestUID }); - if (!('error' in resp)) { - setDisconnectModalOpen(false); + const handleCreateSnapshot = useCallback(() => { + if (sessionUid) { + performCreateSnapshot({ uid: sessionUid }); } - }, [migrationDestUID, performDisconnect]); + }, [performCreateSnapshot, sessionUid]); - const handleStartMigration = useCallback(() => { - if (migrationDestination.data?.uid) { - performRunMigration({ uid: migrationDestination.data?.uid }); + const handleUploadSnapshot = useCallback(() => { + if (sessionUid && snapshotUid) { + performUploadSnapshot({ uid: sessionUid, snapshotUid: snapshotUid }); } - }, [performRunMigration, migrationDestination]); - - const migrationMeta = migrationDestination.data; - const isInitialLoading = migrationDestination.isLoading; + }, [performUploadSnapshot, sessionUid, snapshotUid]); if (isInitialLoading) { // TODO: better loading state @@ -111,7 +137,7 @@ export const Page = () => { return ( <> - {runMigrationResult.isError && ( + {createSnapshotResult.isError && ( { } /> + + + + )} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 92d54e9c3ae..fdcdb1efc8f 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -968,8 +968,9 @@ "disconnect-error-title": "There was an error disconnecting", "run-migration-error-description": "See the Grafana server logs for more details", "run-migration-error-title": "There was an error migrating your resources", - "start-migration": "Upload everything", - "target-stack-title": "Uploading to" + "start-migration": "Build snapshot", + "target-stack-title": "Uploading to", + "upload-migration": "Upload & migrate snapshot" }, "token-status": { "active": "Token created and active", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 9a3df4cb747..2aee9750ec0 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -968,8 +968,9 @@ "disconnect-error-title": "Ŧĥęřę ŵäş äʼn ęřřőř đįşčőʼnʼnęčŧįʼnģ", "run-migration-error-description": "Ŝęę ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş", "run-migration-error-title": "Ŧĥęřę ŵäş äʼn ęřřőř mįģřäŧįʼnģ yőūř řęşőūřčęş", - "start-migration": "Ůpľőäđ ęvęřyŧĥįʼnģ", - "target-stack-title": "Ůpľőäđįʼnģ ŧő" + "start-migration": "ßūįľđ şʼnäpşĥőŧ", + "target-stack-title": "Ůpľőäđįʼnģ ŧő", + "upload-migration": "Ůpľőäđ & mįģřäŧę şʼnäpşĥőŧ" }, "token-status": { "active": "Ŧőĸęʼn čřęäŧęđ äʼnđ äčŧįvę", diff --git a/scripts/generate-rtk-apis.ts b/scripts/generate-rtk-apis.ts index af419895beb..72724c99a84 100644 --- a/scripts/generate-rtk-apis.ts +++ b/scripts/generate-rtk-apis.ts @@ -12,14 +12,21 @@ const config: ConfigFile = { apiFile: '../public/app/features/migrate-to-cloud/api/baseAPI.ts', apiImport: 'baseAPI', filterEndpoints: [ - 'createCloudMigrationToken', 'getSessionList', 'getSession', - 'createSession', 'deleteSession', - 'runCloudMigration', - 'getCloudMigrationRun', - 'getCloudMigrationRunList', + 'createSession', + + 'getShapshotList', + 'getSnapshot', + 'uploadSnapshot', + 'createSnapshot', + 'cancelSnapshot', + + 'createCloudMigrationToken', + 'deleteCloudMigrationToken', + 'getCloudMigrationToken', + 'getDashboardByUid', ], }, From cbbc12a31ba632a6f6b1a6bd09ed1e3832e3881d Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Wed, 3 Jul 2024 13:37:26 +0200 Subject: [PATCH 09/39] Zanzana: Sync team memberships (#89983) * Zanzana: Use uid for users and teams * Zanzana: Team membership migrator --------- Co-authored-by: Alexander Zobnin --- .../accesscontrol/migrator/zanzana.go | 82 +++++++++++++++---- pkg/services/authz/zanzana/zanzana.go | 7 +- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/pkg/services/accesscontrol/migrator/zanzana.go b/pkg/services/accesscontrol/migrator/zanzana.go index 2467b0039a0..426485bde23 100644 --- a/pkg/services/accesscontrol/migrator/zanzana.go +++ b/pkg/services/accesscontrol/migrator/zanzana.go @@ -3,7 +3,6 @@ package migrator import ( "context" "fmt" - "strconv" "strings" openfgav1 "github.com/openfga/api/proto/openfga/v1" @@ -30,9 +29,14 @@ type ZanzanaSynchroniser struct { func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...TupleCollector) *ZanzanaSynchroniser { // Append shared collectors that is used by both enterprise and oss - collectors = append(collectors, managedPermissionsCollector(store)) + collectors = append( + collectors, + teamMembershipCollector(store), + managedPermissionsCollector(store), + ) return &ZanzanaSynchroniser{ + client: client, log: log.New("zanzana.sync"), collectors: collectors, } @@ -75,21 +79,24 @@ func managedPermissionsCollector(store db.DB) TupleCollector { return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { const collectorID = "managed" const query = ` - SELECT ur.user_id, p.action, p.kind, p.identifier, r.org_id FROM permission p - INNER JOIN role r on p.role_id = r.id - LEFT JOIN user_role ur on r.id = ur.role_id - LEFT JOIN team_role tr on r.id = tr.role_id - LEFT JOIN builtin_role br on r.id = br.role_id - WHERE r.name LIKE 'managed:%' - ` + SELECT u.uid as user_uid, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id + FROM permission p + INNER JOIN role r ON p.role_id = r.id + LEFT JOIN user_role ur ON r.id = ur.role_id + LEFT JOIN user u ON u.id = ur.user_id + LEFT JOIN team_role tr ON r.id = tr.role_id + LEFT JOIN team t ON tr.team_id = t.id + LEFT JOIN builtin_role br ON r.id = br.role_id + WHERE r.name LIKE 'managed:%' + ` type Permission struct { RoleName string `xorm:"role_name"` OrgID int64 `xorm:"org_id"` Action string `xorm:"action"` Kind string Identifier string - UserID int64 `xorm:"user_id"` - TeamID int64 `xorm:"user_id"` + UserUID string `xorm:"user_uid"` + TeamUID string `xorm:"team_uid"` } var permissions []Permission @@ -103,10 +110,10 @@ func managedPermissionsCollector(store db.DB) TupleCollector { for _, p := range permissions { var subject string - if p.UserID > 0 { - subject = zanzana.NewObject(zanzana.TypeUser, strconv.FormatInt(p.UserID, 10)) - } else if p.TeamID > 0 { - subject = zanzana.NewObject(zanzana.TypeTeam, strconv.FormatInt(p.TeamID, 10)) + if len(p.UserUID) > 0 { + subject = zanzana.NewObject(zanzana.TypeUser, p.UserUID) + } else if len(p.TeamUID) > 0 { + subject = zanzana.NewObject(zanzana.TypeTeam, p.TeamUID) } else { // FIXME(kalleep): Unsuported role binding (org role). We need to have basic roles in place continue @@ -126,3 +133,48 @@ func managedPermissionsCollector(store db.DB) TupleCollector { return nil } } + +func teamMembershipCollector(store db.DB) TupleCollector { + return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { + const collectorID = "team_membership" + const query = ` + SELECT t.uid as team_uid, u.uid as user_uid, tm.permission + FROM team_member tm + INNER JOIN team t ON tm.team_id = t.id + INNER JOIN user u ON tm.user_id = u.id + ` + + type membership struct { + TeamUID string `xorm:"team_uid"` + UserUID string `xorm:"user_uid"` + Permission int + } + + var memberships []membership + err := store.WithDbSession(ctx, func(sess *db.Session) error { + return sess.SQL(query).Find(&memberships) + }) + + if err != nil { + return err + } + + for _, m := range memberships { + tuple := &openfgav1.TupleKey{ + User: zanzana.NewObject(zanzana.TypeUser, m.UserUID), + Object: zanzana.NewObject(zanzana.TypeTeam, m.TeamUID), + } + + // Admin permission is 4 and member 0 + if m.Permission == 4 { + tuple.Relation = zanzana.RelationTeamAdmin + } else { + tuple.Relation = zanzana.RelationTeamMember + } + + tuples[collectorID] = append(tuples[collectorID], tuple) + } + + return nil + } +} diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index 5d8ecd0c9d6..3062511a031 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -12,6 +12,11 @@ const ( TypeTeam string = "team" ) +const ( + RelationTeamMember string = "member" + RelationTeamAdmin string = "admin" +) + func NewObject(typ, id string) string { return fmt.Sprintf("%s:%s", typ, id) } @@ -49,7 +54,7 @@ func TranslateToTuple(user string, action, kind, identifier string, orgID int64) tuple.User = user tuple.Relation = relation - // UID in grafana are not guarantee to be unique across orgs so we need to scope them. + // Some uid:s in grafana are not guarantee to be unique across orgs so we need to scope them. if t.orgScoped { tuple.Object = NewScopedObject(t.typ, identifier, strconv.FormatInt(orgID, 10)) } else { From c538d7ca6f6e9b85942fafbf859af1d593bab088 Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Wed, 3 Jul 2024 14:08:44 +0200 Subject: [PATCH 10/39] Chore: Update go.mod go.sum and go.work.sum files (#89992) * Update go.mod go.sum and go.work.sum files * set owners --- go.mod | 4 ++-- go.work.sum | 36 ++++++++++++++++++++++++++++++++++-- pkg/promlib/go.mod | 7 +++++++ pkg/promlib/go.sum | 6 ++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 03d76cabd00..709f49e3c88 100644 --- a/go.mod +++ b/go.mod @@ -306,7 +306,7 @@ require ( github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db // indirect github.com/grafana/sqlds/v3 v3.2.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect; @grafana/plugins-platform-backend - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // @grafana/identity-access-team github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect @@ -380,7 +380,7 @@ require ( github.com/redis/rueidis v1.0.16 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rs/cors v1.10.1 // indirect + github.com/rs/cors v1.10.1 // @grafana/identity-access-team github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/segmentio/asm v1.2.0 // indirect diff --git a/go.work.sum b/go.work.sum index 439079c2cb9..0f3ec49aba8 100644 --- a/go.work.sum +++ b/go.work.sum @@ -412,7 +412,9 @@ github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWV github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= +github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= +github.com/ClickHouse/clickhouse-go/v2 v2.17.1 h1:ZCmAYWpu75IyEi7+Yrs/uaAjiCGY5wfW5kXo64exkX4= github.com/ClickHouse/clickhouse-go/v2 v2.17.1/go.mod h1:rkGTvFDTLqLIm0ma+13xmcCfr/08Gvs7KmFt1tgiWHQ= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= @@ -452,6 +454,7 @@ github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:m github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.6 h1:U68crOE3y3MPttCMQGywZOLrTeF5HHJ3/vDBCJn9/bA= github.com/OneOfOne/xxhash v1.2.6/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -471,6 +474,7 @@ github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSd github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw= +github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19 h1:iXUgAaqDcIUGbRoy2TdeofRG/j1zpGRSEmNK05T+bi8= @@ -482,12 +486,15 @@ github.com/alecthomas/kong v0.2.11/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QL github.com/alecthomas/participle/v2 v2.1.0 h1:z7dElHRrOEEq45F2TG5cbQihMtNTv8vwldytDj7Wrz4= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alexflint/go-arg v1.4.2 h1:lDWZAXxpAnZUq4qwb86p/3rIJJ2Li81EoMbTMujhVa0= github.com/alexflint/go-arg v1.4.2/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= +github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70= github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= @@ -524,6 +531,7 @@ github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= +github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs= github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bufbuild/protovalidate-go v0.2.1 h1:pJr07sYhliyfj/STAM7hU4J3FKpVeLVKvOBmOTN8j+s= @@ -780,7 +788,9 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 h1:8yY/I9 github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= +github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn0jg4= github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ= +github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0= github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= @@ -824,7 +834,9 @@ github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITL github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ= github.com/go-fonts/latin-modern v0.2.0 h1:5/Tv1Ek/QCr20C6ZOz15vw3g7GELYL98KWr8Hgo+3vk= @@ -943,8 +955,6 @@ github.com/grafana/dataplane/sdata v0.0.7 h1:CImITypIyS1jxijCR6xqKx71JnYAxcwpH9C github.com/grafana/dataplane/sdata v0.0.7/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b h1:Ha+kSIoTutf4ytlVw/SaEclDUloYx0+FXDKJWKhNbE4= github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b/go.mod h1:3UsooRp7yW5/NJQBlXcTsAHOoykEhNUYXkQ3r6ehEEY= -github.com/grafana/grafana-aws-sdk v0.28.0 h1:ShdA+msLPGJGWWS1SFUYnF+ch1G3gUOlAdGJi6h4sgU= -github.com/grafana/grafana-aws-sdk v0.28.0/go.mod h1:ZSVPU7IIJSi5lEg+K3Js+EUpZLXxUaBdaQWH+As1ihI= github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJf9zRdVxWV/W4Wi+Ox6gifgs= github.com/grafana/grafana-plugin-sdk-go v0.215.0/go.mod h1:nBsh3jRItKQUXDF2BQkiQCPxqrsSQeb+7hiFyJTO1RE= github.com/grafana/grafana-plugin-sdk-go v0.216.0/go.mod h1:FdvSvOliqpVLnytM7e89zCFyYPDE6VOn9SIjVQRvVxM= @@ -1037,6 +1047,7 @@ github.com/jhump/goprotoc v0.5.0 h1:Y1UgUX+txUznfqcGdDef8ZOVlyQvnV0pKWZH08RmZuo= github.com/jmattheis/goverter v1.4.0 h1:SrboBYMpGkj1XSgFhWwqzdP024zIa1+58YzUm+0jcBE= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= @@ -1065,7 +1076,9 @@ github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kevinmbeaulieu/eq-go v1.0.0 h1:AQgYHURDOmnVJ62jnEk0W/7yFKEn+Lv8RHN6t7mB0Zo= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= @@ -1101,10 +1114,12 @@ github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0ot github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM= github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743 h1:143Bb8f8DuGWck/xpNUOckBVYfFbBTnLevfRZ1aVVqo= github.com/lightstep/lightstep-tracer-go v0.18.1 h1:vi1F1IQ8N7hNWytK9DpJsUfQhGuNSc19z330K6vl4zk= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= +github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/lyft/protoc-gen-star v0.6.1 h1:erE0rdztuaDq3bpGifD95wfoPrSZc95nGA6tbiNYh6M= github.com/lyft/protoc-gen-star/v2 v2.0.3 h1:/3+/2sWyXeMLzKd1bX+ixWKgEMsULrIivpDsuaF441o= @@ -1123,6 +1138,7 @@ github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matryer/moq v0.3.1 h1:kLDiBJoGcusWS2BixGyTkF224aSCD8nLY24tj/NcTCs= github.com/matryer/moq v0.3.1/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFKH4s= +github.com/matryer/moq v0.3.3 h1:pScMH9VyrdT4S93yiLpVyU8rCDqGQr24uOyBxmktG5Q= github.com/matryer/moq v0.3.3/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFKH4s= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -1259,6 +1275,7 @@ github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfP github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/pact-foundation/pact-go v1.0.4 h1:OYkFijGHoZAYbOIb1LWXrwKQbMMRUv1oQ89blD2Mh2Q= +github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw= @@ -1409,6 +1426,7 @@ github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYm github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tursodatabase/libsql-client-go v0.0.0-20240411070317-a1138d155304 h1:Y6cw8yjWCEJDy5Bll7HjTinkgTQU55AXiKSEe29SpgA= github.com/tursodatabase/libsql-client-go v0.0.0-20240411070317-a1138d155304/go.mod h1:2Fu26tjM011BLeR5+jwTfs6DX/fNMEWV/3CBZvggrA4= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= @@ -1430,6 +1448,7 @@ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+ github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vburenin/ifacemaker v1.2.1 h1:3Vq8B/bfBgjWTkv+jDg4dVL1KHt3k1K4lO7XRxYA2sk= +github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw= github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= github.com/vinzenz/yaml v0.0.0-20170920082545-91409cdd725d h1:3wDi6J5APMqaHBVPuVd7RmHD2gRTfqbdcVSpCNoUWtk= github.com/vinzenz/yaml v0.0.0-20170920082545-91409cdd725d/go.mod h1:mb5taDqMnJiZNRQ3+02W2IFG+oEz1+dTuCXkp4jpkfo= @@ -1470,7 +1489,9 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= +github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1 h1:Ebo6J5AMXgJ3A438ECYotA0aK7ETqjQx9WoZvVxzKBE= github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1/go.mod h1:udNPW8eupyH/EZocecFmaSNJacKKYjzQa7cVgX5U2nc= github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= @@ -1485,6 +1506,7 @@ github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY3 github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= @@ -1708,6 +1730,7 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= @@ -1758,9 +1781,18 @@ k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= +modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= +modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 736579dc90a..201f24f3496 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -13,6 +13,7 @@ require ( go.opentelemetry.io/otel v1.26.0 go.opentelemetry.io/otel/trace v1.26.0 golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f + k8s.io/apimachinery v0.29.3 ) require ( @@ -118,7 +119,13 @@ require ( google.golang.org/grpc v1.64.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index e3e1bb59f3e..a4775ee92f8 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -187,6 +187,7 @@ github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -289,14 +290,19 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= From 306ae8b4f531739f59a8465ba5095a10b55b21b7 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Wed, 3 Jul 2024 14:14:58 +0200 Subject: [PATCH 11/39] Grafana/data: Remove barrel files part 2 (#89850) * chore(grafana-data): remove datetime barrel file and update all imports * chore(grafana-data): remove types barrel file and update imports * chore(grafana-data): update types imports across package files * chore(grafana-data): fix erroronous type export definition on OrgRole * chore(grafana-data): fix errornous re-export type declarations and missing datetime/common exports * chore(azure-monitor): fix import pointing to nested grafana-data barrel file --- .../DataSourcePluginContextProvider.tsx | 2 +- .../src/dataframe/ArrayDataFrame.test.ts | 2 +- .../src/dataframe/ArrayDataFrame.ts | 2 +- .../src/dataframe/DataFrameJSON.ts | 3 +- .../src/dataframe/DataFrameView.ts | 2 +- .../src/dataframe/StreamingDataFrame.test.ts | 2 +- .../src/dataframe/StreamingDataFrame.ts | 3 +- .../src/dataframe/processDataFrame.test.ts | 3 +- .../src/dataframe/processDataFrame.ts | 26 +- .../grafana-data/src/dataframe/utils.test.ts | 2 +- .../grafana-data/src/datetime/datemath.ts | 2 +- .../grafana-data/src/datetime/formatter.ts | 2 +- packages/grafana-data/src/datetime/index.ts | 11 - .../src/datetime/rangeutil.test.ts | 103 ++-- .../grafana-data/src/datetime/timezones.ts | 2 +- packages/grafana-data/src/events/common.ts | 3 +- .../src/field/displayProcessor.test.ts | 5 +- .../src/field/displayProcessor.ts | 6 +- .../grafana-data/src/field/fieldColor.test.ts | 3 +- packages/grafana-data/src/field/fieldColor.ts | 4 +- .../grafana-data/src/field/fieldComparers.ts | 2 +- .../src/field/fieldDisplay.test.ts | 3 +- .../grafana-data/src/field/fieldDisplay.ts | 19 +- .../src/field/fieldOverrides.test.ts | 24 +- .../grafana-data/src/field/fieldOverrides.ts | 32 +- .../grafana-data/src/field/fieldState.test.ts | 2 +- packages/grafana-data/src/field/fieldState.ts | 8 +- .../src/field/getFieldDisplayValuesProxy.ts | 4 +- .../src/field/overrides/processors.ts | 16 +- packages/grafana-data/src/field/scale.test.ts | 4 +- packages/grafana-data/src/field/scale.ts | 3 +- .../standardFieldConfigEditorRegistry.ts | 4 +- .../grafana-data/src/field/thresholds.test.ts | 3 +- packages/grafana-data/src/field/thresholds.ts | 4 +- packages/grafana-data/src/geo/layer.ts | 2 +- packages/grafana-data/src/index.ts | 469 +++++++++++++++++- .../src/panel/PanelPlugin.test.tsx | 2 +- .../grafana-data/src/panel/PanelPlugin.ts | 15 +- packages/grafana-data/src/rbac/rbac.ts | 3 +- .../grafana-data/src/themes/createV1Theme.ts | 2 +- .../src/themes/createVisualizationColors.ts | 2 +- .../src/transformations/fieldReducer.test.ts | 3 +- .../src/transformations/fieldReducer.ts | 3 +- .../matchers/fieldValueMatcher.test.ts | 2 +- .../matchers/nameMatcher.test.ts | 2 +- .../standardTransformersRegistry.ts | 3 +- .../transformDataFrame.test.ts | 3 +- .../src/transformations/transformDataFrame.ts | 6 +- .../transformers/calculateField.test.ts | 3 +- .../transformers/calculateField.ts | 4 +- .../transformers/convertFieldType.ts | 4 +- .../transformers/filterByName.test.ts | 2 +- .../transformers/filterByValue.test.ts | 3 +- .../transformers/formatString.ts | 2 +- .../transformers/formatTime.ts | 8 +- .../transformers/groupBy.test.ts | 3 +- .../transformations/transformers/groupBy.ts | 4 +- .../transformers/groupToNestedTable.test.ts | 3 +- .../transformers/groupingToMatrix.test.ts | 3 +- .../transformers/groupingToMatrix.ts | 10 +- .../transformations/transformers/histogram.ts | 3 +- .../transformers/joinByField.test.ts | 3 +- .../transformers/joinByField.ts | 3 +- .../transformers/joinDataFrames.ts | 3 +- .../transformers/labelsToFields.test.ts | 3 +- .../transformers/labelsToFields.ts | 3 +- .../transformers/limit.test.ts | 5 +- .../src/transformations/transformers/limit.ts | 2 +- .../transformers/merge.test.ts | 4 +- .../src/transformations/transformers/noop.ts | 2 +- .../nulls/nullInsertThreshold.test.ts | 2 +- .../transformers/nulls/nullInsertThreshold.ts | 2 +- .../transformers/nulls/nullToValue.test.ts | 2 +- .../transformers/nulls/nullToValue.ts | 2 +- .../transformers/order.test.ts | 3 +- .../src/transformations/transformers/order.ts | 2 +- .../transformers/organize.test.ts | 3 +- .../transformations/transformers/organize.ts | 3 +- .../transformers/reduce.test.ts | 3 +- .../transformers/rename.test.ts | 3 +- .../transformers/renameByRegex.test.ts | 3 +- .../transformers/seriesToRows.test.ts | 3 +- .../transformers/sortBy.test.ts | 3 +- .../transformations/transformers/sortBy.ts | 2 +- .../src/transformations/transformers/utils.ts | 3 +- packages/grafana-data/src/types/config.ts | 8 +- packages/grafana-data/src/types/data.ts | 3 +- packages/grafana-data/src/types/datasource.ts | 7 +- .../grafana-data/src/types/fieldOverrides.ts | 16 +- packages/grafana-data/src/types/index.ts | 69 --- .../src/utils/OptionsUIBuilders.ts | 2 +- packages/grafana-data/src/utils/Registry.ts | 2 +- packages/grafana-data/src/utils/csv.ts | 2 +- .../grafana-data/src/utils/dataLinks.test.ts | 6 +- packages/grafana-data/src/utils/dataLinks.ts | 19 +- packages/grafana-data/src/utils/datasource.ts | 12 +- .../src/utils/deprecationWarning.ts | 2 +- packages/grafana-data/src/utils/legend.ts | 2 +- .../grafana-data/src/utils/location.test.ts | 2 +- packages/grafana-data/src/utils/location.ts | 4 +- .../grafana-data/src/utils/matchPluginId.ts | 2 +- .../grafana-data/src/utils/selectUtils.ts | 2 +- .../grafana-data/src/utils/series.test.ts | 2 +- .../src/utils/tests/mockDataSource.ts | 7 +- .../src/utils/tests/mockStandardProperties.ts | 2 +- .../tests/mockTransformationsRegistry.ts | 2 +- packages/grafana-data/src/utils/url.ts | 6 +- .../src/utils/valueMappings.test.ts | 2 +- .../grafana-data/src/utils/valueMappings.ts | 6 +- packages/grafana-data/src/utils/variables.ts | 2 +- .../src/valueFormats/dateTimeFormatters.ts | 5 +- .../src/valueFormats/valueFormats.test.ts | 4 +- .../src/valueFormats/valueFormats.ts | 2 +- .../src/vector/ArrayVector.test.ts | 2 +- .../MetricsQueryEditor/MetricsQueryEditor.tsx | 2 +- 115 files changed, 772 insertions(+), 384 deletions(-) delete mode 100644 packages/grafana-data/src/datetime/index.ts delete mode 100644 packages/grafana-data/src/types/index.ts diff --git a/packages/grafana-data/src/context/plugins/DataSourcePluginContextProvider.tsx b/packages/grafana-data/src/context/plugins/DataSourcePluginContextProvider.tsx index 8747615a7cf..1464f1a6f6c 100644 --- a/packages/grafana-data/src/context/plugins/DataSourcePluginContextProvider.tsx +++ b/packages/grafana-data/src/context/plugins/DataSourcePluginContextProvider.tsx @@ -1,6 +1,6 @@ import { PropsWithChildren, ReactElement, useMemo } from 'react'; -import { DataSourceInstanceSettings } from '../../types'; +import { DataSourceInstanceSettings } from '../../types/datasource'; import { Context, DataSourcePluginContextType } from './PluginContext'; diff --git a/packages/grafana-data/src/dataframe/ArrayDataFrame.test.ts b/packages/grafana-data/src/dataframe/ArrayDataFrame.test.ts index 624829ba778..c2878481565 100644 --- a/packages/grafana-data/src/dataframe/ArrayDataFrame.test.ts +++ b/packages/grafana-data/src/dataframe/ArrayDataFrame.test.ts @@ -1,4 +1,4 @@ -import { DataFrame } from '../types'; +import { DataFrame } from '../types/dataFrame'; import { ArrayDataFrame, arrayToDataFrame } from './ArrayDataFrame'; import { toDataFrameDTO } from './processDataFrame'; diff --git a/packages/grafana-data/src/dataframe/ArrayDataFrame.ts b/packages/grafana-data/src/dataframe/ArrayDataFrame.ts index 06092b59da9..8817812b5bd 100644 --- a/packages/grafana-data/src/dataframe/ArrayDataFrame.ts +++ b/packages/grafana-data/src/dataframe/ArrayDataFrame.ts @@ -1,4 +1,4 @@ -import { QueryResultMeta } from '../types'; +import { QueryResultMeta } from '../types/data'; import { Field, FieldType, DataFrame, TIME_SERIES_VALUE_FIELD_NAME } from '../types/dataFrame'; import { guessFieldTypeForField } from './processDataFrame'; diff --git a/packages/grafana-data/src/dataframe/DataFrameJSON.ts b/packages/grafana-data/src/dataframe/DataFrameJSON.ts index fdfcb4dc586..6d620e97d6f 100644 --- a/packages/grafana-data/src/dataframe/DataFrameJSON.ts +++ b/packages/grafana-data/src/dataframe/DataFrameJSON.ts @@ -1,4 +1,5 @@ -import { DataFrame, FieldType, FieldConfig, Labels, QueryResultMeta, Field } from '../types'; +import { Labels, QueryResultMeta } from '../types/data'; +import { FieldType, DataFrame, Field, FieldConfig } from '../types/dataFrame'; import { guessFieldTypeFromNameAndValue } from './processDataFrame'; diff --git a/packages/grafana-data/src/dataframe/DataFrameView.ts b/packages/grafana-data/src/dataframe/DataFrameView.ts index 6d8b523140d..d4d3c157d62 100644 --- a/packages/grafana-data/src/dataframe/DataFrameView.ts +++ b/packages/grafana-data/src/dataframe/DataFrameView.ts @@ -1,5 +1,5 @@ -import { DisplayProcessor } from '../types'; import { DataFrame, Field } from '../types/dataFrame'; +import { DisplayProcessor } from '../types/displayValue'; import { FunctionalVector } from '../vector/FunctionalVector'; /** diff --git a/packages/grafana-data/src/dataframe/StreamingDataFrame.test.ts b/packages/grafana-data/src/dataframe/StreamingDataFrame.test.ts index cf8ad95d08e..4b9af8b8906 100644 --- a/packages/grafana-data/src/dataframe/StreamingDataFrame.test.ts +++ b/packages/grafana-data/src/dataframe/StreamingDataFrame.test.ts @@ -1,6 +1,6 @@ import { getFieldDisplayName } from '../field/fieldState'; import { reduceField, ReducerID } from '../transformations/fieldReducer'; -import { FieldType, DataFrame } from '../types'; +import { FieldType, DataFrame } from '../types/dataFrame'; import { DataFrameJSON } from './DataFrameJSON'; import { diff --git a/packages/grafana-data/src/dataframe/StreamingDataFrame.ts b/packages/grafana-data/src/dataframe/StreamingDataFrame.ts index f029bc54f4a..2837f3553c6 100644 --- a/packages/grafana-data/src/dataframe/StreamingDataFrame.ts +++ b/packages/grafana-data/src/dataframe/StreamingDataFrame.ts @@ -1,7 +1,8 @@ import { AlignedData } from 'uplot'; import { join } from '../transformations/transformers/joinDataFrames'; -import { FieldDTO, QueryResultMeta, DataFrame, Field, FieldType, Labels } from '../types'; +import { Labels, QueryResultMeta } from '../types/data'; +import { FieldDTO, DataFrame, Field, FieldType } from '../types/dataFrame'; import { parseLabels } from '../utils/labels'; import { renderLegendFormat } from '../utils/legend'; diff --git a/packages/grafana-data/src/dataframe/processDataFrame.test.ts b/packages/grafana-data/src/dataframe/processDataFrame.test.ts index 2c0cd824dfd..7fab4d0bfb2 100644 --- a/packages/grafana-data/src/dataframe/processDataFrame.test.ts +++ b/packages/grafana-data/src/dataframe/processDataFrame.test.ts @@ -1,5 +1,6 @@ import { dateTime } from '../datetime/moment_wrapper'; -import { DataFrameDTO, Field, FieldType, TableData, TimeSeries } from '../types/index'; +import { TimeSeries, TableData } from '../types/data'; +import { FieldType, DataFrameDTO, Field } from '../types/dataFrame'; import { ArrayDataFrame } from './ArrayDataFrame'; import { diff --git a/packages/grafana-data/src/dataframe/processDataFrame.ts b/packages/grafana-data/src/dataframe/processDataFrame.ts index b5a28b83a07..4a028b6b4fd 100644 --- a/packages/grafana-data/src/dataframe/processDataFrame.ts +++ b/packages/grafana-data/src/dataframe/processDataFrame.ts @@ -1,30 +1,24 @@ // Libraries import { isArray, isBoolean, isNumber, isString } from 'lodash'; -// Types import { isDateTime } from '../datetime/moment_wrapper'; import { fieldIndexComparer } from '../field/fieldComparers'; import { getFieldDisplayName } from '../field/fieldState'; +import { Column, LoadingState, TableData, TimeSeries, TimeSeriesValue } from '../types/data'; import { DataFrame, - Field, - FieldConfig, - TimeSeries, FieldType, - TableData, - Column, - GraphSeriesXY, - TimeSeriesValue, - FieldDTO, - DataFrameDTO, - TIME_SERIES_VALUE_FIELD_NAME, TIME_SERIES_TIME_FIELD_NAME, - DataQueryResponseData, - PanelData, - LoadingState, - GraphSeriesValue, + TIME_SERIES_VALUE_FIELD_NAME, + Field, DataFrameWithValue, -} from '../types/index'; + DataFrameDTO, + FieldDTO, + FieldConfig, +} from '../types/dataFrame'; +import { DataQueryResponseData } from '../types/datasource'; +import { GraphSeriesXY, GraphSeriesValue } from '../types/graph'; +import { PanelData } from '../types/panel'; import { arrayToDataFrame } from './ArrayDataFrame'; import { dataFrameFromJSON } from './DataFrameJSON'; diff --git a/packages/grafana-data/src/dataframe/utils.test.ts b/packages/grafana-data/src/dataframe/utils.test.ts index fcac25607df..5f7887a1c8c 100644 --- a/packages/grafana-data/src/dataframe/utils.test.ts +++ b/packages/grafana-data/src/dataframe/utils.test.ts @@ -1,4 +1,4 @@ -import { FieldType } from '../types'; +import { FieldType } from '../types/dataFrame'; import { createDataFrame, toDataFrame } from './processDataFrame'; import { anySeriesWithTimeField, addRow } from './utils'; diff --git a/packages/grafana-data/src/datetime/datemath.ts b/packages/grafana-data/src/datetime/datemath.ts index ebfc079f755..0aedd4934b5 100644 --- a/packages/grafana-data/src/datetime/datemath.ts +++ b/packages/grafana-data/src/datetime/datemath.ts @@ -1,6 +1,6 @@ import { includes, isDate } from 'lodash'; -import { TimeZone } from '../types/index'; +import { TimeZone } from '../types/time'; import { DateTime, dateTime, dateTimeForTimeZone, DurationUnit, isDateTime, ISO_8601 } from './moment_wrapper'; diff --git a/packages/grafana-data/src/datetime/formatter.ts b/packages/grafana-data/src/datetime/formatter.ts index 165422855a4..c78a08d562b 100644 --- a/packages/grafana-data/src/datetime/formatter.ts +++ b/packages/grafana-data/src/datetime/formatter.ts @@ -1,7 +1,7 @@ /* eslint-disable id-blacklist, no-restricted-imports, @typescript-eslint/ban-types */ import moment, { Moment } from 'moment-timezone'; -import { TimeZone } from '../types'; +import { TimeZone } from '../types/time'; import { DateTimeOptions, getTimeZone } from './common'; import { systemDateFormats } from './formats'; diff --git a/packages/grafana-data/src/datetime/index.ts b/packages/grafana-data/src/datetime/index.ts deleted file mode 100644 index 9dbdcf96bc3..00000000000 --- a/packages/grafana-data/src/datetime/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Names are too general to export globally -import * as dateMath from './datemath'; -import * as rangeUtil from './rangeutil'; -export * from './moment_wrapper'; -export * from './timezones'; -export * from './formats'; -export * from './formatter'; -export * from './parser'; -export * from './durationutil'; -export { dateMath, rangeUtil }; -export { type DateTimeOptions, setTimeZoneResolver, type TimeZoneResolver, getTimeZone } from './common'; diff --git a/packages/grafana-data/src/datetime/rangeutil.test.ts b/packages/grafana-data/src/datetime/rangeutil.test.ts index 8fa40c02444..03d3594afb4 100644 --- a/packages/grafana-data/src/datetime/rangeutil.test.ts +++ b/packages/grafana-data/src/datetime/rangeutil.test.ts @@ -1,8 +1,14 @@ import { RawTimeRange, TimeRange } from '../types/time'; -import { timeRangeToRelative } from './rangeutil'; - -import { dateTime, rangeUtil } from './index'; +import { dateTime } from './moment_wrapper'; +import { + convertRawToRange, + describeInterval, + isRelativeTimeRange, + relativeToTimeRange, + roundInterval, + timeRangeToRelative, +} from './rangeutil'; describe('Range Utils', () => { // These tests probably wrap the dateTimeParser tests to some extent @@ -15,7 +21,7 @@ describe('Range Utils', () => { }; it('should serialize the default format by default', () => { - const deserialized = rangeUtil.convertRawToRange(defaultRawTimeRange); + const deserialized = convertRawToRange(defaultRawTimeRange); expect(deserialized.from.format()).toBe(DEFAULT_DATE_VALUE_FORMATTED); }); @@ -26,17 +32,12 @@ describe('Range Utils', () => { to: '30-07-1996 16:20:00', }; - const deserializedTimeRange = rangeUtil.convertRawToRange( - nonDefaultRawTimeRange, - undefined, - undefined, - NON_DEFAULT_FORMAT - ); + const deserializedTimeRange = convertRawToRange(nonDefaultRawTimeRange, undefined, undefined, NON_DEFAULT_FORMAT); expect(deserializedTimeRange.from.format()).toBe(DEFAULT_DATE_VALUE_FORMATTED); }); it('should take timezone into account', () => { - const deserializedTimeRange = rangeUtil.convertRawToRange(defaultRawTimeRange, 'UTC'); + const deserializedTimeRange = convertRawToRange(defaultRawTimeRange, 'UTC'); expect(deserializedTimeRange.from.format()).toBe('1996-07-30T16:00:00Z'); }); @@ -46,7 +47,7 @@ describe('Range Utils', () => { to: 'now', }; - const deserialized = rangeUtil.convertRawToRange(timeRange); + const deserialized = convertRawToRange(timeRange); expect(deserialized.raw).toStrictEqual(timeRange); expect(deserialized.to.toString()).not.toBe(deserialized.raw.to); }); @@ -55,13 +56,13 @@ describe('Range Utils', () => { describe('relative time', () => { it('should identify absolute vs relative', () => { expect( - rangeUtil.isRelativeTimeRange({ + isRelativeTimeRange({ from: '1234', to: '4567', }) ).toBe(false); expect( - rangeUtil.isRelativeTimeRange({ + isRelativeTimeRange({ from: 'now-5', to: 'now', }) @@ -71,7 +72,7 @@ describe('Range Utils', () => { describe('describe_interval', () => { it('falls back to seconds if input is a number', () => { - expect(rangeUtil.describeInterval('123')).toEqual({ + expect(describeInterval('123')).toEqual({ sec: 1, type: 's', count: 123, @@ -79,7 +80,7 @@ describe('Range Utils', () => { }); it('parses a valid time unt string correctly', () => { - expect(rangeUtil.describeInterval('123h')).toEqual({ + expect(describeInterval('123h')).toEqual({ sec: 3600, type: 'h', count: 123, @@ -87,12 +88,12 @@ describe('Range Utils', () => { }); it('fails if input is invalid', () => { - expect(() => rangeUtil.describeInterval('123xyz')).toThrow(); - expect(() => rangeUtil.describeInterval('xyz')).toThrow(); + expect(() => describeInterval('123xyz')).toThrow(); + expect(() => describeInterval('xyz')).toThrow(); }); it('should be able to parse negative values as well', () => { - expect(rangeUtil.describeInterval('-50ms')).toEqual({ + expect(describeInterval('-50ms')).toEqual({ sec: 0.001, type: 'ms', count: -50, @@ -102,130 +103,130 @@ describe('Range Utils', () => { describe('roundInterval', () => { it('rounds 9ms to 1ms', () => { - expect(rangeUtil.roundInterval(9)).toEqual(1); + expect(roundInterval(9)).toEqual(1); }); it('rounds 14ms to 10ms', () => { - expect(rangeUtil.roundInterval(9)).toEqual(1); + expect(roundInterval(9)).toEqual(1); }); it('rounds 34ms to 20ms', () => { - expect(rangeUtil.roundInterval(34)).toEqual(20); + expect(roundInterval(34)).toEqual(20); }); it('rounds 74ms to 50ms', () => { - expect(rangeUtil.roundInterval(74)).toEqual(50); + expect(roundInterval(74)).toEqual(50); }); it('rounds 149ms to 100ms', () => { - expect(rangeUtil.roundInterval(149)).toEqual(100); + expect(roundInterval(149)).toEqual(100); }); it('rounds 349ms to 200ms', () => { - expect(rangeUtil.roundInterval(349)).toEqual(200); + expect(roundInterval(349)).toEqual(200); }); it('rounds 749ms to 500ms', () => { - expect(rangeUtil.roundInterval(749)).toEqual(500); + expect(roundInterval(749)).toEqual(500); }); it('rounds 1.5s to 1s', () => { - expect(rangeUtil.roundInterval(1499)).toEqual(1000); + expect(roundInterval(1499)).toEqual(1000); }); it('rounds 3.5s to 2s', () => { - expect(rangeUtil.roundInterval(3499)).toEqual(2000); + expect(roundInterval(3499)).toEqual(2000); }); it('rounds 7.5s to 5s', () => { - expect(rangeUtil.roundInterval(7499)).toEqual(5000); + expect(roundInterval(7499)).toEqual(5000); }); it('rounds 12.5s to 10s', () => { - expect(rangeUtil.roundInterval(12499)).toEqual(10000); + expect(roundInterval(12499)).toEqual(10000); }); it('rounds 17.5s to 15s', () => { - expect(rangeUtil.roundInterval(17499)).toEqual(15000); + expect(roundInterval(17499)).toEqual(15000); }); it('rounds 25s to 20s', () => { - expect(rangeUtil.roundInterval(24999)).toEqual(20000); + expect(roundInterval(24999)).toEqual(20000); }); it('rounds 45s to 30s', () => { - expect(rangeUtil.roundInterval(44999)).toEqual(30000); + expect(roundInterval(44999)).toEqual(30000); }); it('rounds 1m30s to 1m', () => { - expect(rangeUtil.roundInterval(89999)).toEqual(60000); + expect(roundInterval(89999)).toEqual(60000); }); it('rounds 3m30s to 2m', () => { - expect(rangeUtil.roundInterval(209999)).toEqual(120000); + expect(roundInterval(209999)).toEqual(120000); }); it('rounds 7m30s to 5m', () => { - expect(rangeUtil.roundInterval(449999)).toEqual(300000); + expect(roundInterval(449999)).toEqual(300000); }); it('rounds 12m30s to 10m', () => { - expect(rangeUtil.roundInterval(749999)).toEqual(600000); + expect(roundInterval(749999)).toEqual(600000); }); it('rounds 17m30s to 15m', () => { - expect(rangeUtil.roundInterval(1049999)).toEqual(900000); + expect(roundInterval(1049999)).toEqual(900000); }); it('rounds 25m to 20m', () => { - expect(rangeUtil.roundInterval(1499999)).toEqual(1200000); + expect(roundInterval(1499999)).toEqual(1200000); }); it('rounds 45m to 30m', () => { - expect(rangeUtil.roundInterval(2699999)).toEqual(1800000); + expect(roundInterval(2699999)).toEqual(1800000); }); it('rounds 1h30m to 1h', () => { - expect(rangeUtil.roundInterval(5399999)).toEqual(3600000); + expect(roundInterval(5399999)).toEqual(3600000); }); it('rounds 2h30m to 2h', () => { - expect(rangeUtil.roundInterval(8999999)).toEqual(7200000); + expect(roundInterval(8999999)).toEqual(7200000); }); it('rounds 4h30m to 3h', () => { - expect(rangeUtil.roundInterval(16199999)).toEqual(10800000); + expect(roundInterval(16199999)).toEqual(10800000); }); it('rounds 9h to 6h', () => { - expect(rangeUtil.roundInterval(32399999)).toEqual(21600000); + expect(roundInterval(32399999)).toEqual(21600000); }); it('rounds 1d to 12h', () => { - expect(rangeUtil.roundInterval(86399999)).toEqual(43200000); + expect(roundInterval(86399999)).toEqual(43200000); }); it('rounds 1w to 1d', () => { - expect(rangeUtil.roundInterval(604799999)).toEqual(86400000); + expect(roundInterval(604799999)).toEqual(86400000); }); it('rounds 3w to 1w', () => { - expect(rangeUtil.roundInterval(1814399999)).toEqual(604800000); + expect(roundInterval(1814399999)).toEqual(604800000); }); it('rounds 6w to 30d', () => { - expect(rangeUtil.roundInterval(3628799999)).toEqual(2592000000); + expect(roundInterval(3628799999)).toEqual(2592000000); }); it('rounds >6w to 1y', () => { - expect(rangeUtil.roundInterval(3628800000)).toEqual(31536000000); + expect(roundInterval(3628800000)).toEqual(31536000000); }); }); describe('relativeToTimeRange', () => { it('should convert seconds to timeRange', () => { const relativeTimeRange = { from: 600, to: 300 }; - const timeRange = rangeUtil.relativeToTimeRange(relativeTimeRange, dateTime('2021-04-20T15:55:00Z')); + const timeRange = relativeToTimeRange(relativeTimeRange, dateTime('2021-04-20T15:55:00Z')); expect(timeRange.from.valueOf()).toEqual(dateTime('2021-04-20T15:45:00Z').valueOf()); expect(timeRange.to.valueOf()).toEqual(dateTime('2021-04-20T15:50:00Z').valueOf()); @@ -233,7 +234,7 @@ describe('Range Utils', () => { it('should convert from now', () => { const relativeTimeRange = { from: 600, to: 0 }; - const timeRange = rangeUtil.relativeToTimeRange(relativeTimeRange, dateTime('2021-04-20T15:55:00Z')); + const timeRange = relativeToTimeRange(relativeTimeRange, dateTime('2021-04-20T15:55:00Z')); expect(timeRange.from.valueOf()).toEqual(dateTime('2021-04-20T15:45:00Z').valueOf()); expect(timeRange.to.valueOf()).toEqual(dateTime('2021-04-20T15:55:00Z').valueOf()); diff --git a/packages/grafana-data/src/datetime/timezones.ts b/packages/grafana-data/src/datetime/timezones.ts index 6045ef51d7c..efc0842d28e 100644 --- a/packages/grafana-data/src/datetime/timezones.ts +++ b/packages/grafana-data/src/datetime/timezones.ts @@ -1,7 +1,7 @@ import { memoize } from 'lodash'; import moment from 'moment-timezone'; -import { TimeZone } from '../types'; +import { TimeZone } from '../types/time'; import { getTimeZone } from './common'; diff --git a/packages/grafana-data/src/events/common.ts b/packages/grafana-data/src/events/common.ts index aa94afd843a..415b68508a0 100644 --- a/packages/grafana-data/src/events/common.ts +++ b/packages/grafana-data/src/events/common.ts @@ -1,4 +1,5 @@ -import { AnnotationEvent, DataFrame } from '../types'; +import { AnnotationEvent } from '../types/annotations'; +import { DataFrame } from '../types/dataFrame'; import { BusEventBase, BusEventWithPayload } from './types'; diff --git a/packages/grafana-data/src/field/displayProcessor.test.ts b/packages/grafana-data/src/field/displayProcessor.test.ts index 0a5762430b5..94543fa2554 100644 --- a/packages/grafana-data/src/field/displayProcessor.test.ts +++ b/packages/grafana-data/src/field/displayProcessor.test.ts @@ -1,7 +1,8 @@ -import { systemDateFormats } from '../datetime'; +import { systemDateFormats } from '../datetime/formats'; import { createTheme } from '../themes'; -import { FieldConfig, FieldType, ThresholdsMode } from '../types'; +import { FieldConfig, FieldType } from '../types/dataFrame'; import { DisplayProcessor, DisplayValue } from '../types/displayValue'; +import { ThresholdsMode } from '../types/thresholds'; import { MappingType, ValueMapping } from '../types/valueMapping'; import { getDisplayProcessor, getRawDisplayProcessor } from './displayProcessor'; diff --git a/packages/grafana-data/src/field/displayProcessor.ts b/packages/grafana-data/src/field/displayProcessor.ts index 9cdef2760a6..a9f0a7c2927 100644 --- a/packages/grafana-data/src/field/displayProcessor.ts +++ b/packages/grafana-data/src/field/displayProcessor.ts @@ -3,11 +3,13 @@ import { toString, toNumber as _toNumber, isEmpty, isBoolean, isArray, join } fr // Types import { getFieldTypeFromValue } from '../dataframe/processDataFrame'; -import { toUtc, dateTimeParse } from '../datetime'; +import { toUtc } from '../datetime/moment_wrapper'; +import { dateTimeParse } from '../datetime/parser'; import { GrafanaTheme2 } from '../themes/types'; -import { KeyValue, TimeZone } from '../types'; +import { KeyValue } from '../types/data'; import { Field, FieldType } from '../types/dataFrame'; import { DecimalCount, DisplayProcessor, DisplayValue } from '../types/displayValue'; +import { TimeZone } from '../types/time'; import { anyToNumber } from '../utils/anyToNumber'; import { getValueMappingResult } from '../utils/valueMappings'; import { FormattedValue, getValueFormat, isBooleanUnit } from '../valueFormats/valueFormats'; diff --git a/packages/grafana-data/src/field/fieldColor.test.ts b/packages/grafana-data/src/field/fieldColor.test.ts index 43813f1f10a..bcf95f1fd79 100644 --- a/packages/grafana-data/src/field/fieldColor.test.ts +++ b/packages/grafana-data/src/field/fieldColor.test.ts @@ -1,5 +1,6 @@ import { createTheme } from '../themes'; -import { Field, FieldColorModeId, FieldType } from '../types'; +import { Field, FieldType } from '../types/dataFrame'; +import { FieldColorModeId } from '../types/fieldColor'; import { fieldColorModeRegistry, FieldValueColorCalculator, getFieldSeriesColor } from './fieldColor'; diff --git a/packages/grafana-data/src/field/fieldColor.ts b/packages/grafana-data/src/field/fieldColor.ts index 2336dbb868f..c341b84d30e 100644 --- a/packages/grafana-data/src/field/fieldColor.ts +++ b/packages/grafana-data/src/field/fieldColor.ts @@ -5,7 +5,9 @@ import tinycolor from 'tinycolor2'; import { colorManipulator } from '../themes'; import { GrafanaTheme2 } from '../themes/types'; import { reduceField } from '../transformations/fieldReducer'; -import { FALLBACK_COLOR, Field, FieldColorModeId, Threshold } from '../types'; +import { Field } from '../types/dataFrame'; +import { FALLBACK_COLOR, FieldColorModeId } from '../types/fieldColor'; +import { Threshold } from '../types/thresholds'; import { Registry, RegistryItem } from '../utils/Registry'; import { getScaleCalculator, ColorScaleValue } from './scale'; diff --git a/packages/grafana-data/src/field/fieldComparers.ts b/packages/grafana-data/src/field/fieldComparers.ts index 3f525b06405..bdcf8eb32a5 100644 --- a/packages/grafana-data/src/field/fieldComparers.ts +++ b/packages/grafana-data/src/field/fieldComparers.ts @@ -1,6 +1,6 @@ import { isNumber } from 'lodash'; -import { dateTime, isDateTimeInput } from '../datetime'; +import { isDateTimeInput, dateTime } from '../datetime/moment_wrapper'; import { Field, FieldType } from '../types/dataFrame'; type IndexComparer = (a: number, b: number) => number; diff --git a/packages/grafana-data/src/field/fieldDisplay.test.ts b/packages/grafana-data/src/field/fieldDisplay.test.ts index 4cf946c7493..35bf73b9299 100644 --- a/packages/grafana-data/src/field/fieldDisplay.test.ts +++ b/packages/grafana-data/src/field/fieldDisplay.test.ts @@ -3,7 +3,8 @@ import { merge } from 'lodash'; import { toDataFrame } from '../dataframe/processDataFrame'; import { createTheme } from '../themes'; import { ReducerID } from '../transformations/fieldReducer'; -import { FieldConfigPropertyItem, MappingType, SpecialValueMatch, ValueMapping } from '../types'; +import { FieldConfigPropertyItem } from '../types/fieldOverrides'; +import { MappingType, SpecialValueMatch, ValueMapping } from '../types/valueMapping'; import { getDisplayProcessor } from './displayProcessor'; import { fixCellTemplateExpressions, getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay'; diff --git a/packages/grafana-data/src/field/fieldDisplay.ts b/packages/grafana-data/src/field/fieldDisplay.ts index 11bc62e0116..29a3ebc095f 100644 --- a/packages/grafana-data/src/field/fieldDisplay.ts +++ b/packages/grafana-data/src/field/fieldDisplay.ts @@ -6,20 +6,13 @@ import { GrafanaTheme2 } from '../themes'; import { reduceField, ReducerID } from '../transformations/fieldReducer'; import { getFieldMatcher } from '../transformations/matchers'; import { FieldMatcherID } from '../transformations/matchers/ids'; -import { - DataFrame, - DisplayValue, - DisplayValueAlignmentFactors, - Field, - FieldConfig, - FieldConfigSource, - FieldType, - InterpolateFunction, - LinkModel, - TimeRange, - TimeZone, -} from '../types'; import { ScopedVars } from '../types/ScopedVars'; +import { DataFrame, Field, FieldConfig, FieldType } from '../types/dataFrame'; +import { LinkModel } from '../types/dataLink'; +import { DisplayValue, DisplayValueAlignmentFactors } from '../types/displayValue'; +import { FieldConfigSource } from '../types/fieldOverrides'; +import { InterpolateFunction } from '../types/panel'; +import { TimeRange, TimeZone } from '../types/time'; import { getDisplayProcessor } from './displayProcessor'; import { getFieldDisplayName } from './fieldState'; diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts index 6268bfc591d..c6bba4debf3 100644 --- a/packages/grafana-data/src/field/fieldOverrides.test.ts +++ b/packages/grafana-data/src/field/fieldOverrides.test.ts @@ -1,21 +1,15 @@ import { ArrayDataFrame } from '../dataframe/ArrayDataFrame'; import { createDataFrame, toDataFrame } from '../dataframe/processDataFrame'; -import { rangeUtil } from '../datetime'; +import { relativeToTimeRange } from '../datetime/rangeutil'; import { createTheme } from '../themes'; import { FieldMatcherID } from '../transformations/matchers/ids'; -import { - DataFrame, - Field, - FieldColorModeId, - FieldConfig, - FieldConfigPropertyItem, - FieldConfigSource, - FieldType, - GrafanaConfig, - InterpolateFunction, - ScopedVars, - ThresholdsMode, -} from '../types'; +import { ScopedVars } from '../types/ScopedVars'; +import { GrafanaConfig } from '../types/config'; +import { FieldType, DataFrame, Field, FieldConfig } from '../types/dataFrame'; +import { FieldColorModeId } from '../types/fieldColor'; +import { FieldConfigPropertyItem, FieldConfigSource } from '../types/fieldOverrides'; +import { InterpolateFunction } from '../types/panel'; +import { ThresholdsMode } from '../types/thresholds'; import { Registry } from '../utils/Registry'; import { locationUtil } from '../utils/location'; import { mockStandardProperties } from '../utils/tests/mockStandardProperties'; @@ -912,7 +906,7 @@ describe('getLinksSupplier', () => { }); const datasourceUid = '1234'; - const range = rangeUtil.relativeToTimeRange({ from: 600, to: 0 }); + const range = relativeToTimeRange({ from: 600, to: 0 }); const f0 = createDataFrame({ name: 'A', fields: [ diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index 8b170606507..47f5552a0ce 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -11,31 +11,21 @@ import { GrafanaTheme2 } from '../themes'; import { asHexString } from '../themes/colorManipulator'; import { ReducerID, reduceField } from '../transformations/fieldReducer'; import { fieldMatchers } from '../transformations/matchers'; +import { ScopedVars, DataContextScopedVar } from '../types/ScopedVars'; +import { DataFrame, NumericRange, FieldType, Field, ValueLinkConfig, FieldConfig } from '../types/dataFrame'; +import { LinkModel, DataLink } from '../types/dataLink'; +import { DisplayProcessor, DisplayValue, DecimalCount } from '../types/displayValue'; +import { FieldColorModeId } from '../types/fieldColor'; import { - ApplyFieldOverrideOptions, - DataContextScopedVar, - DataFrame, - DataLink, - DecimalCount, - DisplayProcessor, - DisplayValue, DynamicConfigValue, - Field, - FieldColorModeId, - FieldConfig, - FieldConfigPropertyItem, - FieldConfigSource, + ApplyFieldOverrideOptions, FieldOverrideContext, - FieldType, + FieldConfigPropertyItem, DataLinkPostProcessor, - InterpolateFunction, - LinkModel, - NumericRange, - PanelData, - ScopedVars, - TimeZone, - ValueLinkConfig, -} from '../types'; + FieldConfigSource, +} from '../types/fieldOverrides'; +import { InterpolateFunction, PanelData } from '../types/panel'; +import { TimeZone } from '../types/time'; import { FieldMatcher } from '../types/transformations'; import { mapInternalLinkToExplore } from '../utils/dataLinks'; import { locationUtil } from '../utils/location'; diff --git a/packages/grafana-data/src/field/fieldState.test.ts b/packages/grafana-data/src/field/fieldState.test.ts index 3e1ef1ac9db..91a5fca2d52 100644 --- a/packages/grafana-data/src/field/fieldState.test.ts +++ b/packages/grafana-data/src/field/fieldState.test.ts @@ -1,5 +1,5 @@ import { toDataFrame } from '../dataframe/processDataFrame'; -import { DataFrame, TIME_SERIES_VALUE_FIELD_NAME, FieldType, TIME_SERIES_TIME_FIELD_NAME } from '../types'; +import { DataFrame, TIME_SERIES_TIME_FIELD_NAME, FieldType, TIME_SERIES_VALUE_FIELD_NAME } from '../types/dataFrame'; import { getFieldDisplayName, getFrameDisplayName } from './fieldState'; diff --git a/packages/grafana-data/src/field/fieldState.ts b/packages/grafana-data/src/field/fieldState.ts index be225d2f0b0..2ec8942fcfe 100644 --- a/packages/grafana-data/src/field/fieldState.ts +++ b/packages/grafana-data/src/field/fieldState.ts @@ -1,12 +1,12 @@ import { getFieldMatcher } from '../transformations/matchers'; import { DataFrame, - Field, - TIME_SERIES_VALUE_FIELD_NAME, FieldType, + Field, TIME_SERIES_TIME_FIELD_NAME, - FieldConfigSource, -} from '../types'; + TIME_SERIES_VALUE_FIELD_NAME, +} from '../types/dataFrame'; +import { FieldConfigSource } from '../types/fieldOverrides'; import { formatLabels } from '../utils/labels'; /** diff --git a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.ts b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.ts index 49c56d59891..6b81610d4f2 100644 --- a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.ts +++ b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.ts @@ -1,6 +1,8 @@ import { toNumber } from 'lodash'; -import { DataFrame, DisplayValue, TimeZone } from '../types'; +import { DataFrame } from '../types/dataFrame'; +import { DisplayValue } from '../types/displayValue'; +import { TimeZone } from '../types/time'; import { formattedValueToString } from '../valueFormats/valueFormats'; import { getDisplayProcessor } from './displayProcessor'; diff --git a/packages/grafana-data/src/field/overrides/processors.ts b/packages/grafana-data/src/field/overrides/processors.ts index fbd40d9d890..ee396ebba01 100644 --- a/packages/grafana-data/src/field/overrides/processors.ts +++ b/packages/grafana-data/src/field/overrides/processors.ts @@ -1,12 +1,10 @@ -import { - DataLink, - Field, - FieldOverrideContext, - SelectableValue, - SliderMarks, - ThresholdsConfig, - ValueMapping, -} from '../../types'; +import { Field } from '../../types/dataFrame'; +import { DataLink } from '../../types/dataLink'; +import { FieldOverrideContext } from '../../types/fieldOverrides'; +import { SelectableValue } from '../../types/select'; +import { SliderMarks } from '../../types/slider'; +import { ThresholdsConfig } from '../../types/thresholds'; +import { ValueMapping } from '../../types/valueMapping'; export const identityOverrideProcessor = (value: T) => { return value; diff --git a/packages/grafana-data/src/field/scale.test.ts b/packages/grafana-data/src/field/scale.test.ts index 9ba0cb6227b..ba941992765 100644 --- a/packages/grafana-data/src/field/scale.test.ts +++ b/packages/grafana-data/src/field/scale.test.ts @@ -1,5 +1,7 @@ import { createTheme } from '../themes'; -import { ThresholdsMode, Field, FieldType, FieldColorModeId } from '../types'; +import { Field, FieldType } from '../types/dataFrame'; +import { FieldColorModeId } from '../types/fieldColor'; +import { ThresholdsMode } from '../types/thresholds'; import { getScaleCalculator } from './scale'; import { sortThresholds } from './thresholds'; diff --git a/packages/grafana-data/src/field/scale.ts b/packages/grafana-data/src/field/scale.ts index c8624e6086f..f578942924f 100644 --- a/packages/grafana-data/src/field/scale.ts +++ b/packages/grafana-data/src/field/scale.ts @@ -2,7 +2,8 @@ import { isNumber } from 'lodash'; import { GrafanaTheme2 } from '../themes/types'; import { reduceField, ReducerID } from '../transformations/fieldReducer'; -import { Field, FieldConfig, FieldType, NumericRange, Threshold } from '../types'; +import { Field, FieldConfig, FieldType, NumericRange } from '../types/dataFrame'; +import { Threshold } from '../types/thresholds'; import { getFieldColorModeForField } from './fieldColor'; import { getActiveThresholdForValue } from './thresholds'; diff --git a/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts b/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts index 6f8beee78ae..2bc3ac995be 100644 --- a/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts +++ b/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts @@ -1,7 +1,9 @@ import { ComponentType } from 'react'; import { EventBus } from '../events'; -import { DataFrame, InterpolateFunction, VariableSuggestionsScope, VariableSuggestion } from '../types'; +import { DataFrame } from '../types/dataFrame'; +import { VariableSuggestionsScope, VariableSuggestion } from '../types/dataLink'; +import { InterpolateFunction } from '../types/panel'; import { Registry, RegistryItem } from '../utils/Registry'; import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry'; diff --git a/packages/grafana-data/src/field/thresholds.test.ts b/packages/grafana-data/src/field/thresholds.test.ts index 77df5edf76e..0a01d7a9270 100644 --- a/packages/grafana-data/src/field/thresholds.test.ts +++ b/packages/grafana-data/src/field/thresholds.test.ts @@ -1,4 +1,5 @@ -import { ThresholdsConfig, ThresholdsMode, FieldConfig, Threshold, Field, FieldType } from '../types'; +import { Field, FieldConfig, FieldType } from '../types/dataFrame'; +import { Threshold, ThresholdsConfig, ThresholdsMode } from '../types/thresholds'; import { validateFieldConfig } from './fieldOverrides'; import { sortThresholds, getActiveThreshold, getActiveThresholdForValue } from './thresholds'; diff --git a/packages/grafana-data/src/field/thresholds.ts b/packages/grafana-data/src/field/thresholds.ts index a756af17114..71c59156822 100644 --- a/packages/grafana-data/src/field/thresholds.ts +++ b/packages/grafana-data/src/field/thresholds.ts @@ -1,4 +1,6 @@ -import { Threshold, FALLBACK_COLOR, Field, ThresholdsMode } from '../types'; +import { Field } from '../types/dataFrame'; +import { FALLBACK_COLOR } from '../types/fieldColor'; +import { Threshold, ThresholdsMode } from '../types/thresholds'; export const fallBackThreshold: Threshold = { value: 0, color: FALLBACK_COLOR }; diff --git a/packages/grafana-data/src/geo/layer.ts b/packages/grafana-data/src/geo/layer.ts index e034fdb1cf5..7b21393ae19 100644 --- a/packages/grafana-data/src/geo/layer.ts +++ b/packages/grafana-data/src/geo/layer.ts @@ -7,7 +7,7 @@ import { MapLayerOptions, FrameGeometrySourceMode } from '@grafana/schema'; import { EventBus } from '../events'; import { StandardEditorContext } from '../field/standardFieldConfigEditorRegistry'; import { GrafanaTheme2 } from '../themes'; -import { PanelData } from '../types'; +import { PanelData } from '../types/panel'; import { PanelOptionsEditorBuilder } from '../utils/OptionsUIBuilders'; import { RegistryItemWithOptions } from '../utils/Registry'; diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index 1ed254b828c..97860df3a19 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -4,8 +4,6 @@ * @packageDocumentation */ -export * from './types'; -export * from './datetime'; export * from './text'; export * from './events'; export * from './themes'; @@ -85,7 +83,6 @@ export { export { compareDataFrameStructures, compareArrayValues, shallowCompare } from './dataframe/frameComparisons'; // Field - export { getFieldColorModeForField, getFieldColorMode, @@ -163,7 +160,6 @@ export { } from './field/overrides/processors'; // Utils - export { PanelOptionsEditorBuilder, FieldConfigEditorBuilder } from './utils/OptionsUIBuilders'; export { getFlotPairs, getFlotPairsConstant } from './utils/flotPairs'; export { locationUtil } from './utils/location'; @@ -222,7 +218,6 @@ export { store } from './utils/store'; export { LocalStorageValueProvider } from './utils/LocalStorageValueProvider'; // Tranformations - export { standardTransformers } from './transformations/transformers'; export { fieldMatchers, @@ -273,7 +268,6 @@ export { applyNullInsertThreshold } from './transformations/transformers/nulls/n export { nullToValue } from './transformations/transformers/nulls/nullToValue'; // ValueFormats - export { type FormattedValue, type ValueFormatter, @@ -295,6 +289,71 @@ export { getValueFormats, } from './valueFormats/valueFormats'; +// datetime +export * as dateMath from './datetime/datemath'; +export * as rangeUtil from './datetime/rangeutil'; +export { type DateTimeOptions, setTimeZoneResolver, type TimeZoneResolver, getTimeZone } from './datetime/common'; +export { + ISO_8601, + type DateTimeBuiltinFormat, + type DateTimeInput, + type FormatInput, + type DurationInput, + type DurationUnit, + type DateTimeLocale, + type DateTimeDuration, + type DateTime, + setLocale, + getLocale, + getLocaleData, + isDateTimeInput, + isDateTime, + toUtc, + toDuration, + dateTime, + dateTimeAsMoment, + dateTimeForTimeZone, + getWeekdayIndex, + getWeekdayIndexByEnglishName, + setWeekStart, +} from './datetime/moment_wrapper'; +export { + InternalTimeZones, + timeZoneFormatUserFriendly, + getZone, + type TimeZoneCountry, + type TimeZoneInfo, + type GroupedTimeZones, + getTimeZoneInfo, + getTimeZones, + getTimeZoneGroups, +} from './datetime/timezones'; +export { + type SystemDateFormatSettings, + SystemDateFormatsState, + localTimeFormat, + systemDateFormats, +} from './datetime/formats'; +export { + type DateTimeOptionsWithFormat, + dateTimeFormat, + dateTimeFormatISO, + dateTimeFormatTimeAgo, + dateTimeFormatWithAbbrevation, + timeZoneAbbrevation, +} from './datetime/formatter'; +export { type DateTimeOptionsWhenParsing, dateTimeParse } from './datetime/parser'; +export { + intervalToAbbreviatedDurationString, + parseDuration, + addDurationToDate, + durationToMilliseconds, + isValidDate, + isValidDuration, + isValidGoDuration, + isValidGrafanaDuration, +} from './datetime/durationutil'; + export { type ValueMatcherOptions, type BasicValueMatcherOptions, @@ -323,6 +382,404 @@ export { usePluginContext } from './context/plugins/usePluginContext'; export { isDataSourcePluginContext } from './context/plugins/guards'; export { getLinksSupplier } from './field/fieldOverrides'; +// Types +export { isUnsignedPluginSignature } from './types/pluginSignature'; +export type { + CurrentUserDTO, + AnalyticsSettings, + BootData, + OAuth, + OAuthSettings, + AuthSettings, + GrafanaConfig, + BuildInfo, + LicenseInfo, +} from './types/config'; +export { availableIconsIndex, type IconName, isIconName, toIconName } from './types/icon'; +export type { WithAccessControlMetadata } from './types/accesscontrol'; +export { AlertState, type AlertStateInfo } from './types/alerts'; +export type { CartesianCoords2D, Dimensions2D } from './types/geometry'; +export { + VariableSupportType, + VariableSupportBase, + StandardVariableSupport, + CustomVariableSupport, + DataSourceVariableSupport, + type StandardVariableQuery, +} from './types/variables'; +export { + type AlertPayload, + type AlertErrorPayload, + AppEvents, + PanelEvents, + type LegacyGraphHoverEventPayload, + LegacyGraphHoverEvent, + LegacyGraphHoverClearEvent, +} from './types/legacyEvents'; +export type { + URLRangeValue, + URLRange, + ExploreUrlState, + ExplorePanelsState, + ExploreCorrelationHelperData, + ExploreTracePanelState, + ExploreLogsPanelState, + SplitOpenOptions, + SplitOpen, +} from './types/explore'; +export type { TraceKeyValuePair, TraceLog, TraceSpanReference, TraceSpanRow } from './types/trace'; +export type { FlotDataPoint } from './types/flot'; +export { type UserOrgDTO, OrgRole } from './types/orgs'; +export { GrafanaThemeType, type GrafanaThemeCommons, type GrafanaTheme } from './types/theme'; +export { FieldColorModeId, type FieldColor, type FieldColorSeriesByMode, FALLBACK_COLOR } from './types/fieldColor'; +export { + VariableRefresh, + VariableSort, + VariableHide, + type VariableType, + type VariableModel, + type TypedVariableModel, + type AdHocVariableFilter, + type AdHocVariableModel, + type GroupByVariableModel, + type VariableOption, + type IntervalVariableModel, + type CustomVariableModel, + type DataSourceVariableModel, + type QueryVariableModel, + type TextBoxVariableModel, + type ConstantVariableModel, + type VariableWithMultiSupport, + type VariableWithOptions, + type DashboardProps, + type DashboardVariableModel, + type OrgProps, + type OrgVariableModel, + type UserProps, + type UserVariableModel, + type SystemVariable, + type BaseVariableModel, +} from './types/templateVars'; +export { type Threshold, ThresholdsMode, type ThresholdsConfig } from './types/thresholds'; +export { + LiveChannelScope, + LiveChannelType, + LiveChannelConnectionState, + LiveChannelEventType, + type LiveChannelStatusEvent, + type LiveChannelJoinEvent, + type LiveChannelLeaveEvent, + type LiveChannelMessageEvent, + type LiveChannelEvent, + type LiveChannelPresenceStatus, + type LiveChannelId, + type LiveChannelAddress, + isLiveChannelStatusEvent, + isLiveChannelJoinEvent, + isLiveChannelLeaveEvent, + isLiveChannelMessageEvent, + parseLiveChannelAddress, + isValidLiveChannelAddress, + toLiveChannelId, +} from './types/live'; +export type { SliderMarks } from './types/slider'; +export type { FeatureToggles } from './types/featureToggles.gen'; +export { + PluginExtensionTypes, + PluginExtensionPoints, + type PluginExtension, + type PluginExtensionLink, + type PluginExtensionComponent, + type PluginExtensionConfig, + type PluginExtensionLinkConfig, + type PluginExtensionComponentConfig, + type PluginExtensionEventHelpers, + type PluginExtensionPanelContext, + type PluginExtensionDataSourceConfigContext, + type PluginExtensionCommandPaletteContext, + type PluginExtensionOpenModalOptions, +} from './types/pluginExtensions'; +export { + type ScopeDashboardBindingSpec, + type ScopeDashboardBinding, + type ScopeFilterOperator, + type ScopeSpecFilter, + type ScopeSpec, + type Scope, + type ScopeNodeNodeType, + type ScopeNodeLinkType, + type ScopeNodeSpec, + type ScopeNode, + scopeFilterOperatorMap, +} from './types/scopes'; +export { + PluginState, + PluginType, + PluginSignatureStatus, + PluginSignatureType, + PluginErrorCode, + PluginIncludeType, + GrafanaPlugin, + type PluginError, + type AngularMeta, + type PluginMeta, + type PluginDependencies, + type PluginInclude, + type PluginBuildInfo, + type ScreenshotInfo, + type PluginMetaInfo, + type PluginConfigPageProps, + type PluginConfigPage, +} from './types/plugin'; +export { + type InterpolateFunction, + type PanelPluginMeta, + type PanelData, + type PanelProps, + type PanelEditorProps, + type PanelMigrationHandler, + type PanelTypeChangedHandler, + type PanelOptionEditorsRegistry, + type PanelOptionsEditorProps, + type PanelOptionsEditorItem, + type PanelOptionsEditorConfig, + type PanelMenuItem, + type AngularPanelMenuItem, + type PanelPluginDataSupport, + type VisualizationSuggestion, + type PanelDataSummary, + type VisualizationSuggestionsSupplier, + VizOrientation, + VisualizationSuggestionScore, + VisualizationSuggestionsBuilder, + VisualizationSuggestionsListAppender, +} from './types/panel'; +export { + type DataSourcePluginOptionsEditorProps, + type DataSourceQueryType, + type DataSourceOptionsType, + type DataSourcePluginMeta, + type DataSourcePluginComponents, + type DataSourceConstructor, + type DataSourceGetTagKeysOptions, + type DataSourceGetTagValuesOptions, + type MetadataInspectorProps, + type LegacyMetricFindQueryOptions, + type QueryEditorProps, + type QueryEditorHelpProps, + type LegacyResponseData, + type DataQueryResponseData, + type DataQueryResponse, + type TestDataSourceResponse, + type DataQueryError, + type DataQueryRequest, + type DataQueryTimings, + type QueryFix, + type QueryFixType, + type QueryFixAction, + type QueryHint, + type MetricFindValue, + type DataSourceJsonData, + type DataSourceSettings, + type DataSourceInstanceSettings, + type DataSourceSelectItem, + type AnnotationQueryRequest, + type HistoryItem, + type GetTagResponse, + DataSourcePlugin, + DataQueryErrorType, + ExploreMode, + LanguageProvider, + DataSourceApi, +} from './types/datasource'; +export { CoreApp, type AppRootProps, type AppPluginMeta, AppPlugin, FeatureState } from './types/app'; +export { patchArrayVectorProrotypeMethods } from './types/vector'; +export { + type DynamicConfigValue, + type ConfigOverrideRule, + type SystemConfigOverrideRule, + isSystemOverrideWithRef, + isSystemOverride, + type FieldConfigSource, + type FieldOverrideContext, + type FieldConfigEditorProps, + type FieldOverrideEditorProps, + type FieldConfigEditorConfig, + type FieldConfigPropertyItem, + type DataLinkPostProcessorOptions, + type DataLinkPostProcessor, + type ApplyFieldOverrideOptions, + FieldConfigProperty, +} from './types/fieldOverrides'; +export { + type MatcherConfig, + type DataTransformContext, + type TransformationApplicabilityScore, + TransformationApplicabilityLevels, + type DataTransformerInfo, + type CustomTransformOperator, + type SynchronousDataTransformerInfo, + type DataTransformerConfig, + type FrameMatcher, + type FieldMatcher, + type ValueMatcher, + type FieldMatcherInfo, + type FrameMatcherInfo, + type ValueMatcherInfo, + SpecialValue, +} from './types/transformations'; +export type { ScopedVar, ScopedVars, DataContextScopedVar } from './types/ScopedVars'; +export type { YAxis, GraphSeriesValue, GraphSeriesXY, CreatePlotOverlay } from './types/graph'; +export type { + DisplayProcessor, + DisplayValue, + DisplayValueAlignmentFactors, + DecimalCount, + DecimalInfo, +} from './types/displayValue'; +export { + MappingType, + type ValueMappingResult, + type ValueMap, + type RangeMapOptions, + type RangeMap, + type RegexMapOptions, + type RegexMap, + type SpecialValueOptions, + SpecialValueMatch, + type SpecialValueMap, + type ValueMapping, +} from './types/valueMapping'; +export { + type RawTimeRange, + type TimeRange, + type RelativeTimeRange, + type AbsoluteTimeRange, + type IntervalValues, + type TimeOption, + type TimeZone, + type TimeZoneBrowser, + type TimeZoneUtc, + DefaultTimeZone, + type TimeOptions, + type TimeFragment, + TIME_FORMAT, + getDefaultTimeRange, + getDefaultRelativeTimeRange, + makeTimeRange, +} from './types/time'; +export type { SelectableValue } from './types/select'; +export { type NavLinkDTO, type NavModelItem, type NavModel, type NavIndex, PageLayoutType } from './types/navModel'; +export { LogsDedupStrategy, LogsSortOrder } from '@grafana/schema'; + +export { + LogLevel, + NumericLogLevel, + LogsMetaKind, + type LogsMetaItem, + type LogRowModel, + type LogsModel, + type LogSearchMatch, + type LogLabelStatsModel, + LogsDedupDescription, + type LogRowContextOptions, + LogRowContextQueryDirection, + type DataSourceWithLogsContextSupport, + hasLogsContextSupport, + SupplementaryQueryType, + type SupplementaryQueryOptions, + type LogsVolumeOption, + type LogsSampleOptions, + LogsVolumeType, + type LogsVolumeCustomMetaData, + type DataSourceWithSupplementaryQueriesSupport, + hasSupplementaryQuerySupport, + hasLogsContextUiSupport, + type QueryFilterOptions, + type ToggleFilterAction, + type DataSourceWithToggleableQueryFiltersSupport, + type DataSourceWithQueryModificationSupport, + hasToggleableQueryFiltersSupport, + hasQueryModificationSupport, +} from './types/logs'; +export { + type AnnotationQuery, + type AnnotationEvent, + type AnnotationEventUIModel, + type AnnotationEventFieldMapping, + type AnnotationEventMappings, + type AnnotationSupport, + AnnotationEventFieldSource, +} from './types/annotations'; +export { + DataTopic, + type DataQuery, + type DataSourceRef, + type AbstractQuery, + AbstractLabelOperator, + type AbstractLabelMatcher, + type DataSourceWithQueryImportSupport, + type DataSourceWithQueryExportSupport, + hasQueryImportSupport, + hasQueryExportSupport, +} from './types/query'; +export { DashboardCursorSync, type PanelModel } from './types/dashboard'; +export { + type DataLink, + type DataLinkClickEvent, + type DataLinkTransformationConfig, + DataLinkConfigOrigin, + SupportedTransformationType, + type InternalDataLink, + type LinkTarget, + type LinkModel, + type LinkModelSupplier, + VariableOrigin, + type VariableSuggestion, + VariableSuggestionsScope, +} from './types/dataLink'; +export { DataFrameType } from './types/dataFrameTypes'; +export { + FieldType, + type FieldConfig, + type FieldTypeConfig, + type EnumFieldConfig, + type ValueLinkConfig, + type Field, + type FieldState, + type NumericRange, + type DataFrame, + type DataFrameWithValue, + type FieldDTO, + type DataFrameDTO, + type FieldCalcs, + TIME_SERIES_VALUE_FIELD_NAME, + TIME_SERIES_TIME_FIELD_NAME, + TIME_SERIES_METRIC_FIELD_NAME, + type DataFrameFieldIndex, +} from './types/dataFrame'; +export { + type KeyValue, + LoadingState, + preferredVisualizationTypes, + type PreferredVisualisationType, + type QueryResultMeta, + type QueryResultMetaStat, + type QueryResultMetaNotice, + type QueryResultBase, + type Labels, + type Column, + type TableData, + type TimeSeriesValue, + type TimeSeriesPoints, + type TimeSeries, + NullValueMode, + type DataConfigSource, + isTruthy, + isObject, +} from './types/data'; +export { GAUGE_DEFAULT_MINIMUM, GAUGE_DEFAULT_MAXIMUM, DEFAULT_SAML_NAME } from './types/constants'; + // deprecated export { CircularVector } from './vector/CircularVector'; export { vectorator } from './vector/FunctionalVector'; diff --git a/packages/grafana-data/src/panel/PanelPlugin.test.tsx b/packages/grafana-data/src/panel/PanelPlugin.test.tsx index eab21a06f42..50801dd8a84 100644 --- a/packages/grafana-data/src/panel/PanelPlugin.test.tsx +++ b/packages/grafana-data/src/panel/PanelPlugin.test.tsx @@ -4,7 +4,7 @@ import { standardEditorsRegistry, standardFieldConfigEditorRegistry, } from '../field/standardFieldConfigEditorRegistry'; -import { FieldConfigProperty, FieldConfigPropertyItem } from '../types'; +import { FieldConfigProperty, FieldConfigPropertyItem } from '../types/fieldOverrides'; import { PanelOptionsEditorBuilder } from '../utils/OptionsUIBuilders'; import { PanelPlugin } from './PanelPlugin'; diff --git a/packages/grafana-data/src/panel/PanelPlugin.ts b/packages/grafana-data/src/panel/PanelPlugin.ts index 8d21b9b77b5..c4b2e0ed037 100644 --- a/packages/grafana-data/src/panel/PanelPlugin.ts +++ b/packages/grafana-data/src/panel/PanelPlugin.ts @@ -3,18 +3,17 @@ import { ComponentClass, ComponentType } from 'react'; import { FieldConfigOptionsRegistry } from '../field/FieldConfigOptionsRegistry'; import { StandardEditorContext } from '../field/standardFieldConfigEditorRegistry'; +import { FieldConfigProperty, FieldConfigSource } from '../types/fieldOverrides'; import { - FieldConfigProperty, - FieldConfigSource, - GrafanaPlugin, + PanelPluginMeta, + VisualizationSuggestionsSupplier, + PanelProps, PanelEditorProps, PanelMigrationHandler, - PanelPluginDataSupport, - PanelPluginMeta, - PanelProps, PanelTypeChangedHandler, - VisualizationSuggestionsSupplier, -} from '../types'; + PanelPluginDataSupport, +} from '../types/panel'; +import { GrafanaPlugin } from '../types/plugin'; import { FieldConfigEditorBuilder, PanelOptionsEditorBuilder } from '../utils/OptionsUIBuilders'; import { deprecationWarning } from '../utils/deprecationWarning'; diff --git a/packages/grafana-data/src/rbac/rbac.ts b/packages/grafana-data/src/rbac/rbac.ts index 90e87522caa..02fea0b13d1 100644 --- a/packages/grafana-data/src/rbac/rbac.ts +++ b/packages/grafana-data/src/rbac/rbac.ts @@ -1,4 +1,5 @@ -import { CurrentUserDTO, WithAccessControlMetadata } from '../types'; +import { WithAccessControlMetadata } from '../types/accesscontrol'; +import { CurrentUserDTO } from '../types/config'; export interface CurrentUser extends Omit {} diff --git a/packages/grafana-data/src/themes/createV1Theme.ts b/packages/grafana-data/src/themes/createV1Theme.ts index a4754db37e6..61f1f85fbd1 100644 --- a/packages/grafana-data/src/themes/createV1Theme.ts +++ b/packages/grafana-data/src/themes/createV1Theme.ts @@ -1,4 +1,4 @@ -import { GrafanaTheme, GrafanaThemeCommons, GrafanaThemeType } from '../types'; +import { GrafanaTheme, GrafanaThemeCommons, GrafanaThemeType } from '../types/theme'; import { GrafanaTheme2 } from './types'; diff --git a/packages/grafana-data/src/themes/createVisualizationColors.ts b/packages/grafana-data/src/themes/createVisualizationColors.ts index 89ccd9bbd20..36b908add01 100644 --- a/packages/grafana-data/src/themes/createVisualizationColors.ts +++ b/packages/grafana-data/src/themes/createVisualizationColors.ts @@ -1,4 +1,4 @@ -import { FALLBACK_COLOR } from '../types'; +import { FALLBACK_COLOR } from '../types/fieldColor'; import { ThemeColors } from './createColors'; diff --git a/packages/grafana-data/src/transformations/fieldReducer.test.ts b/packages/grafana-data/src/transformations/fieldReducer.test.ts index f155a62e511..baa5d704911 100644 --- a/packages/grafana-data/src/transformations/fieldReducer.test.ts +++ b/packages/grafana-data/src/transformations/fieldReducer.test.ts @@ -1,7 +1,8 @@ import { difference } from 'lodash'; import { createDataFrame, guessFieldTypeFromValue } from '../dataframe/processDataFrame'; -import { Field, FieldType, NullValueMode } from '../types/index'; +import { NullValueMode } from '../types/data'; +import { Field, FieldType } from '../types/dataFrame'; import { fieldReducers, ReducerID, reduceField, defaultCalcs } from './fieldReducer'; diff --git a/packages/grafana-data/src/transformations/fieldReducer.ts b/packages/grafana-data/src/transformations/fieldReducer.ts index b3d547184cb..d0bd2e9a178 100644 --- a/packages/grafana-data/src/transformations/fieldReducer.ts +++ b/packages/grafana-data/src/transformations/fieldReducer.ts @@ -1,7 +1,8 @@ // Libraries import { isNumber } from 'lodash'; -import { NullValueMode, Field, FieldCalcs, FieldType } from '../types/index'; +import { NullValueMode } from '../types/data'; +import { Field, FieldCalcs, FieldType } from '../types/dataFrame'; import { Registry, RegistryItem } from '../utils/Registry'; export enum ReducerID { diff --git a/packages/grafana-data/src/transformations/matchers/fieldValueMatcher.test.ts b/packages/grafana-data/src/transformations/matchers/fieldValueMatcher.test.ts index 90dac69896f..3e4773f9fec 100644 --- a/packages/grafana-data/src/transformations/matchers/fieldValueMatcher.test.ts +++ b/packages/grafana-data/src/transformations/matchers/fieldValueMatcher.test.ts @@ -1,8 +1,8 @@ import { ComparisonOperation } from '@grafana/schema'; import { toDataFrame } from '../../dataframe/processDataFrame'; -import { FieldMatcher } from '../../types'; import { DataFrame, FieldType } from '../../types/dataFrame'; +import { FieldMatcher } from '../../types/transformations'; import { ReducerID } from '../fieldReducer'; import { fieldValueMatcherInfo } from './fieldValueMatcher'; diff --git a/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts b/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts index ec1ce239664..f55bc1fce61 100644 --- a/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts +++ b/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts @@ -1,5 +1,5 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { FieldType, DataFrame } from '../../types'; +import { DataFrame, FieldType } from '../../types/dataFrame'; import { getFieldMatcher } from '../matchers'; import { FieldMatcherID } from './ids'; diff --git a/packages/grafana-data/src/transformations/standardTransformersRegistry.ts b/packages/grafana-data/src/transformations/standardTransformersRegistry.ts index e04950873be..87970a33994 100644 --- a/packages/grafana-data/src/transformations/standardTransformersRegistry.ts +++ b/packages/grafana-data/src/transformations/standardTransformersRegistry.ts @@ -1,6 +1,7 @@ import * as React from 'react'; -import { DataFrame, DataTransformerInfo } from '../types'; +import { DataFrame } from '../types/dataFrame'; +import { DataTransformerInfo } from '../types/transformations'; import { Registry, RegistryItem } from '../utils/Registry'; export interface TransformerUIProps { diff --git a/packages/grafana-data/src/transformations/transformDataFrame.test.ts b/packages/grafana-data/src/transformations/transformDataFrame.test.ts index 83643922401..1a61a26b432 100644 --- a/packages/grafana-data/src/transformations/transformDataFrame.test.ts +++ b/packages/grafana-data/src/transformations/transformDataFrame.test.ts @@ -1,7 +1,8 @@ import { map } from 'rxjs'; import { toDataFrame } from '../dataframe/processDataFrame'; -import { CustomTransformOperator, FieldType } from '../types'; +import { FieldType } from '../types/dataFrame'; +import { CustomTransformOperator } from '../types/transformations'; import { mockTransformationsRegistry } from '../utils/tests/mockTransformationsRegistry'; import { ReducerID } from './fieldReducer'; diff --git a/packages/grafana-data/src/transformations/transformDataFrame.ts b/packages/grafana-data/src/transformations/transformDataFrame.ts index 6717b6565a5..810177aa1c2 100644 --- a/packages/grafana-data/src/transformations/transformDataFrame.ts +++ b/packages/grafana-data/src/transformations/transformDataFrame.ts @@ -1,13 +1,13 @@ import { MonoTypeOperatorFunction, Observable, of } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; +import { DataFrame } from '../types/dataFrame'; import { - DataFrame, + CustomTransformOperator, DataTransformContext, DataTransformerConfig, FrameMatcher, - CustomTransformOperator, -} from '../types'; +} from '../types/transformations'; import { getFrameMatchers } from './matchers'; import { standardTransformersRegistry, TransformerRegistryItem } from './standardTransformersRegistry'; diff --git a/packages/grafana-data/src/transformations/transformers/calculateField.test.ts b/packages/grafana-data/src/transformations/transformers/calculateField.test.ts index 7e2db7468d9..154083e3169 100644 --- a/packages/grafana-data/src/transformations/transformers/calculateField.test.ts +++ b/packages/grafana-data/src/transformations/transformers/calculateField.test.ts @@ -1,7 +1,8 @@ import { DataFrameView } from '../../dataframe/DataFrameView'; import { toDataFrame } from '../../dataframe/processDataFrame'; -import { DataTransformContext, ScopedVars } from '../../types'; +import { ScopedVars } from '../../types/ScopedVars'; import { FieldType } from '../../types/dataFrame'; +import { DataTransformContext } from '../../types/transformations'; import { BinaryOperationID } from '../../utils/binaryOperators'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { UnaryOperationID } from '../../utils/unaryOperators'; diff --git a/packages/grafana-data/src/transformations/transformers/calculateField.ts b/packages/grafana-data/src/transformations/transformers/calculateField.ts index 72c4f62c519..820ecdec7cf 100644 --- a/packages/grafana-data/src/transformations/transformers/calculateField.ts +++ b/packages/grafana-data/src/transformations/transformers/calculateField.ts @@ -3,7 +3,9 @@ import { map } from 'rxjs/operators'; import { getTimeField } from '../../dataframe/processDataFrame'; import { getFieldDisplayName } from '../../field/fieldState'; -import { DataFrame, DataTransformerInfo, Field, FieldType, NullValueMode } from '../../types'; +import { NullValueMode } from '../../types/data'; +import { DataFrame, FieldType, Field } from '../../types/dataFrame'; +import { DataTransformerInfo } from '../../types/transformations'; import { BinaryOperationID, binaryOperators } from '../../utils/binaryOperators'; import { UnaryOperationID, unaryOperators } from '../../utils/unaryOperators'; import { doStandardCalcs, fieldReducers, ReducerID } from '../fieldReducer'; diff --git a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts index 669a9480bd1..7eda28e04f5 100644 --- a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts +++ b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts @@ -2,9 +2,9 @@ import { map } from 'rxjs/operators'; import { TimeZone } from '@grafana/schema'; -import { DateTimeOptionsWhenParsing, dateTimeParse } from '../../datetime'; -import { SynchronousDataTransformerInfo } from '../../types'; +import { dateTimeParse, DateTimeOptionsWhenParsing } from '../../datetime/parser'; import { DataFrame, EnumFieldConfig, Field, FieldType } from '../../types/dataFrame'; +import { SynchronousDataTransformerInfo } from '../../types/transformations'; import { fieldMatchers } from '../matchers'; import { FieldMatcherID } from '../matchers/ids'; diff --git a/packages/grafana-data/src/transformations/transformers/filterByName.test.ts b/packages/grafana-data/src/transformations/transformers/filterByName.test.ts index e357b2c6b40..82fe567ee3d 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByName.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByName.test.ts @@ -1,5 +1,5 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { ScopedVars } from '../../types'; +import { ScopedVars } from '../../types/ScopedVars'; import { FieldType } from '../../types/dataFrame'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts b/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts index 5d2ba4b10f9..a254707bb44 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { DataTransformerConfig, FieldType, MatcherConfig } from '../../types'; +import { FieldType } from '../../types/dataFrame'; +import { DataTransformerConfig, MatcherConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { ValueMatcherID } from '../matchers/ids'; import { BasicValueMatcherOptions } from '../matchers/valueMatchers/types'; diff --git a/packages/grafana-data/src/transformations/transformers/formatString.ts b/packages/grafana-data/src/transformations/transformers/formatString.ts index 2e46e4d5a72..15041c0e947 100644 --- a/packages/grafana-data/src/transformations/transformers/formatString.ts +++ b/packages/grafana-data/src/transformations/transformers/formatString.ts @@ -1,6 +1,6 @@ import { map } from 'rxjs/operators'; -import { DataFrame, Field, FieldType } from '../../types'; +import { DataFrame, Field, FieldType } from '../../types/dataFrame'; import { DataTransformerInfo, FieldMatcher, TransformationApplicabilityLevels } from '../../types/transformations'; import { fieldMatchers } from '../matchers'; import { FieldMatcherID } from '../matchers/ids'; diff --git a/packages/grafana-data/src/transformations/transformers/formatTime.ts b/packages/grafana-data/src/transformations/transformers/formatTime.ts index f11c434a5fa..71310c4524b 100644 --- a/packages/grafana-data/src/transformations/transformers/formatTime.ts +++ b/packages/grafana-data/src/transformations/transformers/formatTime.ts @@ -3,8 +3,12 @@ import { map } from 'rxjs/operators'; import { TimeZone } from '@grafana/schema'; import { cacheFieldDisplayNames } from '../../field/fieldState'; -import { DataFrame, TransformationApplicabilityLevels } from '../../types'; -import { DataTransformContext, DataTransformerInfo } from '../../types/transformations'; +import { DataFrame } from '../../types/dataFrame'; +import { + DataTransformContext, + DataTransformerInfo, + TransformationApplicabilityLevels, +} from '../../types/transformations'; import { fieldToStringField } from './convertFieldType'; import { DataTransformerID } from './ids'; diff --git a/packages/grafana-data/src/transformations/transformers/groupBy.test.ts b/packages/grafana-data/src/transformations/transformers/groupBy.test.ts index 3485d6615b7..cbdcc0c402f 100644 --- a/packages/grafana-data/src/transformations/transformers/groupBy.test.ts +++ b/packages/grafana-data/src/transformations/transformers/groupBy.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { DataTransformerConfig, Field, FieldType } from '../../types'; +import { FieldType, Field } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { ReducerID } from '../fieldReducer'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/groupBy.ts b/packages/grafana-data/src/transformations/transformers/groupBy.ts index a8faf733e44..7bcb2ebc3e2 100644 --- a/packages/grafana-data/src/transformations/transformers/groupBy.ts +++ b/packages/grafana-data/src/transformations/transformers/groupBy.ts @@ -2,8 +2,8 @@ import { map } from 'rxjs/operators'; import { guessFieldTypeForField } from '../../dataframe/processDataFrame'; import { getFieldDisplayName } from '../../field/fieldState'; -import { DataFrame, Field, FieldType, TransformationApplicabilityLevels } from '../../types'; -import { DataTransformerInfo } from '../../types/transformations'; +import { DataFrame, Field, FieldType } from '../../types/dataFrame'; +import { DataTransformerInfo, TransformationApplicabilityLevels } from '../../types/transformations'; import { reduceField, ReducerID } from '../fieldReducer'; import { DataTransformerID } from './ids'; diff --git a/packages/grafana-data/src/transformations/transformers/groupToNestedTable.test.ts b/packages/grafana-data/src/transformations/transformers/groupToNestedTable.test.ts index d36a0ce1adf..2737732a383 100644 --- a/packages/grafana-data/src/transformations/transformers/groupToNestedTable.test.ts +++ b/packages/grafana-data/src/transformations/transformers/groupToNestedTable.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { DataTransformerConfig, Field, FieldType } from '../../types'; +import { FieldType, Field } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { ReducerID } from '../fieldReducer'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/groupingToMatrix.test.ts b/packages/grafana-data/src/transformations/transformers/groupingToMatrix.test.ts index 97270e24e89..057c41c2fec 100644 --- a/packages/grafana-data/src/transformations/transformers/groupingToMatrix.test.ts +++ b/packages/grafana-data/src/transformations/transformers/groupingToMatrix.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { DataTransformerConfig, FieldType, Field, SpecialValue } from '../../types'; +import { FieldType, Field } from '../../types/dataFrame'; +import { DataTransformerConfig, SpecialValue } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/groupingToMatrix.ts b/packages/grafana-data/src/transformations/transformers/groupingToMatrix.ts index bb1dcd445d4..a71fb929c55 100644 --- a/packages/grafana-data/src/transformations/transformers/groupingToMatrix.ts +++ b/packages/grafana-data/src/transformations/transformers/groupingToMatrix.ts @@ -1,14 +1,8 @@ import { map } from 'rxjs/operators'; import { getFieldDisplayName } from '../../field/fieldState'; -import { - DataFrame, - DataTransformerInfo, - Field, - FieldType, - SpecialValue, - TransformationApplicabilityLevels, -} from '../../types'; +import { DataFrame, Field, FieldType } from '../../types/dataFrame'; +import { SpecialValue, DataTransformerInfo, TransformationApplicabilityLevels } from '../../types/transformations'; import { fieldMatchers } from '../matchers'; import { FieldMatcherID } from '../matchers/ids'; diff --git a/packages/grafana-data/src/transformations/transformers/histogram.ts b/packages/grafana-data/src/transformations/transformers/histogram.ts index 217caec18cb..a94cd182138 100644 --- a/packages/grafana-data/src/transformations/transformers/histogram.ts +++ b/packages/grafana-data/src/transformations/transformers/histogram.ts @@ -2,8 +2,9 @@ import { map } from 'rxjs/operators'; import { getDisplayProcessor } from '../../field/displayProcessor'; import { createTheme, GrafanaTheme2 } from '../../themes'; -import { DataFrameType, DataTransformContext, SynchronousDataTransformerInfo } from '../../types'; import { DataFrame, Field, FieldConfig, FieldType } from '../../types/dataFrame'; +import { DataFrameType } from '../../types/dataFrameTypes'; +import { DataTransformContext, SynchronousDataTransformerInfo } from '../../types/transformations'; import { roundDecimals } from '../../utils/numbers'; import { DataTransformerID } from './ids'; diff --git a/packages/grafana-data/src/transformations/transformers/joinByField.test.ts b/packages/grafana-data/src/transformations/transformers/joinByField.test.ts index 3673b82e050..897a03b7fe9 100644 --- a/packages/grafana-data/src/transformations/transformers/joinByField.test.ts +++ b/packages/grafana-data/src/transformations/transformers/joinByField.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { FieldType, DataTransformerConfig } from '../../types'; +import { FieldType } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/joinByField.ts b/packages/grafana-data/src/transformations/transformers/joinByField.ts index 7af73403164..127cb3a3bd2 100644 --- a/packages/grafana-data/src/transformations/transformers/joinByField.ts +++ b/packages/grafana-data/src/transformations/transformers/joinByField.ts @@ -1,6 +1,7 @@ import { map } from 'rxjs/operators'; -import { DataFrame, SynchronousDataTransformerInfo, FieldMatcher, DataTransformContext } from '../../types'; +import { DataFrame } from '../../types/dataFrame'; +import { DataTransformContext, FieldMatcher, SynchronousDataTransformerInfo } from '../../types/transformations'; import { fieldMatchers } from '../matchers'; import { FieldMatcherID } from '../matchers/ids'; diff --git a/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts b/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts index 21a780aae77..a2e5f0ad080 100644 --- a/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts +++ b/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts @@ -1,5 +1,6 @@ import { getTimeField, sortDataFrame } from '../../dataframe/processDataFrame'; -import { DataFrame, Field, FieldMatcher, FieldType, TIME_SERIES_VALUE_FIELD_NAME } from '../../types'; +import { DataFrame, Field, FieldType, TIME_SERIES_VALUE_FIELD_NAME } from '../../types/dataFrame'; +import { FieldMatcher } from '../../types/transformations'; import { fieldMatchers } from '../matchers'; import { FieldMatcherID } from '../matchers/ids'; diff --git a/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts b/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts index e9061bdc248..ec578d99c5f 100644 --- a/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts +++ b/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts @@ -1,7 +1,8 @@ import { Subscription } from 'rxjs'; import { toDataFrame, toDataFrameDTO } from '../../dataframe/processDataFrame'; -import { DataFrame, DataTransformerConfig, FieldDTO, FieldType } from '../../types'; +import { DataFrame, FieldDTO, FieldType } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/labelsToFields.ts b/packages/grafana-data/src/transformations/transformers/labelsToFields.ts index c21a0ec2abd..b63af3d16cb 100644 --- a/packages/grafana-data/src/transformations/transformers/labelsToFields.ts +++ b/packages/grafana-data/src/transformations/transformers/labelsToFields.ts @@ -1,7 +1,8 @@ import { map } from 'rxjs/operators'; import { getFieldDisplayName } from '../../field/fieldState'; -import { DataFrame, Field, FieldType, SynchronousDataTransformerInfo } from '../../types'; +import { DataFrame, Field, FieldType } from '../../types/dataFrame'; +import { SynchronousDataTransformerInfo } from '../../types/transformations'; import { DataTransformerID } from './ids'; diff --git a/packages/grafana-data/src/transformations/transformers/limit.test.ts b/packages/grafana-data/src/transformations/transformers/limit.test.ts index 6608646b986..ea476b36446 100644 --- a/packages/grafana-data/src/transformations/transformers/limit.test.ts +++ b/packages/grafana-data/src/transformations/transformers/limit.test.ts @@ -1,7 +1,6 @@ -import { DataTransformerConfig } from '@grafana/data'; - import { toDataFrame } from '../../dataframe/processDataFrame'; -import { Field, FieldType } from '../../types'; +import { FieldType, Field } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/limit.ts b/packages/grafana-data/src/transformations/transformers/limit.ts index 2aa4d4c0fa2..a37bc7b2143 100644 --- a/packages/grafana-data/src/transformations/transformers/limit.ts +++ b/packages/grafana-data/src/transformations/transformers/limit.ts @@ -1,6 +1,6 @@ import { map } from 'rxjs/operators'; -import { DataTransformerInfo } from '../../types'; +import { DataTransformerInfo } from '../../types/transformations'; import { DataTransformerID } from './ids'; import { transformationsVariableSupport } from './utils'; diff --git a/packages/grafana-data/src/transformations/transformers/merge.test.ts b/packages/grafana-data/src/transformations/transformers/merge.test.ts index 35306cfebfd..81bcf535c7d 100644 --- a/packages/grafana-data/src/transformations/transformers/merge.test.ts +++ b/packages/grafana-data/src/transformations/transformers/merge.test.ts @@ -1,5 +1,7 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { DataTransformerConfig, DisplayProcessor, Field, FieldType } from '../../types'; +import { Field, FieldType } from '../../types/dataFrame'; +import { DisplayProcessor } from '../../types/displayValue'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/noop.ts b/packages/grafana-data/src/transformations/transformers/noop.ts index bf2d5f93c9b..c9ef44b2802 100644 --- a/packages/grafana-data/src/transformations/transformers/noop.ts +++ b/packages/grafana-data/src/transformations/transformers/noop.ts @@ -1,4 +1,4 @@ -import { DataFrame } from '../../types'; +import { DataFrame } from '../../types/dataFrame'; import { SynchronousDataTransformerInfo } from '../../types/transformations'; import { DataTransformerID } from './ids'; diff --git a/packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.test.ts b/packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.test.ts index b6b7e20f3f2..ac1c98f318b 100644 --- a/packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.test.ts +++ b/packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.test.ts @@ -1,5 +1,5 @@ import { createDataFrame } from '../../../dataframe/processDataFrame'; -import { FieldType } from '../../../types'; +import { FieldType } from '../../../types/dataFrame'; import { applyNullInsertThreshold } from './nullInsertThreshold'; diff --git a/packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.ts b/packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.ts index 6da2748415e..47411b7f3e7 100644 --- a/packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.ts +++ b/packages/grafana-data/src/transformations/transformers/nulls/nullInsertThreshold.ts @@ -1,4 +1,4 @@ -import { DataFrame, FieldType } from '../../../types'; +import { DataFrame, FieldType } from '../../../types/dataFrame'; type InsertMode = (prev: number, next: number, threshold: number) => number; diff --git a/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.test.ts b/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.test.ts index ac24f194954..03b21865798 100644 --- a/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.test.ts +++ b/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.test.ts @@ -1,5 +1,5 @@ import { createDataFrame } from '../../../dataframe/processDataFrame'; -import { FieldType } from '../../../types'; +import { FieldType } from '../../../types/dataFrame'; import { applyNullInsertThreshold } from './nullInsertThreshold'; import { nullToValue } from './nullToValue'; diff --git a/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.ts b/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.ts index a2d2eeb88c7..3e767b38d4d 100644 --- a/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.ts +++ b/packages/grafana-data/src/transformations/transformers/nulls/nullToValue.ts @@ -1,4 +1,4 @@ -import { DataFrame, Field } from '../../../types'; +import { DataFrame, Field } from '../../../types/dataFrame'; export function nullToValue(frame: DataFrame) { return { diff --git a/packages/grafana-data/src/transformations/transformers/order.test.ts b/packages/grafana-data/src/transformations/transformers/order.test.ts index 447823c8b07..50ebdba74bc 100644 --- a/packages/grafana-data/src/transformations/transformers/order.test.ts +++ b/packages/grafana-data/src/transformations/transformers/order.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { FieldType, DataTransformerConfig } from '../../types'; +import { FieldType } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/order.ts b/packages/grafana-data/src/transformations/transformers/order.ts index bd6a595e75b..897c3f9016a 100644 --- a/packages/grafana-data/src/transformations/transformers/order.ts +++ b/packages/grafana-data/src/transformations/transformers/order.ts @@ -2,7 +2,7 @@ import { clone } from 'lodash'; import { map } from 'rxjs/operators'; import { getFieldDisplayName } from '../../field/fieldState'; -import { DataFrame, Field } from '../../types'; +import { DataFrame, Field } from '../../types/dataFrame'; import { DataTransformerInfo } from '../../types/transformations'; import { DataTransformerID } from './ids'; diff --git a/packages/grafana-data/src/transformations/transformers/organize.test.ts b/packages/grafana-data/src/transformations/transformers/organize.test.ts index ce5eb838a56..ff98427c804 100644 --- a/packages/grafana-data/src/transformations/transformers/organize.test.ts +++ b/packages/grafana-data/src/transformations/transformers/organize.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { FieldType, DataTransformerConfig } from '../../types'; +import { FieldType } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/organize.ts b/packages/grafana-data/src/transformations/transformers/organize.ts index dfda3a779b4..b9d3355c36b 100644 --- a/packages/grafana-data/src/transformations/transformers/organize.ts +++ b/packages/grafana-data/src/transformations/transformers/organize.ts @@ -1,4 +1,5 @@ -import { DataFrame, DataTransformerInfo, TransformationApplicabilityLevels } from '../../types'; +import { DataFrame } from '../../types/dataFrame'; +import { DataTransformerInfo, TransformationApplicabilityLevels } from '../../types/transformations'; import { filterFieldsByNameTransformer } from './filterByName'; import { DataTransformerID } from './ids'; diff --git a/packages/grafana-data/src/transformations/transformers/reduce.test.ts b/packages/grafana-data/src/transformations/transformers/reduce.test.ts index 45c1648223e..5d66b7ec67f 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.test.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.test.ts @@ -1,6 +1,7 @@ import { DataFrameView } from '../../dataframe/DataFrameView'; import { toDataFrame } from '../../dataframe/processDataFrame'; -import { DataTransformerConfig, Field, FieldType } from '../../types'; +import { Field, FieldType } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { ReducerID } from '../fieldReducer'; import { notTimeFieldMatcher } from '../matchers/predicates'; diff --git a/packages/grafana-data/src/transformations/transformers/rename.test.ts b/packages/grafana-data/src/transformations/transformers/rename.test.ts index 75e1ad9bc52..05378e1c97f 100644 --- a/packages/grafana-data/src/transformations/transformers/rename.test.ts +++ b/packages/grafana-data/src/transformations/transformers/rename.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { FieldType, DataTransformerConfig } from '../../types'; +import { FieldType } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/renameByRegex.test.ts b/packages/grafana-data/src/transformations/transformers/renameByRegex.test.ts index 36629c57081..9390ba44e6c 100644 --- a/packages/grafana-data/src/transformations/transformers/renameByRegex.test.ts +++ b/packages/grafana-data/src/transformations/transformers/renameByRegex.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { FieldType, DataTransformerConfig } from '../../types'; +import { FieldType } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts b/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts index 617af200c82..0db6812a0d8 100644 --- a/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts +++ b/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { DataTransformerConfig, Field, FieldType } from '../../types'; +import { FieldType, Field } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/sortBy.test.ts b/packages/grafana-data/src/transformations/transformers/sortBy.test.ts index 039ac0167eb..c7de0e93414 100644 --- a/packages/grafana-data/src/transformations/transformers/sortBy.test.ts +++ b/packages/grafana-data/src/transformations/transformers/sortBy.test.ts @@ -1,5 +1,6 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; -import { DataTransformerConfig, Field, FieldType } from '../../types'; +import { Field, FieldType } from '../../types/dataFrame'; +import { DataTransformerConfig } from '../../types/transformations'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; diff --git a/packages/grafana-data/src/transformations/transformers/sortBy.ts b/packages/grafana-data/src/transformations/transformers/sortBy.ts index ce323c1e095..8df10fd7aaa 100644 --- a/packages/grafana-data/src/transformations/transformers/sortBy.ts +++ b/packages/grafana-data/src/transformations/transformers/sortBy.ts @@ -2,7 +2,7 @@ import { map } from 'rxjs/operators'; import { sortDataFrame } from '../../dataframe/processDataFrame'; import { getFieldDisplayName } from '../../field/fieldState'; -import { DataFrame } from '../../types'; +import { DataFrame } from '../../types/dataFrame'; import { DataTransformContext, DataTransformerInfo } from '../../types/transformations'; import { DataTransformerID } from './ids'; diff --git a/packages/grafana-data/src/transformations/transformers/utils.ts b/packages/grafana-data/src/transformations/transformers/utils.ts index 7b3c0efaf50..699cb3517ca 100644 --- a/packages/grafana-data/src/transformations/transformers/utils.ts +++ b/packages/grafana-data/src/transformations/transformers/utils.ts @@ -1,4 +1,5 @@ -import { BootData, DataFrame } from '../../types'; +import { BootData } from '../../types/config'; +import { DataFrame } from '../../types/dataFrame'; declare global { interface Window { diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 72f89ea9286..b8703f55aaf 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -1,12 +1,14 @@ -import { SystemDateFormatSettings } from '../datetime'; +import { SystemDateFormatSettings } from '../datetime/formats'; import { MapLayerOptions } from '../geo/layer'; import { GrafanaTheme2 } from '../themes'; import { DataSourceInstanceSettings } from './datasource'; import { FeatureToggles } from './featureToggles.gen'; +import { IconName } from './icon'; +import { NavLinkDTO } from './navModel'; +import { OrgRole } from './orgs'; import { PanelPluginMeta } from './panel'; - -import { GrafanaTheme, IconName, NavLinkDTO, OrgRole } from '.'; +import { GrafanaTheme } from './theme'; /** * Describes the build information that will be available via the Grafana configuration. diff --git a/packages/grafana-data/src/types/data.ts b/packages/grafana-data/src/types/data.ts index 13a0eb789dc..3640e25c655 100644 --- a/packages/grafana-data/src/types/data.ts +++ b/packages/grafana-data/src/types/data.ts @@ -1,11 +1,10 @@ import { DataFrameDTO, FieldConfig } from './dataFrame'; import { DataFrameType } from './dataFrameTypes'; import { ApplyFieldOverrideOptions } from './fieldOverrides'; +import { PanelPluginDataSupport } from './panel'; import { DataTopic } from './query'; import { DataTransformerConfig } from './transformations'; -import { PanelPluginDataSupport } from '.'; - export type KeyValue = Record; /** diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index 2990facb107..d4970c3d750 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -4,18 +4,19 @@ import { Observable } from 'rxjs'; import { makeClassES5Compatible } from '../utils/makeClassES5Compatible'; import { ScopedVars } from './ScopedVars'; +import { WithAccessControlMetadata } from './accesscontrol'; import { AnnotationEvent, AnnotationQuery, AnnotationSupport } from './annotations'; import { CoreApp } from './app'; import { KeyValue, LoadingState, TableData, TimeSeries } from './data'; import { DataFrame, DataFrameDTO } from './dataFrame'; import { PanelData } from './panel'; import { GrafanaPlugin, PluginMeta } from './plugin'; -import { DataQuery } from './query'; +import { DataQuery, DataSourceRef } from './query'; +import { Scope } from './scopes'; +import { AdHocVariableFilter } from './templateVars'; import { RawTimeRange, TimeRange } from './time'; import { CustomVariableSupport, DataSourceVariableSupport, StandardVariableSupport } from './variables'; -import { AdHocVariableFilter, DataSourceRef, Scope, WithAccessControlMetadata } from '.'; - export interface DataSourcePluginOptionsEditorProps< JSONData extends DataSourceJsonData = DataSourceJsonData, SecureJSONData = {}, diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index 975c2dd729a..a6159030bbf 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -3,21 +3,15 @@ import { ComponentType } from 'react'; import { FieldConfigOptionsRegistry } from '../field/FieldConfigOptionsRegistry'; import { StandardEditorContext, StandardEditorProps } from '../field/standardFieldConfigEditorRegistry'; import { GrafanaTheme2 } from '../themes'; -import { - MatcherConfig, - FieldConfig, - Field, - DataFrame, - TimeZone, - ScopedVars, - ValueLinkConfig, - LinkModel, - DataLink, -} from '../types'; import { OptionsEditorItem } from './OptionsUIRegistryBuilder'; +import { ScopedVars } from './ScopedVars'; +import { DataFrame, Field, FieldConfig, ValueLinkConfig } from './dataFrame'; +import { DataLink, LinkModel } from './dataLink'; import { OptionEditorConfig } from './options'; import { InterpolateFunction } from './panel'; +import { TimeZone } from './time'; +import { MatcherConfig } from './transformations'; export interface DynamicConfigValue { id: string; diff --git a/packages/grafana-data/src/types/index.ts b/packages/grafana-data/src/types/index.ts deleted file mode 100644 index e467d5d8ee4..00000000000 --- a/packages/grafana-data/src/types/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -export * from './constants'; -export * from './data'; -export * from './dataFrame'; -export * from './dataFrameTypes'; -export * from './dataLink'; -export * from './dashboard'; -export * from './query'; -export * from './annotations'; -export * from './logs'; -export * from './navModel'; -export * from './select'; -export * from './time'; -export * from './thresholds'; -export * from './valueMapping'; -export * from './displayValue'; -export * from './graph'; -export * from './ScopedVars'; -export * from './transformations'; -export * from './fieldOverrides'; -export * from './vector'; -export * from './app'; -export * from './datasource'; -export * from './panel'; -export * from './plugin'; -export * from './thresholds'; -export * from './templateVars'; -export * from './fieldColor'; -export * from './theme'; -export * from './orgs'; -export * from './flot'; -export * from './trace'; -export * from './explore'; -export * from './legacyEvents'; -export * from './live'; -export * from './variables'; -export * from './geometry'; -export { isUnsignedPluginSignature } from './pluginSignature'; -export type { - CurrentUserDTO, - AnalyticsSettings, - BootData, - OAuth, - OAuthSettings, - AuthSettings, - GrafanaConfig, - BuildInfo, - LicenseInfo, -} from './config'; -export type { FeatureToggles } from './featureToggles.gen'; -export * from './alerts'; -export * from './slider'; -export * from './accesscontrol'; -export * from './icon'; -export { - PluginExtensionTypes, - PluginExtensionPoints, - type PluginExtension, - type PluginExtensionLink, - type PluginExtensionComponent, - type PluginExtensionConfig, - type PluginExtensionLinkConfig, - type PluginExtensionComponentConfig, - type PluginExtensionEventHelpers, - type PluginExtensionPanelContext, - type PluginExtensionDataSourceConfigContext, - type PluginExtensionCommandPaletteContext, - type PluginExtensionOpenModalOptions, -} from './pluginExtensions'; -export * from './scopes'; diff --git a/packages/grafana-data/src/utils/OptionsUIBuilders.ts b/packages/grafana-data/src/utils/OptionsUIBuilders.ts index 0d5a8bb1751..c7d9bf83257 100644 --- a/packages/grafana-data/src/utils/OptionsUIBuilders.ts +++ b/packages/grafana-data/src/utils/OptionsUIBuilders.ts @@ -20,8 +20,8 @@ import { standardEditorsRegistry, } from '../field/standardFieldConfigEditorRegistry'; import { PanelOptionsSupplier } from '../panel/PanelPlugin'; -import { isObject } from '../types'; import { OptionsEditorItem, OptionsUIRegistryBuilder } from '../types/OptionsUIRegistryBuilder'; +import { isObject } from '../types/data'; import { FieldConfigPropertyItem, FieldConfigEditorConfig } from '../types/fieldOverrides'; import { PanelOptionsEditorConfig, PanelOptionsEditorItem } from '../types/panel'; diff --git a/packages/grafana-data/src/utils/Registry.ts b/packages/grafana-data/src/utils/Registry.ts index ba26da74fdc..963cb5ec6ae 100644 --- a/packages/grafana-data/src/utils/Registry.ts +++ b/packages/grafana-data/src/utils/Registry.ts @@ -1,4 +1,4 @@ -import { PluginState } from '../types'; +import { PluginState } from '../types/plugin'; import { SelectableValue } from '../types/select'; export interface RegistryItem { diff --git a/packages/grafana-data/src/utils/csv.ts b/packages/grafana-data/src/utils/csv.ts index d8d4e33d242..766c3680ee8 100644 --- a/packages/grafana-data/src/utils/csv.ts +++ b/packages/grafana-data/src/utils/csv.ts @@ -6,7 +6,7 @@ import Papa, { ParseConfig, Parser, ParseResult } from 'papaparse'; import { MutableDataFrame } from '../dataframe/MutableDataFrame'; import { guessFieldTypeFromValue } from '../dataframe/processDataFrame'; import { getFieldDisplayName } from '../field/fieldState'; -import { DataFrame, Field, FieldConfig, FieldType } from '../types'; +import { DataFrame, Field, FieldConfig, FieldType } from '../types/dataFrame'; import { formattedValueToString } from '../valueFormats/valueFormats'; export enum CSVHeaderStyle { diff --git a/packages/grafana-data/src/utils/dataLinks.test.ts b/packages/grafana-data/src/utils/dataLinks.test.ts index 31f6e4162b8..945e88aa1e0 100644 --- a/packages/grafana-data/src/utils/dataLinks.test.ts +++ b/packages/grafana-data/src/utils/dataLinks.test.ts @@ -1,5 +1,7 @@ -import { DateTime, toUtc } from '../datetime'; -import { DataLink, FieldType, TimeRange } from '../types'; +import { DateTime, toUtc } from '../datetime/moment_wrapper'; +import { FieldType } from '../types/dataFrame'; +import { DataLink } from '../types/dataLink'; +import { TimeRange } from '../types/time'; import { mapInternalLinkToExplore } from './dataLinks'; diff --git a/packages/grafana-data/src/utils/dataLinks.ts b/packages/grafana-data/src/utils/dataLinks.ts index d47d03d71bb..8c8084cee34 100644 --- a/packages/grafana-data/src/utils/dataLinks.ts +++ b/packages/grafana-data/src/utils/dataLinks.ts @@ -1,15 +1,10 @@ -import { - DataLink, - DataQuery, - ExplorePanelsState, - Field, - InternalDataLink, - InterpolateFunction, - LinkModel, - ScopedVars, - SplitOpen, - TimeRange, -} from '../types'; +import { ScopedVars } from '../types/ScopedVars'; +import { Field } from '../types/dataFrame'; +import { DataLink, InternalDataLink, LinkModel } from '../types/dataLink'; +import { SplitOpen, ExplorePanelsState } from '../types/explore'; +import { InterpolateFunction } from '../types/panel'; +import { DataQuery } from '../types/query'; +import { TimeRange } from '../types/time'; import { locationUtil } from './location'; import { serializeStateToUrlParam, toURLRange } from './url'; diff --git a/packages/grafana-data/src/utils/datasource.ts b/packages/grafana-data/src/utils/datasource.ts index 19d7971986a..7b9ba8d0de5 100644 --- a/packages/grafana-data/src/utils/datasource.ts +++ b/packages/grafana-data/src/utils/datasource.ts @@ -1,14 +1,14 @@ import { isString } from 'lodash'; +import { KeyValue } from '../types/data'; import { - DataSourcePluginOptionsEditorProps, - SelectableValue, - KeyValue, - DataSourceSettings, DataSourceInstanceSettings, - DataSourceRef, DataSourceJsonData, -} from '../types'; + DataSourcePluginOptionsEditorProps, + DataSourceSettings, +} from '../types/datasource'; +import { DataSourceRef } from '../types/query'; +import { SelectableValue } from '../types/select'; /** * Convert instance settings to a reference diff --git a/packages/grafana-data/src/utils/deprecationWarning.ts b/packages/grafana-data/src/utils/deprecationWarning.ts index b959ccd1e18..9bc37f9e3f3 100644 --- a/packages/grafana-data/src/utils/deprecationWarning.ts +++ b/packages/grafana-data/src/utils/deprecationWarning.ts @@ -1,4 +1,4 @@ -import { KeyValue } from '../types'; +import { KeyValue } from '../types/data'; // Avoid writing the warning message more than once every 10s const history: KeyValue = {}; diff --git a/packages/grafana-data/src/utils/legend.ts b/packages/grafana-data/src/utils/legend.ts index 407e4fb874a..34073eda52f 100644 --- a/packages/grafana-data/src/utils/legend.ts +++ b/packages/grafana-data/src/utils/legend.ts @@ -1,4 +1,4 @@ -import { Labels } from '../types'; +import { Labels } from '../types/data'; /** replace labels in a string. Used for loki+prometheus legend formats */ export function renderLegendFormat(aliasPattern: string, aliasData: Labels): string { diff --git a/packages/grafana-data/src/utils/location.test.ts b/packages/grafana-data/src/utils/location.test.ts index 897b4678cbb..b0fd82bbd98 100644 --- a/packages/grafana-data/src/utils/location.test.ts +++ b/packages/grafana-data/src/utils/location.test.ts @@ -1,6 +1,6 @@ import { Location } from 'history'; -import { GrafanaConfig } from '../types'; +import { GrafanaConfig } from '../types/config'; import { locationUtil } from './location'; diff --git a/packages/grafana-data/src/utils/location.ts b/packages/grafana-data/src/utils/location.ts index 0e33f3da102..cbff51a7f30 100644 --- a/packages/grafana-data/src/utils/location.ts +++ b/packages/grafana-data/src/utils/location.ts @@ -1,7 +1,9 @@ import { Location } from 'history'; import { textUtil } from '../text'; -import { GrafanaConfig, RawTimeRange, ScopedVars } from '../types'; +import { ScopedVars } from '../types/ScopedVars'; +import { GrafanaConfig } from '../types/config'; +import { RawTimeRange } from '../types/time'; import { UrlQueryMap, urlUtil } from './url'; diff --git a/packages/grafana-data/src/utils/matchPluginId.ts b/packages/grafana-data/src/utils/matchPluginId.ts index d21a0654215..6804597de8f 100644 --- a/packages/grafana-data/src/utils/matchPluginId.ts +++ b/packages/grafana-data/src/utils/matchPluginId.ts @@ -1,4 +1,4 @@ -import { PluginMeta } from '../types'; +import { PluginMeta } from '../types/plugin'; export function matchPluginId(idToMatch: string, pluginMeta: PluginMeta) { if (pluginMeta.id === idToMatch) { diff --git a/packages/grafana-data/src/utils/selectUtils.ts b/packages/grafana-data/src/utils/selectUtils.ts index 8c682fba762..4a46f5bad4d 100644 --- a/packages/grafana-data/src/utils/selectUtils.ts +++ b/packages/grafana-data/src/utils/selectUtils.ts @@ -1,3 +1,3 @@ -import { SelectableValue } from '../types'; +import { SelectableValue } from '../types/select'; export const toOption = (value: string): SelectableValue => ({ label: value, value }); diff --git a/packages/grafana-data/src/utils/series.test.ts b/packages/grafana-data/src/utils/series.test.ts index 8ac46275c31..bbf484232a2 100644 --- a/packages/grafana-data/src/utils/series.test.ts +++ b/packages/grafana-data/src/utils/series.test.ts @@ -1,4 +1,4 @@ -import { Field, FieldType } from '../types'; +import { Field, FieldType } from '../types/dataFrame'; import { getSeriesTimeStep, hasMsResolution } from './series'; diff --git a/packages/grafana-data/src/utils/tests/mockDataSource.ts b/packages/grafana-data/src/utils/tests/mockDataSource.ts index cfc0d99d6be..b19adba178e 100644 --- a/packages/grafana-data/src/utils/tests/mockDataSource.ts +++ b/packages/grafana-data/src/utils/tests/mockDataSource.ts @@ -1,17 +1,16 @@ import { Observable } from 'rxjs'; import { - DataQuery, DataQueryRequest, DataQueryResponse, DataSourceApi, DataSourceInstanceSettings, DataSourceJsonData, DataSourcePluginMeta, - PluginMetaInfo, - PluginType, TestDataSourceResponse, -} from '../../types'; +} from '../../types/datasource'; +import { PluginMetaInfo, PluginType } from '../../types/plugin'; +import { DataQuery } from '../../types/query'; export interface TestQuery extends DataQuery { query: string; diff --git a/packages/grafana-data/src/utils/tests/mockStandardProperties.ts b/packages/grafana-data/src/utils/tests/mockStandardProperties.ts index ef52b7f255e..729a55e55e8 100644 --- a/packages/grafana-data/src/utils/tests/mockStandardProperties.ts +++ b/packages/grafana-data/src/utils/tests/mockStandardProperties.ts @@ -1,5 +1,5 @@ import { displayNameOverrideProcessor, identityOverrideProcessor } from '../../field/overrides/processors'; -import { ThresholdsMode } from '../../types'; +import { ThresholdsMode } from '../../types/thresholds'; export const mockStandardProperties = () => { const title = { diff --git a/packages/grafana-data/src/utils/tests/mockTransformationsRegistry.ts b/packages/grafana-data/src/utils/tests/mockTransformationsRegistry.ts index 0d502a641f8..ef1b6f78fce 100644 --- a/packages/grafana-data/src/utils/tests/mockTransformationsRegistry.ts +++ b/packages/grafana-data/src/utils/tests/mockTransformationsRegistry.ts @@ -1,5 +1,5 @@ import { standardTransformersRegistry } from '../../transformations/standardTransformersRegistry'; -import { DataTransformerInfo } from '../../types'; +import { DataTransformerInfo } from '../../types/transformations'; export const mockTransformationsRegistry = (transformers: DataTransformerInfo[]) => { standardTransformersRegistry.setInit(() => { diff --git a/packages/grafana-data/src/utils/url.ts b/packages/grafana-data/src/utils/url.ts index 2ee35d9f449..b98ee5f5b64 100644 --- a/packages/grafana-data/src/utils/url.ts +++ b/packages/grafana-data/src/utils/url.ts @@ -2,9 +2,9 @@ * @preserve jquery-param (c) 2015 KNOWLEDGECODE | MIT */ -import { isDateTime } from '../datetime'; -import { URLRange, RawTimeRange } from '../types'; -import { ExploreUrlState } from '../types/explore'; +import { isDateTime } from '../datetime/moment_wrapper'; +import { ExploreUrlState, URLRange } from '../types/explore'; +import { RawTimeRange } from '../types/time'; /** * Type to represent the value of a single query variable. diff --git a/packages/grafana-data/src/utils/valueMappings.test.ts b/packages/grafana-data/src/utils/valueMappings.test.ts index 85b197735fe..2b60616d850 100644 --- a/packages/grafana-data/src/utils/valueMappings.test.ts +++ b/packages/grafana-data/src/utils/valueMappings.test.ts @@ -1,4 +1,4 @@ -import { ValueMapping, MappingType, SpecialValueMatch } from '../types'; +import { MappingType, SpecialValueMatch, ValueMapping } from '../types/valueMapping'; import { getValueMappingResult, isNumeric } from './valueMappings'; diff --git a/packages/grafana-data/src/utils/valueMappings.ts b/packages/grafana-data/src/utils/valueMappings.ts index 352699259ee..d7ab2cf6a24 100644 --- a/packages/grafana-data/src/utils/valueMappings.ts +++ b/packages/grafana-data/src/utils/valueMappings.ts @@ -1,14 +1,14 @@ import { getActiveThreshold } from '../field/thresholds'; import { stringToJsRegex } from '../text/string'; +import { ThresholdsConfig } from '../types/thresholds'; import { MappingType, SpecialValueMatch, - ThresholdsConfig, + SpecialValueOptions, ValueMap, ValueMapping, ValueMappingResult, - SpecialValueOptions, -} from '../types'; +} from '../types/valueMapping'; export function getValueMappingResult(valueMappings: ValueMapping[], value: any): ValueMappingResult | null { for (const vm of valueMappings) { diff --git a/packages/grafana-data/src/utils/variables.ts b/packages/grafana-data/src/utils/variables.ts index 2002472d174..d99db7ebb32 100644 --- a/packages/grafana-data/src/utils/variables.ts +++ b/packages/grafana-data/src/utils/variables.ts @@ -1,4 +1,4 @@ -import { ScopedVars } from '../types'; +import { ScopedVars } from '../types/ScopedVars'; const SEARCH_FILTER_VARIABLE = '__searchFilter'; diff --git a/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts b/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts index 4eb18bf8c50..62980e7dc2f 100644 --- a/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts +++ b/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts @@ -1,7 +1,8 @@ -import { dateTimeFormat, dateTimeFormatTimeAgo, localTimeFormat, systemDateFormats } from '../datetime'; +import { localTimeFormat, systemDateFormats } from '../datetime/formats'; +import { dateTimeFormat, dateTimeFormatTimeAgo } from '../datetime/formatter'; import { toDuration as duration, toUtc, dateTime } from '../datetime/moment_wrapper'; -import { TimeZone } from '../types'; import { DecimalCount } from '../types/displayValue'; +import { TimeZone } from '../types/time'; import { toFixed, toFixedScaled, FormattedValue, ValueFormatter } from './valueFormats'; diff --git a/packages/grafana-data/src/valueFormats/valueFormats.test.ts b/packages/grafana-data/src/valueFormats/valueFormats.test.ts index b0776ae4b41..a0df43a7e8a 100644 --- a/packages/grafana-data/src/valueFormats/valueFormats.test.ts +++ b/packages/grafana-data/src/valueFormats/valueFormats.test.ts @@ -1,6 +1,6 @@ -import { dateTime } from '../datetime'; -import { TimeZone } from '../types'; +import { dateTime } from '../datetime/moment_wrapper'; import { DecimalCount } from '../types/displayValue'; +import { TimeZone } from '../types/time'; import { toFixed, getValueFormat, scaledUnits, formattedValueToString } from './valueFormats'; diff --git a/packages/grafana-data/src/valueFormats/valueFormats.ts b/packages/grafana-data/src/valueFormats/valueFormats.ts index e4d99adcc91..0a4b943357f 100644 --- a/packages/grafana-data/src/valueFormats/valueFormats.ts +++ b/packages/grafana-data/src/valueFormats/valueFormats.ts @@ -1,7 +1,7 @@ import { clamp } from 'lodash'; -import { TimeZone } from '../types'; import { DecimalCount } from '../types/displayValue'; +import { TimeZone } from '../types/time'; import { getCategories } from './categories'; import { toDateTimeValueFormatter } from './dateTimeFormatters'; diff --git a/packages/grafana-data/src/vector/ArrayVector.test.ts b/packages/grafana-data/src/vector/ArrayVector.test.ts index eb0c53dfa0c..daa8c6375f5 100644 --- a/packages/grafana-data/src/vector/ArrayVector.test.ts +++ b/packages/grafana-data/src/vector/ArrayVector.test.ts @@ -1,4 +1,4 @@ -import { Field, FieldType } from '../types'; +import { Field, FieldType } from '../types/dataFrame'; import { ArrayVector } from './ArrayVector'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx index a312c2ecddd..751392acf44 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.tsx @@ -1,4 +1,4 @@ -import { PanelData } from '@grafana/data/src/types'; +import { PanelData } from '@grafana/data'; import { EditorRows, EditorRow, EditorFieldGroup } from '@grafana/experimental'; import { multiResourceCompatibleTypes } from '../../azureMetadata'; From 7b29242600073428db2983905414450583b2d3ec Mon Sep 17 00:00:00 2001 From: Joey <90795735+joey-grafana@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:55:33 +0100 Subject: [PATCH 12/39] Tempo: Fix query history (#89991) Fix query history for Tempo queries --- public/app/plugins/datasource/tempo/datasource.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index d287cae6459..93db409c37f 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -1,4 +1,4 @@ -import { groupBy, startCase } from 'lodash'; +import { groupBy } from 'lodash'; import { EMPTY, from, lastValueFrom, merge, Observable, of } from 'rxjs'; import { catchError, concatMap, map, mergeMap, toArray } from 'rxjs/operators'; import semver from 'semver'; @@ -738,17 +738,12 @@ export class TempoDatasource extends DataSourceWithBackend - > = ['serviceName', 'spanName', 'search', 'minDuration', 'maxDuration', 'limit']; - return keys - .filter((key) => query[key]) - .map((key) => `${startCase(key)}: ${query[key]}`) - .join(', '); + const appliedQuery = this.applyVariables(query, {}); + return generateQueryFromFilters(appliedQuery.filters); } } From d1952bb68142e4da857bbc82b2d5517a7503b8fe Mon Sep 17 00:00:00 2001 From: Bruno Date: Wed, 3 Jul 2024 10:38:26 -0300 Subject: [PATCH 13/39] Cloud migrations: create snapshot files (#89693) * Cloud migrations: create snapshot and store it on disk * fix merge conflicts * implement StartSnapshot for gms client * pass snapshot directory as argument to snapshot builder * ensure snapshot folder is set * make swagger-gen * remove Test_ExecuteAsyncWorkflow * pass signed in user to buildSnapshot method / use github.com/grafana/grafana-cloud-migration-snapshot to create snapshot files * fix FakeServiceImpl.CreateSnapshot * remove new line --- conf/defaults.ini | 4 + go.mod | 7 +- go.sum | 2 + pkg/services/cloudmigration/api/api.go | 3 +- .../cloudmigration/api/curl_commands.txt | 2 +- pkg/services/cloudmigration/cloudmigration.go | 3 +- .../cloudmigrationimpl/cloudmigration.go | 36 ++++---- .../cloudmigrationimpl/cloudmigration_noop.go | 3 +- .../cloudmigrationimpl/cloudmigration_test.go | 71 +-------------- .../fake/cloudmigration_fake.go | 3 +- .../cloudmigrationimpl/snapshot_mgmt.go | 90 ++++++++++++++----- .../cloudmigration/gmsclient/client.go | 2 +- .../cloudmigration/gmsclient/gms_client.go | 39 +++++++- .../gmsclient/inmemory_client.go | 12 +-- pkg/services/cloudmigration/model.go | 12 ++- .../cloudmigration/slicesext/slicesext.go | 33 +++++++ .../slicesext/slicesext_test.go | 80 +++++++++++++++++ pkg/setting/setting_cloud_migration.go | 10 +++ 18 files changed, 285 insertions(+), 127 deletions(-) create mode 100644 pkg/services/cloudmigration/slicesext/slicesext.go create mode 100644 pkg/services/cloudmigration/slicesext/slicesext_test.go diff --git a/conf/defaults.ini b/conf/defaults.ini index 931b9268897..88ed4791098 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1929,6 +1929,8 @@ enabled = true is_target = false # Token used to send requests to grafana com gcom_api_token = "" +# How long to wait for a request sent to gms to start a snapshot to complete +start_snapshot_timeout = 5s # How long to wait for a request to fetch an instance to complete fetch_instance_timeout = 5s # How long to wait for a request to create an access policy to complete @@ -1939,3 +1941,5 @@ fetch_access_policy_timeout = 5s delete_access_policy_timeout = 5s # The domain name used to access cms domain = grafana-dev.net +# Folder used to store snapshot files. Defaults to the home dir +snapshot_folder = "" \ No newline at end of file diff --git a/go.mod b/go.mod index 709f49e3c88..a1a32c1d0ac 100644 --- a/go.mod +++ b/go.mod @@ -89,6 +89,7 @@ require ( github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586 // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-aws-sdk v0.28.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.0.4 // @grafana/partner-datasources + github.com/grafana/grafana-cloud-migration-snapshot v1.0.0 // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group github.com/grafana/grafana-plugin-sdk-go v0.235.0 // @grafana/plugins-platform-backend @@ -381,10 +382,7 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/cors v1.10.1 // @grafana/identity-access-team - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/segmentio/asm v1.2.0 // indirect - github.com/segmentio/encoding v0.3.6 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect @@ -458,8 +456,11 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pressly/goose/v3 v3.20.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect + github.com/segmentio/encoding v0.3.6 // indirect github.com/sethvargo/go-retry v0.2.4 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect diff --git a/go.sum b/go.sum index 7db2c385dae..74c8f8e568e 100644 --- a/go.sum +++ b/go.sum @@ -2321,6 +2321,8 @@ github.com/grafana/grafana-aws-sdk v0.28.0 h1:ShdA+msLPGJGWWS1SFUYnF+ch1G3gUOlAd github.com/grafana/grafana-aws-sdk v0.28.0/go.mod h1:ZSVPU7IIJSi5lEg+K3Js+EUpZLXxUaBdaQWH+As1ihI= github.com/grafana/grafana-azure-sdk-go/v2 v2.0.4 h1:z6amQ286IJSBctHf6c+ibJq/v0+TvmEjVkrdMNBd4uY= github.com/grafana/grafana-azure-sdk-go/v2 v2.0.4/go.mod h1:aKlFPE36IDa8qccRg3KbgZX3MQ5xymS3RelT4j6kkVU= +github.com/grafana/grafana-cloud-migration-snapshot v1.0.0 h1:vOepRtpYS5ssG/PXLTpc/7OcL4lJiGruiU3Cw0c0DE4= +github.com/grafana/grafana-cloud-migration-snapshot v1.0.0/go.mod h1:rWNhyxYkgiXgV7xZ4yOQzMV08yikO8L8S8M5KNoQNpA= github.com/grafana/grafana-google-sdk-go v0.1.0 h1:LKGY8z2DSxKjYfr2flZsWgTRTZ6HGQbTqewE3JvRaNA= github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= diff --git a/pkg/services/cloudmigration/api/api.go b/pkg/services/cloudmigration/api/api.go index cc221f66b98..e436857ab64 100644 --- a/pkg/services/cloudmigration/api/api.go +++ b/pkg/services/cloudmigration/api/api.go @@ -376,11 +376,12 @@ func (cma *CloudMigrationAPI) CreateSnapshot(c *contextmodel.ReqContext) respons defer span.End() uid := web.Params(c.Req)[":uid"] + if err := util.ValidateUID(uid); err != nil { return response.ErrOrFallback(http.StatusBadRequest, "invalid session uid", err) } - ss, err := cma.cloudMigrationService.CreateSnapshot(ctx, uid) + ss, err := cma.cloudMigrationService.CreateSnapshot(ctx, c.SignedInUser, uid) if err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "error creating snapshot", err) } diff --git a/pkg/services/cloudmigration/api/curl_commands.txt b/pkg/services/cloudmigration/api/curl_commands.txt index f4556aae0e1..925df467c31 100644 --- a/pkg/services/cloudmigration/api/curl_commands.txt +++ b/pkg/services/cloudmigration/api/curl_commands.txt @@ -1,7 +1,7 @@ [sample token] // NOT A REAL TOKEN eyJUb2tlbiI6ImNvbXBsZXRlbHlfZmFrZV90b2tlbl9jZG9peTFhYzdwdXlwZCIsIkluc3RhbmNlIjp7IlN0YWNrSUQiOjEyMzQ1LCJTbHVnIjoic3R1Ymluc3RhbmNlIiwiUmVnaW9uU2x1ZyI6ImZha2UtcmVnaW9uIiwiQ2x1c3RlclNsdWciOiJmYWtlLWNsdXNlciJ9fQ== -[create session} +[create session] curl -X POST -H "Content-Type: application/json" \ http://admin:admin@localhost:3000/api/cloudmigration/migration \ -d '{"AuthToken":"eyJUb2tlbiI6ImNvbXBsZXRlbHlfZmFrZV90b2tlbl9jZG9peTFhYzdwdXlwZCIsIkluc3RhbmNlIjp7IlN0YWNrSUQiOjEyMzQ1LCJTbHVnIjoic3R1Ymluc3RhbmNlIiwiUmVnaW9uU2x1ZyI6ImZha2UtcmVnaW9uIiwiQ2x1c3RlclNsdWciOiJmYWtlLWNsdXNlciJ9fQ=="}' diff --git a/pkg/services/cloudmigration/cloudmigration.go b/pkg/services/cloudmigration/cloudmigration.go index a431ba10f67..5c0d13cbe08 100644 --- a/pkg/services/cloudmigration/cloudmigration.go +++ b/pkg/services/cloudmigration/cloudmigration.go @@ -4,6 +4,7 @@ import ( "context" "github.com/grafana/grafana/pkg/services/gcom" + "github.com/grafana/grafana/pkg/services/user" ) type Service interface { @@ -24,7 +25,7 @@ type Service interface { GetMigrationStatus(ctx context.Context, runUID string) (*CloudMigrationSnapshot, error) GetMigrationRunList(ctx context.Context, migUID string) (*CloudMigrationRunList, error) - CreateSnapshot(ctx context.Context, sessionUid string) (*CloudMigrationSnapshot, error) + CreateSnapshot(ctx context.Context, signedInUser *user.SignedInUser, sessionUid string) (*CloudMigrationSnapshot, error) GetSnapshot(ctx context.Context, query GetSnapshotsQuery) (*CloudMigrationSnapshot, error) GetSnapshotList(ctx context.Context, query ListSnapshotsQuery) ([]CloudMigrationSnapshot, error) UploadSnapshot(ctx context.Context, sessionUid string, snapshotUid string) error diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go index 3b631d84bd9..7b085ea15d8 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "net/http" - "os" "path/filepath" "sync" "time" @@ -26,6 +25,7 @@ import ( "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/gcom" "github.com/grafana/grafana/pkg/services/secrets" + "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" "github.com/prometheus/client_golang/prometheus" @@ -41,7 +41,6 @@ type Service struct { cfg *setting.Cfg buildSnapshotMutex sync.Mutex - buildSnapshotError bool features featuremgmt.FeatureToggles gmsClient gmsclient.Client @@ -391,7 +390,7 @@ func (s *Service) RunMigration(ctx context.Context, uid string) (*cloudmigration } // Get migration data JSON - request, err := s.getMigrationDataJSON(ctx) + request, err := s.getMigrationDataJSON(ctx, &user.SignedInUser{}) if err != nil { s.log.Error("error getting the json request body for migration run", "err", err.Error()) return nil, fmt.Errorf("migration data get error: %w", err) @@ -459,8 +458,10 @@ func (s *Service) DeleteSession(ctx context.Context, uid string) (*cloudmigratio return c, nil } -func (s *Service) CreateSnapshot(ctx context.Context, sessionUid string) (*cloudmigration.CloudMigrationSnapshot, error) { - ctx, span := s.tracer.Start(ctx, "CloudMigrationService.CreateSnapshot") +func (s *Service) CreateSnapshot(ctx context.Context, signedInUser *user.SignedInUser, sessionUid string) (*cloudmigration.CloudMigrationSnapshot, error) { + ctx, span := s.tracer.Start(ctx, "CloudMigrationService.CreateSnapshot", trace.WithAttributes( + attribute.String("sessionUid", sessionUid), + )) defer span.End() // fetch session for the gms auth token @@ -470,28 +471,25 @@ func (s *Service) CreateSnapshot(ctx context.Context, sessionUid string) (*cloud } // query gms to establish new snapshot - initResp, err := s.gmsClient.InitializeSnapshot(ctx, *session) + timeoutCtx, cancel := context.WithTimeout(ctx, s.cfg.CloudMigration.StartSnapshotTimeout) + defer cancel() + initResp, err := s.gmsClient.StartSnapshot(timeoutCtx, *session) if err != nil { return nil, fmt.Errorf("initializing snapshot with GMS for session %s: %w", sessionUid, err) } - // create new directory for snapshot writing - snapshotUid := util.GenerateShortUID() - dir := filepath.Join("cloudmigration.snapshots", fmt.Sprintf("snapshot-%s-%s", snapshotUid, initResp.GMSSnapshotUID)) - err = os.MkdirAll(dir, 0750) - if err != nil { - return nil, fmt.Errorf("creating snapshot directory: %w", err) + if s.cfg.CloudMigration.SnapshotFolder == "" { + return nil, fmt.Errorf("snapshot folder is not set") } - // save snapshot to the db snapshot := cloudmigration.CloudMigrationSnapshot{ - UID: snapshotUid, + UID: util.GenerateShortUID(), SessionUID: sessionUid, Status: cloudmigration.SnapshotStatusInitializing, EncryptionKey: initResp.EncryptionKey, UploadURL: initResp.UploadURL, - GMSSnapshotUID: initResp.GMSSnapshotUID, - LocalDir: dir, + GMSSnapshotUID: initResp.SnapshotID, + LocalDir: filepath.Join(s.cfg.CloudMigration.SnapshotFolder, "grafana", "snapshots", initResp.SnapshotID), } uid, err := s.store.CreateSnapshot(ctx, snapshot) @@ -501,7 +499,11 @@ func (s *Service) CreateSnapshot(ctx context.Context, sessionUid string) (*cloud snapshot.UID = uid // start building the snapshot asynchronously while we return a success response to the client - go s.buildSnapshot(context.Background(), snapshot) + go func() { + if err := s.buildSnapshot(context.Background(), signedInUser, initResp.MaxItemsPerPartition, snapshot); err != nil { + s.log.Error("building snapshot", "err", err.Error()) + } + }() return &snapshot, nil } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_noop.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_noop.go index d435b9f1ba8..1027f1254f4 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_noop.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_noop.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/services/cloudmigration" "github.com/grafana/grafana/pkg/services/gcom" + "github.com/grafana/grafana/pkg/services/user" ) // NoopServiceImpl Define the Service Implementation. @@ -60,7 +61,7 @@ func (s *NoopServiceImpl) RunMigration(context.Context, string) (*cloudmigration return nil, cloudmigration.ErrFeatureDisabledError } -func (s *NoopServiceImpl) CreateSnapshot(ctx context.Context, sessionUid string) (*cloudmigration.CloudMigrationSnapshot, error) { +func (s *NoopServiceImpl) CreateSnapshot(ctx context.Context, user *user.SignedInUser, sessionUid string) (*cloudmigration.CloudMigrationSnapshot, error) { return nil, cloudmigration.ErrFeatureDisabledError } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go index 04ac1ff3217..eef78c9401f 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go @@ -2,8 +2,11 @@ package cloudmigrationimpl import ( "context" + "os" + "path/filepath" "testing" + "github.com/google/uuid" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" @@ -109,73 +112,6 @@ func Test_CreateGetRunMigrationsAndRuns(t *testing.T) { require.NotNil(t, createResp.UID, delMigResp.UID) } -func Test_ExecuteAsyncWorkflow(t *testing.T) { - s := setUpServiceTest(t, false) - - createTokenResp, err := s.CreateToken(context.Background()) - assert.NoError(t, err) - assert.NotEmpty(t, createTokenResp.Token) - - cmd := cloudmigration.CloudMigrationSessionRequest{ - AuthToken: createTokenResp.Token, - } - - createResp, err := s.CreateSession(context.Background(), cmd) - require.NoError(t, err) - require.NotEmpty(t, createResp.UID) - require.NotEmpty(t, createResp.Slug) - - getSessionResp, err := s.GetSession(context.Background(), createResp.UID) - require.NoError(t, err) - require.NotNil(t, getSessionResp) - require.Equal(t, createResp.UID, getSessionResp.UID) - require.Equal(t, createResp.Slug, getSessionResp.Slug) - - listResp, err := s.GetSessionList(context.Background()) - require.NoError(t, err) - require.NotNil(t, listResp) - require.Equal(t, 1, len(listResp.Sessions)) - require.Equal(t, createResp.UID, listResp.Sessions[0].UID) - require.Equal(t, createResp.Slug, listResp.Sessions[0].Slug) - - sessionUid := createResp.UID - snapshotResp, err := s.CreateSnapshot(ctxWithSignedInUser(), sessionUid) - require.NoError(t, err) - require.NotEmpty(t, snapshotResp.UID) - require.Equal(t, sessionUid, snapshotResp.SessionUID) - snapshotUid := snapshotResp.UID - - // Service doesn't currently expose updating a snapshot externally, so we will just manually add a resource - err = (s.(*Service)).store.CreateUpdateSnapshotResources(context.Background(), snapshotUid, []cloudmigration.CloudMigrationResource{{Type: cloudmigration.DashboardDataType, RefID: "qwerty", Status: cloudmigration.ItemStatusOK}}) - assert.NoError(t, err) - - snapshot, err := s.GetSnapshot(ctxWithSignedInUser(), cloudmigration.GetSnapshotsQuery{ - SnapshotUID: snapshotUid, - SessionUID: sessionUid, - ResultPage: 1, - ResultLimit: 100, - }) - require.NoError(t, err) - assert.Equal(t, snapshotResp.UID, snapshot.UID) - assert.Equal(t, snapshotResp.EncryptionKey, snapshot.EncryptionKey) - assert.Len(t, snapshot.Resources, 1) - assert.Equal(t, "qwerty", snapshot.Resources[0].RefID) - - snapshots, err := s.GetSnapshotList(ctxWithSignedInUser(), cloudmigration.ListSnapshotsQuery{SessionUID: sessionUid, Page: 1, Limit: 100}) - require.NoError(t, err) - assert.Len(t, snapshots, 1) - assert.Equal(t, snapshotResp.UID, snapshots[0].UID) - assert.Equal(t, snapshotResp.EncryptionKey, snapshots[0].EncryptionKey) - assert.Empty(t, snapshots[0].Resources) - - err = s.UploadSnapshot(ctxWithSignedInUser(), sessionUid, snapshotUid) - require.NoError(t, err) - - assert.Panics(t, func() { - err = s.CancelSnapshot(ctxWithSignedInUser(), sessionUid, snapshotUid) - }) -} - func ctxWithSignedInUser() context.Context { c := &contextmodel.ReqContext{ SignedInUser: &user.SignedInUser{OrgID: 1}, @@ -202,6 +138,7 @@ func setUpServiceTest(t *testing.T, withDashboardMock bool) cloudmigration.Servi require.NoError(t, err) // dont know if this is the best, but dont want to refactor at the moment cfg.CloudMigration.IsDeveloperMode = true + cfg.CloudMigration.SnapshotFolder = filepath.Join(os.TempDir(), uuid.NewString()) dashboardService := dashboards.NewFakeDashboardService(t) if withDashboardMock { diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go b/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go index 2de9080c4f0..9192ebd99b5 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/fake/cloudmigration_fake.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/services/cloudmigration" "github.com/grafana/grafana/pkg/services/gcom" + "github.com/grafana/grafana/pkg/services/user" ) var fixedDate = time.Date(2024, 6, 5, 17, 30, 40, 0, time.UTC) @@ -129,7 +130,7 @@ func (m FakeServiceImpl) GetMigrationRunList(_ context.Context, _ string) (*clou }, nil } -func (m FakeServiceImpl) CreateSnapshot(ctx context.Context, sessionUid string) (*cloudmigration.CloudMigrationSnapshot, error) { +func (m FakeServiceImpl) CreateSnapshot(ctx context.Context, user *user.SignedInUser, sessionUid string) (*cloudmigration.CloudMigrationSnapshot, error) { if m.ReturnError { return nil, fmt.Errorf("mock error") } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go index 0d43a3c7127..fad2fba3d2e 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go @@ -2,17 +2,24 @@ package cloudmigrationimpl import ( "context" + cryptoRand "crypto/rand" + "fmt" "time" + snapshot "github.com/grafana/grafana-cloud-migration-snapshot/src" + "github.com/grafana/grafana-cloud-migration-snapshot/src/contracts" + "github.com/grafana/grafana-cloud-migration-snapshot/src/infra/crypto" "github.com/grafana/grafana/pkg/services/cloudmigration" - "github.com/grafana/grafana/pkg/services/contexthandler" + "github.com/grafana/grafana/pkg/services/cloudmigration/slicesext" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util/retryer" + "golang.org/x/crypto/nacl/box" ) -func (s *Service) getMigrationDataJSON(ctx context.Context) (*cloudmigration.MigrateDataRequest, error) { +func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.SignedInUser) (*cloudmigration.MigrateDataRequest, error) { // Data sources dataSources, err := s.getDataSources(ctx) if err != nil { @@ -28,7 +35,7 @@ func (s *Service) getMigrationDataJSON(ctx context.Context) (*cloudmigration.Mig } // Folders - folders, err := s.getFolders(ctx) + folders, err := s.getFolders(ctx, signedInUser) if err != nil { s.log.Error("Failed to get folders", "err", err) return nil, err @@ -111,10 +118,9 @@ func (s *Service) getDataSources(ctx context.Context) ([]datasources.AddDataSour return result, err } -func (s *Service) getFolders(ctx context.Context) ([]folder.Folder, error) { - reqCtx := contexthandler.FromContext(ctx) +func (s *Service) getFolders(ctx context.Context, signedInUser *user.SignedInUser) ([]folder.Folder, error) { folders, err := s.folderService.GetFolders(ctx, folder.GetFoldersQuery{ - SignedInUser: reqCtx.SignedInUser, + SignedInUser: signedInUser, }) if err != nil { return nil, err @@ -143,11 +149,10 @@ func (s *Service) getDashboards(ctx context.Context) ([]dashboards.Dashboard, er } // asynchronous process for writing the snapshot to the filesystem and updating the snapshot status -func (s *Service) buildSnapshot(ctx context.Context, snapshotMeta cloudmigration.CloudMigrationSnapshot) { +func (s *Service) buildSnapshot(ctx context.Context, signedInUser *user.SignedInUser, maxItemsPerPartition uint32, snapshotMeta cloudmigration.CloudMigrationSnapshot) error { // TODO -- make sure we can only build one snapshot at a time s.buildSnapshotMutex.Lock() defer s.buildSnapshotMutex.Unlock() - s.buildSnapshotError = false // update snapshot status to creating, add some retries since this is a background task if err := retryer.Retry(func() (retryer.RetrySignal, error) { @@ -158,18 +163,60 @@ func (s *Service) buildSnapshot(ctx context.Context, snapshotMeta cloudmigration return retryer.FuncComplete, err }, 10, time.Millisecond*100, time.Second*10); err != nil { s.log.Error("failed to set snapshot status to 'creating'", "err", err) - s.buildSnapshotError = true - return + return fmt.Errorf("setting snapshot status to creating: snapshotUID=%s %w", snapshotMeta.UID, err) } - // build snapshot - // just sleep for now to simulate snapshot creation happening - // need to do a couple of fancy things when we implement this: - // - some sort of regular check-in so we know we haven't timed out - // - a channel to listen for cancel events - // - retries baked into the snapshot writing process? - s.log.Debug("snapshot meta", "snapshot", snapshotMeta) - time.Sleep(3 * time.Second) + publicKey, privateKey, err := box.GenerateKey(cryptoRand.Reader) + if err != nil { + return fmt.Errorf("nacl: generating public and private key: %w", err) + } + + // Use GMS public key + the grafana generated private private key to encrypt snapshot files. + snapshotWriter, err := snapshot.NewSnapshotWriter(contracts.AssymetricKeys{ + Public: []byte(snapshotMeta.EncryptionKey), + Private: privateKey[:], + }, + crypto.NewNacl(), + snapshotMeta.LocalDir, + ) + if err != nil { + return fmt.Errorf("instantiating snapshot writer: %w", err) + } + + migrationData, err := s.getMigrationDataJSON(ctx, signedInUser) + if err != nil { + return fmt.Errorf("fetching migration data: %w", err) + } + + resourcesGroupedByType := make(map[cloudmigration.MigrateDataType][]snapshot.MigrateDataRequestItemDTO, 0) + for _, item := range migrationData.Items { + resourcesGroupedByType[item.Type] = append(resourcesGroupedByType[item.Type], snapshot.MigrateDataRequestItemDTO{ + Type: snapshot.MigrateDataType(item.Type), + RefID: item.RefID, + Name: item.Name, + Data: item.Data, + }) + } + + for _, resourceType := range []cloudmigration.MigrateDataType{ + cloudmigration.DatasourceDataType, + cloudmigration.FolderDataType, + cloudmigration.DashboardDataType, + } { + for _, chunk := range slicesext.Chunks(int(maxItemsPerPartition), resourcesGroupedByType[resourceType]) { + if err := snapshotWriter.Write(string(resourceType), chunk); err != nil { + return fmt.Errorf("writing resources to snapshot writer: resourceType=%s %w", resourceType, err) + } + } + } + + // Add the grafana generated public key to the index file so gms can use it to decrypt the snapshot files later. + // This works because the snapshot files are being encrypted with + // the grafana generated private key + the gms public key. + _, err = snapshotWriter.Finish(publicKey[:]) + if err != nil { + return fmt.Errorf("finishing writing snapshot files and generating index file: %w", err) + } // update snapshot status to pending upload with retry if err := retryer.Retry(func() (retryer.RetrySignal, error) { @@ -180,8 +227,10 @@ func (s *Service) buildSnapshot(ctx context.Context, snapshotMeta cloudmigration return retryer.FuncComplete, err }, 10, time.Millisecond*100, time.Second*10); err != nil { s.log.Error("failed to set snapshot status to 'pending upload'", "err", err) - s.buildSnapshotError = true + return fmt.Errorf("setting snapshot status to pending upload: snapshotID=%s %w", snapshotMeta.UID, err) } + + return nil } // asynchronous process for and updating the snapshot status @@ -189,7 +238,6 @@ func (s *Service) uploadSnapshot(ctx context.Context, snapshotMeta cloudmigratio // TODO -- make sure we can only upload one snapshot at a time s.buildSnapshotMutex.Lock() defer s.buildSnapshotMutex.Unlock() - s.buildSnapshotError = false // update snapshot status to uploading, add some retries since this is a background task if err := retryer.Retry(func() (retryer.RetrySignal, error) { @@ -200,7 +248,6 @@ func (s *Service) uploadSnapshot(ctx context.Context, snapshotMeta cloudmigratio return retryer.FuncComplete, err }, 10, time.Millisecond*100, time.Second*10); err != nil { s.log.Error("failed to set snapshot status to 'creating'", "err", err) - s.buildSnapshotError = true return } @@ -218,7 +265,6 @@ func (s *Service) uploadSnapshot(ctx context.Context, snapshotMeta cloudmigratio return retryer.FuncComplete, err }, 10, time.Millisecond*100, time.Second*10); err != nil { s.log.Error("failed to set snapshot status to 'pending upload'", "err", err) - s.buildSnapshotError = true } // simulate the rest diff --git a/pkg/services/cloudmigration/gmsclient/client.go b/pkg/services/cloudmigration/gmsclient/client.go index 6f13a60acf3..6e0fb157ccc 100644 --- a/pkg/services/cloudmigration/gmsclient/client.go +++ b/pkg/services/cloudmigration/gmsclient/client.go @@ -9,7 +9,7 @@ import ( type Client interface { ValidateKey(context.Context, cloudmigration.CloudMigrationSession) error MigrateData(context.Context, cloudmigration.CloudMigrationSession, cloudmigration.MigrateDataRequest) (*cloudmigration.MigrateDataResponse, error) - InitializeSnapshot(context.Context, cloudmigration.CloudMigrationSession) (*cloudmigration.InitializeSnapshotResponse, error) + StartSnapshot(context.Context, cloudmigration.CloudMigrationSession) (*cloudmigration.StartSnapshotResponse, error) GetSnapshotStatus(context.Context, cloudmigration.CloudMigrationSession, cloudmigration.CloudMigrationSnapshot) (*cloudmigration.CloudMigrationSnapshot, error) } diff --git a/pkg/services/cloudmigration/gmsclient/gms_client.go b/pkg/services/cloudmigration/gmsclient/gms_client.go index 383fcabb8e2..4d4e6cc41b2 100644 --- a/pkg/services/cloudmigration/gmsclient/gms_client.go +++ b/pkg/services/cloudmigration/gmsclient/gms_client.go @@ -111,8 +111,43 @@ func (c *gmsClientImpl) MigrateData(ctx context.Context, cm cloudmigration.Cloud return &result, nil } -func (c *gmsClientImpl) InitializeSnapshot(context.Context, cloudmigration.CloudMigrationSession) (*cloudmigration.InitializeSnapshotResponse, error) { - panic("not implemented") +func (c *gmsClientImpl) StartSnapshot(ctx context.Context, session cloudmigration.CloudMigrationSession) (*cloudmigration.StartSnapshotResponse, error) { + logger := c.log.FromContext(ctx) + + path := fmt.Sprintf("https://cms-%s.%s/cloud-migrations/api/v1/start-snapshot", session.ClusterSlug, c.domain) + + // Send the request to cms with the associated auth token + req, err := http.NewRequest(http.MethodPost, path, nil) + if err != nil { + c.log.Error("error creating http request to start snapshot", "err", err.Error()) + return nil, fmt.Errorf("http request error: %w", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %d:%s", session.StackID, session.AuthToken)) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + c.log.Error("error sending http request to start snapshot", "err", err.Error()) + return nil, fmt.Errorf("http request error: %w", err) + } else if resp.StatusCode >= 400 { + c.log.Error("received error response to start snapshot", "statusCode", resp.StatusCode) + return nil, fmt.Errorf("http request error: %w", err) + } + + defer func() { + if err := resp.Body.Close(); err != nil { + logger.Error("closing request body: %w", err) + } + }() + + var result cloudmigration.StartSnapshotResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + logger.Error("unmarshalling response body: %w", err) + return nil, fmt.Errorf("unmarshalling start snapshot response: %w", err) + } + + return &result, nil } func (c *gmsClientImpl) GetSnapshotStatus(context.Context, cloudmigration.CloudMigrationSession, cloudmigration.CloudMigrationSnapshot) (*cloudmigration.CloudMigrationSnapshot, error) { diff --git a/pkg/services/cloudmigration/gmsclient/inmemory_client.go b/pkg/services/cloudmigration/gmsclient/inmemory_client.go index 3148fb109f1..c7b9b9abb87 100644 --- a/pkg/services/cloudmigration/gmsclient/inmemory_client.go +++ b/pkg/services/cloudmigration/gmsclient/inmemory_client.go @@ -15,7 +15,7 @@ func NewInMemoryClient() Client { } type memoryClientImpl struct { - snapshot *cloudmigration.InitializeSnapshotResponse + snapshot *cloudmigration.StartSnapshotResponse } func (c *memoryClientImpl) ValidateKey(ctx context.Context, cm cloudmigration.CloudMigrationSession) error { @@ -48,11 +48,11 @@ func (c *memoryClientImpl) MigrateData( return &result, nil } -func (c *memoryClientImpl) InitializeSnapshot(context.Context, cloudmigration.CloudMigrationSession) (*cloudmigration.InitializeSnapshotResponse, error) { - c.snapshot = &cloudmigration.InitializeSnapshotResponse{ - EncryptionKey: util.GenerateShortUID(), - GMSSnapshotUID: util.GenerateShortUID(), - UploadURL: "localhost:3000", +func (c *memoryClientImpl) StartSnapshot(context.Context, cloudmigration.CloudMigrationSession) (*cloudmigration.StartSnapshotResponse, error) { + c.snapshot = &cloudmigration.StartSnapshotResponse{ + EncryptionKey: util.GenerateShortUID(), + SnapshotID: util.GenerateShortUID(), + UploadURL: "localhost:3000", } return c.snapshot, nil diff --git a/pkg/services/cloudmigration/model.go b/pkg/services/cloudmigration/model.go index 40904dd4d93..fe257f07c50 100644 --- a/pkg/services/cloudmigration/model.go +++ b/pkg/services/cloudmigration/model.go @@ -195,8 +195,12 @@ type CreateSessionResponse struct { SnapshotUid string } -type InitializeSnapshotResponse struct { - EncryptionKey string - UploadURL string - GMSSnapshotUID string +type StartSnapshotResponse struct { + SnapshotID string `json:"snapshotID"` + MaxItemsPerPartition uint32 `json:"maxItemsPerPartition"` + Algo string `json:"algo"` + UploadURL string `json:"uploadURL"` + PresignedURLFormData map[string]string `json:"presignedURLFormData"` + EncryptionKey string `json:"encryptionKey"` + Nonce string `json:"nonce"` } diff --git a/pkg/services/cloudmigration/slicesext/slicesext.go b/pkg/services/cloudmigration/slicesext/slicesext.go new file mode 100644 index 00000000000..66d9fb6b6cf --- /dev/null +++ b/pkg/services/cloudmigration/slicesext/slicesext.go @@ -0,0 +1,33 @@ +package slicesext + +import "math" + +// Partitions the input into slices where the length is <= chunkSize. +// +// Example: +// +// Chunks(2, []int{1, 2, 3, 4}) +// => [][]int{{1, 2}, {3, 4}} +func Chunks[T any](chunkSize int, xs []T) [][]T { + if chunkSize < 0 { + panic("chunk size must be greater than or equal to 0") + } + if chunkSize == 0 { + return [][]T{} + } + + out := make([][]T, 0, int(math.Ceil(float64(len(xs))/float64(chunkSize)))) + + for i := 0; i < len(xs); i += chunkSize { + var chunk []T + if i+chunkSize < len(xs) { + chunk = xs[i : i+chunkSize] + } else { + chunk = xs[i:] + } + + out = append(out, chunk) + } + + return out +} diff --git a/pkg/services/cloudmigration/slicesext/slicesext_test.go b/pkg/services/cloudmigration/slicesext/slicesext_test.go new file mode 100644 index 00000000000..150f92b004e --- /dev/null +++ b/pkg/services/cloudmigration/slicesext/slicesext_test.go @@ -0,0 +1,80 @@ +package slicesext + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestChunks(t *testing.T) { + t.Parallel() + + t.Run("chunkSize must be greater than 0", func(t *testing.T) { + t.Parallel() + + assert.PanicsWithValue(t, "chunk size must be greater than or equal to 0", func() { + Chunks(-1, []string{}) + }) + }) + + t.Run("basic", func(t *testing.T) { + t.Parallel() + + cases := []struct { + description string + chunkSize int + input []int + expected [][]int + }{ + { + description: "empty slice", + chunkSize: 2, + input: []int{}, + expected: [][]int{}, + }, + { + description: "nil slice", + chunkSize: 2, + input: nil, + expected: [][]int{}, + }, + { + description: "chunk size is 0", + chunkSize: 0, + input: []int{1, 2, 3}, + expected: [][]int{}, + }, + { + description: "chunk size is greater than slice length", + chunkSize: 3, + input: []int{1}, + expected: [][]int{{1}}, + }, + { + description: "chunk size is 1", + chunkSize: 1, + input: []int{1, 2, 3}, + expected: [][]int{{1}, {2}, {3}}, + }, + { + description: "chunk size is 2 and slice length is 3", + chunkSize: 2, + input: []int{1, 2, 3}, + expected: [][]int{{1, 2}, {3}}, + }, + { + description: "chunk size is 2 and slice length is 6", + chunkSize: 2, + input: []int{1, 2, 3, 4, 5, 6}, + expected: [][]int{{1, 2}, {3, 4}, {5, 6}}, + }, + } + + for _, tt := range cases { + t.Run(tt.description, func(t *testing.T) { + result := Chunks(tt.chunkSize, tt.input) + assert.Equal(t, tt.expected, result) + }) + } + }) +} diff --git a/pkg/setting/setting_cloud_migration.go b/pkg/setting/setting_cloud_migration.go index 3c68147a5ec..67d295d34b8 100644 --- a/pkg/setting/setting_cloud_migration.go +++ b/pkg/setting/setting_cloud_migration.go @@ -1,12 +1,15 @@ package setting import ( + "os" "time" ) type CloudMigrationSettings struct { IsTarget bool GcomAPIToken string + SnapshotFolder string + StartSnapshotTimeout time.Duration FetchInstanceTimeout time.Duration CreateAccessPolicyTimeout time.Duration FetchAccessPolicyTimeout time.Duration @@ -23,6 +26,8 @@ func (cfg *Cfg) readCloudMigrationSettings() { cloudMigration := cfg.Raw.Section("cloud_migration") cfg.CloudMigration.IsTarget = cloudMigration.Key("is_target").MustBool(false) cfg.CloudMigration.GcomAPIToken = cloudMigration.Key("gcom_api_token").MustString("") + cfg.CloudMigration.SnapshotFolder = cloudMigration.Key("snapshot_folder").MustString("") + cfg.CloudMigration.StartSnapshotTimeout = cloudMigration.Key("start_snapshot_timeout").MustDuration(5 * time.Second) cfg.CloudMigration.FetchInstanceTimeout = cloudMigration.Key("fetch_instance_timeout").MustDuration(5 * time.Second) cfg.CloudMigration.CreateAccessPolicyTimeout = cloudMigration.Key("create_access_policy_timeout").MustDuration(5 * time.Second) cfg.CloudMigration.FetchAccessPolicyTimeout = cloudMigration.Key("fetch_access_policy_timeout").MustDuration(5 * time.Second) @@ -32,4 +37,9 @@ func (cfg *Cfg) readCloudMigrationSettings() { cfg.CloudMigration.DeleteTokenTimeout = cloudMigration.Key("delete_token_timeout").MustDuration(5 * time.Second) cfg.CloudMigration.TokenExpiresAfter = cloudMigration.Key("token_expires_after").MustDuration(7 * 24 * time.Hour) cfg.CloudMigration.IsDeveloperMode = cloudMigration.Key("developer_mode").MustBool(false) + + if cfg.CloudMigration.SnapshotFolder == "" { + homeDir, _ := os.UserHomeDir() + cfg.CloudMigration.SnapshotFolder = homeDir + } } From f659bc1f400dff35884dcb6d5586361b962d7544 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 3 Jul 2024 16:00:45 +0200 Subject: [PATCH 14/39] Dashboard: Allow disabling dashboard grid lazy loading (#89280) * Schema update * Dashboard: Allow opting out from dashboard panels lazy loading * Locale * Lint fix * Snaps fix --- kinds/dashboard/dashboard_kind.cue | 3 + .../raw/dashboard/x/dashboard_types.gen.ts | 4 + pkg/kinds/dashboard/dashboard_spec_gen.go | 3 + .../pages/DashboardScenePageStateManager.ts | 79 +++++++++++-------- .../PanelDataAlertingTab.test.tsx | 4 +- .../dashboard-scene/scene/DashboardScene.tsx | 4 +- .../transformSceneToSaveModel.test.ts.snap | 3 + .../transformSaveModelToScene.test.ts | 25 ++++-- .../transformSaveModelToScene.ts | 9 ++- .../transformSceneToSaveModel.ts | 1 + .../settings/GeneralSettingsEditView.tsx | 19 +++++ .../HelpWizard/SupportSnapshotService.ts | 2 +- public/locales/en-US/grafana.json | 2 + public/locales/pseudo-LOCALE/grafana.json | 2 + 14 files changed, 114 insertions(+), 46 deletions(-) diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index 04e9d4f1322..58ae9457718 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -99,6 +99,9 @@ lineage: schemas: [{ // Snapshot options. They are present only if the dashboard is a snapshot. snapshot?: #Snapshot @grafanamaturity(NeedsExpertReview) + + // When set to true, the dashboard will load all panels in the dashboard when it's loaded. + preload?: bool } @cuetsy(kind="interface") @grafana(TSVeneer="type") /////////////////////////////////////// diff --git a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts index 46e58446be2..784b96494f4 100644 --- a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts +++ b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts @@ -1062,6 +1062,10 @@ export interface Dashboard { * List of dashboard panels */ panels?: Array<(Panel | RowPanel)>; + /** + * When set to true, the dashboard will load all panels in the dashboard when it's loaded. + */ + preload?: boolean; /** * Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d". */ diff --git a/pkg/kinds/dashboard/dashboard_spec_gen.go b/pkg/kinds/dashboard/dashboard_spec_gen.go index ab33a177135..889dd767c9b 100644 --- a/pkg/kinds/dashboard/dashboard_spec_gen.go +++ b/pkg/kinds/dashboard/dashboard_spec_gen.go @@ -753,6 +753,9 @@ type Spec struct { // List of dashboard panels Panels []any `json:"panels,omitempty"` + // When set to true, the dashboard will load all panels in the dashboard when it's loaded. + Preload *bool `json:"preload,omitempty"` + // Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d". Refresh *string `json:"refresh,omitempty"` diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index b55f281e4a8..201773bd2f6 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -322,40 +322,57 @@ export function getDashboardScenePageStateManager(): DashboardScenePageStateMana } function getErrorScene(msg: string) { - return createDashboardSceneFromDashboardModel( - new DashboardModel( - { - ...defaultDashboard, - title: msg, - panels: [ + const dto: DashboardDTO = { + dashboard: { + ...defaultDashboard, + uid: 'error-dash', + title: msg, + annotations: { + list: [ { - fieldConfig: { - defaults: {}, - overrides: [], + builtIn: 1, + datasource: { + type: 'grafana', + uid: '-- Grafana --', }, - gridPos: { - h: 6, - w: 12, - x: 7, - y: 0, - }, - id: 1, - options: { - code: { - language: 'plaintext', - showLineNumbers: false, - showMiniMap: false, - }, - content: `

${msg}

`, - mode: 'html', - }, - title: '', - transparent: true, - type: 'text', + enable: false, + hide: true, + iconColor: 'rgba(0, 211, 255, 1)', + name: 'Annotations & Alerts', + type: 'dashboard', }, ], }, - { canSave: false, canEdit: false } - ) - ); + + panels: [ + { + fieldConfig: { + defaults: {}, + overrides: [], + }, + gridPos: { + h: 6, + w: 12, + x: 7, + y: 0, + }, + id: 1, + options: { + code: { + language: 'plaintext', + showLineNumbers: false, + showMiniMap: false, + }, + content: `

${msg}

`, + mode: 'html', + }, + title: '', + transparent: true, + type: 'text', + }, + ], + }, + meta: { canSave: false, canEdit: false }, + }; + return createDashboardSceneFromDashboardModel(new DashboardModel(dto.dashboard, dto.meta), dto.dashboard); } diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx index e5fabb7f09d..1726c669031 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx @@ -28,7 +28,7 @@ import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { configureStore } from 'app/store/configureStore'; -import { AccessControlAction } from 'app/types'; +import { AccessControlAction, DashboardDataDTO } from 'app/types'; import { AlertQuery, PromRulesResponse } from 'app/types/unified-alerting-dto'; import { createDashboardSceneFromDashboardModel } from '../../serialization/transformSaveModelToScene'; @@ -359,7 +359,7 @@ async function clickNewButton() { } function createModel(dashboard: DashboardModel) { - const scene = createDashboardSceneFromDashboardModel(dashboard); + const scene = createDashboardSceneFromDashboardModel(dashboard, {} as DashboardDataDTO); const vizPanel = findVizPanelByKey(scene, getVizPanelKeyForPanelId(34))!; const model = new PanelDataAlertingTab(VizPanelManager.createFor(vizPanel)); jest.spyOn(utils, 'getDashboardSceneFor').mockReturnValue(scene); diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 2b04bec57de..8c84394c5eb 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -78,7 +78,7 @@ import { ScopesScene } from './Scopes/ScopesScene'; import { ViewPanelScene } from './ViewPanelScene'; import { setupKeyboardShortcuts } from './keyboardShortcuts'; -export const PERSISTED_PROPS = ['title', 'description', 'tags', 'editable', 'graphTooltip', 'links', 'meta']; +export const PERSISTED_PROPS = ['title', 'description', 'tags', 'editable', 'graphTooltip', 'links', 'meta', 'preload']; export interface DashboardSceneState extends SceneObjectState { /** The title */ @@ -91,6 +91,8 @@ export interface DashboardSceneState extends SceneObjectState { links: DashboardLink[]; /** Is editable */ editable?: boolean; + /** Allows disabling grid lazy loading */ + preload?: boolean; /** A uid when saved */ uid?: string; /** @deprecated */ diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap index 48a27c18ea2..9b5c0a119ae 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap @@ -278,6 +278,7 @@ exports[`transformSceneToSaveModel Given a scene with rows Should transform back "type": "row", }, ], + "preload": false, "refresh": "", "schemaVersion": 39, "tags": [ @@ -545,6 +546,7 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho "type": "text", }, ], + "preload": false, "refresh": "5m", "schemaVersion": 39, "tags": [ @@ -902,6 +904,7 @@ exports[`transformSceneToSaveModel Given a simple scene with variables Should tr "type": "text", }, ], + "preload": false, "refresh": "", "schemaVersion": 39, "tags": [ diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index 444676ca936..ea3e0c936f5 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -111,7 +111,7 @@ describe('transformSaveModelToScene', () => { }; const oldModel = new DashboardModel(dash); - const scene = createDashboardSceneFromDashboardModel(oldModel); + const scene = createDashboardSceneFromDashboardModel(oldModel, dash); const dashboardControls = scene.state.controls!; expect(scene.state.title).toBe('test'); @@ -138,11 +138,13 @@ describe('transformSaveModelToScene', () => { it('should apply cursor sync behavior', () => { const dash = { ...defaultDashboard, + title: 'Test dashboard', + uid: 'test-uid', graphTooltip: DashboardCursorSync.Crosshair, }; const oldModel = new DashboardModel(dash); - const scene = createDashboardSceneFromDashboardModel(oldModel); + const scene = createDashboardSceneFromDashboardModel(oldModel, dash); const cursorSync = scene.state.$behaviors?.find((b) => b instanceof behaviors.CursorSync); expect(cursorSync).toBeInstanceOf(behaviors.CursorSync); @@ -150,8 +152,13 @@ describe('transformSaveModelToScene', () => { }); it('should apply live now timer behavior', () => { - const oldModel = new DashboardModel(defaultDashboard); - const scene = createDashboardSceneFromDashboardModel(oldModel); + const dash = { + ...defaultDashboard, + title: 'Test dashboard', + uid: 'test-uid', + }; + const oldModel = new DashboardModel(dash); + const scene = createDashboardSceneFromDashboardModel(oldModel, dash); const liveNowTimer = scene.state.$behaviors?.find((b) => b instanceof behaviors.LiveNowTimer); expect(liveNowTimer).toBeInstanceOf(behaviors.LiveNowTimer); @@ -172,7 +179,7 @@ describe('transformSaveModelToScene', () => { }; const oldModel = new DashboardModel(dash); - const scene = createDashboardSceneFromDashboardModel(oldModel); + const scene = createDashboardSceneFromDashboardModel(oldModel, dash); expect(scene.state.$variables?.state.variables).toBeDefined(); }); }); @@ -212,12 +219,14 @@ describe('transformSaveModelToScene', () => { const dashboard = { ...defaultDashboard, + title: 'Test dashboard', + uid: 'test-uid', panels: [row], }; const oldModel = new DashboardModel(dashboard); - const scene = createDashboardSceneFromDashboardModel(oldModel); + const scene = createDashboardSceneFromDashboardModel(oldModel, dashboard); const body = scene.state.body as SceneGridLayout; expect(body.state.children).toHaveLength(1); @@ -304,12 +313,14 @@ describe('transformSaveModelToScene', () => { const dashboard = { ...defaultDashboard, + title: 'Test dashboard', + uid: 'test-uid', panels: [panelOutOfRow, libPanelOutOfRow, rowWithPanel, panelInRow, libPanelInRow, emptyRow], }; const oldModel = new DashboardModel(dashboard); - const scene = createDashboardSceneFromDashboardModel(oldModel); + const scene = createDashboardSceneFromDashboardModel(oldModel, dashboard); const body = scene.state.body as SceneGridLayout; expect(body.state.children).toHaveLength(4); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 2d76314a1cb..1e11bc51bde 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -30,7 +30,7 @@ import { AdHocFiltersVariable, } from '@grafana/scenes'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; -import { DashboardDTO } from 'app/types'; +import { DashboardDTO, DashboardDataDTO } from 'app/types'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; @@ -74,7 +74,7 @@ export function transformSaveModelToScene(rsp: DashboardDTO): DashboardScene { // Just to have migrations run const oldModel = new DashboardModel(rsp.dashboard, rsp.meta); - const scene = createDashboardSceneFromDashboardModel(oldModel); + const scene = createDashboardSceneFromDashboardModel(oldModel, rsp.dashboard); // TODO: refactor createDashboardSceneFromDashboardModel to work on Dashboard schema model scene.setInitialSaveModel(rsp.dashboard); @@ -190,7 +190,7 @@ function createRowFromPanelModel(row: PanelModel, content: SceneGridItemLike[]): }); } -export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) { +export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, dto: DashboardDataDTO) { let variables: SceneVariableSet | undefined; let annotationLayers: SceneDataLayerProvider[] = []; let alertStatesLayer: AlertStatesDataLayer | undefined; @@ -249,6 +249,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) const dashboardScene = new DashboardScene({ description: oldModel.description, editable: oldModel.editable, + preload: dto.preload ?? false, id: oldModel.id, isDirty: false, links: oldModel.links || [], @@ -258,7 +259,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) uid: oldModel.uid, version: oldModel.version, body: new SceneGridLayout({ - isLazy: true, + isLazy: dto.preload ? false : true, children: createSceneObjectsForPanels(oldModel.panels), $behaviors: [trackIfEmpty], }), diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index e633b7fb0ce..d6bba6aa82f 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -112,6 +112,7 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa uid: state.uid, id: state.id, editable: state.editable, + preload: state.preload, time: { from: timeRange.from, to: timeRange.to, diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx index fafe32472c7..73b1d8cdceb 100644 --- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx @@ -12,6 +12,7 @@ import { Label, RadioButtonGroup, Stack, + Switch, TagsInput, TextArea, } from '@grafana/ui'; @@ -161,6 +162,10 @@ export class GeneralSettingsEditView this.getCursorSync()?.setState({ sync: value }); }; + public onPreloadChange = (preload: boolean) => { + this._dashboard.setState({ preload }); + }; + public onDeleteDashboard = () => {}; static Component = ({ model }: SceneComponentProps) => { @@ -271,6 +276,20 @@ export class GeneralSettingsEditView > + + + model.onPreloadChange(e.currentTarget.checked)} + /> + {meta.canDelete && } diff --git a/public/app/features/dashboard/components/HelpWizard/SupportSnapshotService.ts b/public/app/features/dashboard/components/HelpWizard/SupportSnapshotService.ts index 0a1f9b39f2c..e9f68ad556d 100644 --- a/public/app/features/dashboard/components/HelpWizard/SupportSnapshotService.ts +++ b/public/app/features/dashboard/components/HelpWizard/SupportSnapshotService.ts @@ -83,7 +83,7 @@ export class SupportSnapshotService extends StateManagerBase Date: Wed, 3 Jul 2024 16:21:29 +0200 Subject: [PATCH 15/39] Chore: Cleanup duplicated code in grafana-prometheus package (#89542) * remove redundant test matchers * use amendTable, trimTable functions from @grafana/data package * move getMockDataSource function into the mocks.ts * use LocalStorageValueProvider from @grafana/o11y-ds-frontend * move all mocks under __mocks__ directory * use store from @grafana/o11y-ds-frontend * move test related files under test directory * use getNextRefId from @grafana/data instead of deprecated getNextRefIdChar See: https://github.com/grafana/grafana/pull/87460 * betterer * remove unnecessary mockings * import from @grafana/data * import from @grafana/data --- .betterer.results | 18 -- .../src/components/PromQueryField.tsx | 10 +- .../components/VariableQueryEditor.test.tsx | 2 +- .../src/configuration/PromSettings.test.tsx | 3 +- .../src/configuration/mocks.ts | 15 -- .../grafana-prometheus/src/datasource.test.ts | 16 +- .../LocalStorageValueProvider.tsx | 51 ------ .../LocalStorageValueProvider/index.tsx | 2 - .../src/gcopypaste/app/core/store.ts | 66 -------- .../src/gcopypaste/app/core/utils/query.ts | 21 --- .../datasources/__mocks__/dataSourcesMocks.ts | 31 ---- .../app/features/live/data/amendTimeSeries.ts | 94 ----------- .../gcopypaste/public/test/matchers/index.ts | 11 -- .../public/test/matchers/toEmitValues.test.ts | 141 ---------------- .../public/test/matchers/toEmitValues.ts | 91 ----------- .../test/matchers/toEmitValuesWith.test.ts | 154 ------------------ .../public/test/matchers/toEmitValuesWith.ts | 63 ------- .../gcopypaste/public/test/matchers/types.ts | 14 -- .../gcopypaste/public/test/matchers/utils.ts | 64 -------- .../src/querybuilder/QueryPatternsModal.tsx | 5 +- .../components/LabelFilters.test.tsx | 2 +- .../PromQueryBuilderOptions.test.tsx | 2 +- ...omQueryCodeEditorAutocompleteInfo.test.tsx | 12 -- .../PromQueryEditorSelector.test.tsx | 12 -- .../components/promQail/PromQail.tsx | 3 +- .../src/querybuilder/hooks/useFlag.ts | 2 +- .../src/querybuilder/state.ts | 3 +- .../src/querycache/QueryCache.ts | 4 +- .../src/{ => test}/__mocks__/datasource.ts | 51 +++++- .../test/helpers/selectOptionInTest.ts | 0 30 files changed, 78 insertions(+), 885 deletions(-) delete mode 100644 packages/grafana-prometheus/src/configuration/mocks.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx delete mode 100644 packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/index.tsx delete mode 100644 packages/grafana-prometheus/src/gcopypaste/app/core/store.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/app/core/utils/query.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.test.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.test.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/public/test/matchers/types.ts delete mode 100644 packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts rename packages/grafana-prometheus/src/{ => test}/__mocks__/datasource.ts (71%) rename packages/grafana-prometheus/src/{gcopypaste => }/test/helpers/selectOptionInTest.ts (100%) diff --git a/.betterer.results b/.betterer.results index a3f84ce1d29..089cb0a051a 100644 --- a/.betterer.results +++ b/.betterer.results @@ -455,24 +455,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "4"], [0, 0, 0, "Unexpected any. Specify a different type.", "5"] ], - "packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Do not use any type assertions.", "3"], - [0, 0, 0, "Do not use any type assertions.", "4"] - ], - "packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] - ], - "packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], "packages/grafana-prometheus/src/language_provider.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], diff --git a/packages/grafana-prometheus/src/components/PromQueryField.tsx b/packages/grafana-prometheus/src/components/PromQueryField.tsx index 35ff6174ad3..4c619f88b7a 100644 --- a/packages/grafana-prometheus/src/components/PromQueryField.tsx +++ b/packages/grafana-prometheus/src/components/PromQueryField.tsx @@ -2,13 +2,19 @@ import { css, cx } from '@emotion/css'; import { PureComponent, ReactNode } from 'react'; -import { isDataFrame, QueryEditorProps, QueryHint, TimeRange, toLegacyResponseData } from '@grafana/data'; +import { + isDataFrame, + LocalStorageValueProvider, + QueryEditorProps, + QueryHint, + TimeRange, + toLegacyResponseData, +} from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { reportInteraction } from '@grafana/runtime'; import { clearButtonStyles, Icon, Themeable2, withTheme2 } from '@grafana/ui'; import { PrometheusDatasource } from '../datasource'; -import { LocalStorageValueProvider } from '../gcopypaste/app/core/components/LocalStorageValueProvider'; import { roundMsToMin } from '../language_utils'; import { PromOptions, PromQuery } from '../types'; diff --git a/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx b/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx index 749416c5e14..dd9b17beb3c 100644 --- a/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx +++ b/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx @@ -5,9 +5,9 @@ import userEvent from '@testing-library/user-event'; import { dateTime, TimeRange } from '@grafana/data'; import { PrometheusDatasource } from '../datasource'; -import { selectOptionInTest } from '../gcopypaste/test/helpers/selectOptionInTest'; import PrometheusLanguageProvider from '../language_provider'; import { migrateVariableEditorBackToVariableSupport } from '../migrations/variableMigration'; +import { selectOptionInTest } from '../test/helpers/selectOptionInTest'; import { PromVariableQuery, PromVariableQueryType, StandardPromVariableQuery } from '../types'; import { PromVariableQueryEditor, Props, variableMigration } from './VariableQueryEditor'; diff --git a/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx b/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx index 8d90f39084f..15af11210cc 100644 --- a/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx +++ b/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx @@ -6,8 +6,9 @@ import { SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { config } from '@grafana/runtime'; +import { createDefaultConfigOptions } from '../test/__mocks__/datasource'; + import { countError, getValueFromEventItem, PromSettings } from './PromSettings'; -import { createDefaultConfigOptions } from './mocks'; beforeEach(() => { jest.replaceProperty(config, 'featureToggles', { diff --git a/packages/grafana-prometheus/src/configuration/mocks.ts b/packages/grafana-prometheus/src/configuration/mocks.ts deleted file mode 100644 index 3c1d7e29762..00000000000 --- a/packages/grafana-prometheus/src/configuration/mocks.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/configuration/mocks.ts -import { DataSourceSettings } from '@grafana/data'; - -import { getMockDataSource } from '../gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks'; -import { PromOptions } from '../types'; - -export function createDefaultConfigOptions(): DataSourceSettings { - return getMockDataSource({ - jsonData: { - timeInterval: '1m', - queryTimeout: '1m', - httpMethod: 'GET', - }, - }); -} diff --git a/packages/grafana-prometheus/src/datasource.test.ts b/packages/grafana-prometheus/src/datasource.test.ts index 08104df79b3..8132d670aae 100644 --- a/packages/grafana-prometheus/src/datasource.test.ts +++ b/packages/grafana-prometheus/src/datasource.test.ts @@ -18,14 +18,6 @@ import { } from '@grafana/data'; import { config, getBackendSrv, setBackendSrv, TemplateSrv } from '@grafana/runtime'; -import { - createAnnotationResponse, - createDataRequest, - createDefaultPromResponse, - createEmptyAnnotationResponse, - fetchMockCalledWith, - getMockTimeRange, -} from './__mocks__/datasource'; import { alignRange, extractRuleMappingFromGroups, @@ -34,6 +26,14 @@ import { prometheusSpecialRegexEscape, } from './datasource'; import PromQlLanguageProvider from './language_provider'; +import { + createAnnotationResponse, + createDataRequest, + createDefaultPromResponse, + createEmptyAnnotationResponse, + fetchMockCalledWith, + getMockTimeRange, +} from './test/__mocks__/datasource'; import { PromApplication, PrometheusCacheLevel, PromOptions, PromQuery, PromQueryRequest } from './types'; const fetchMock = jest.fn().mockReturnValue(of(createDefaultPromResponse())); diff --git a/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx b/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx deleted file mode 100644 index 903a6b94b53..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx +++ /dev/null @@ -1,51 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx -import { useEffect, useState } from 'react'; -import * as React from 'react'; - -import store from '../../store'; - -export interface Props { - storageKey: string; - defaultValue: T; - children: (value: T, onSaveToStore: (value: T) => void, onDeleteFromStore: () => void) => React.ReactNode; -} - -export const LocalStorageValueProvider = (props: Props) => { - const { children, storageKey, defaultValue } = props; - - const [state, setState] = useState({ value: store.getObject(props.storageKey, props.defaultValue) }); - - useEffect(() => { - const onStorageUpdate = (v: StorageEvent) => { - if (v.key === storageKey) { - setState({ value: store.getObject(props.storageKey, props.defaultValue) }); - } - }; - - window.addEventListener('storage', onStorageUpdate); - - return () => { - window.removeEventListener('storage', onStorageUpdate); - }; - }); - - const onSaveToStore = (value: T) => { - try { - store.setObject(storageKey, value); - } catch (error) { - console.error(error); - } - setState({ value }); - }; - - const onDeleteFromStore = () => { - try { - store.delete(storageKey); - } catch (error) { - console.log(error); - } - setState({ value: defaultValue }); - }; - - return <>{children(state.value, onSaveToStore, onDeleteFromStore)}; -}; diff --git a/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/index.tsx b/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/index.tsx deleted file mode 100644 index e3df7a01194..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/app/core/components/LocalStorageValueProvider/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/components/LocalStorageValueProvider/index.tsx -export { LocalStorageValueProvider } from './LocalStorageValueProvider'; diff --git a/packages/grafana-prometheus/src/gcopypaste/app/core/store.ts b/packages/grafana-prometheus/src/gcopypaste/app/core/store.ts deleted file mode 100644 index a23d14c93cc..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/app/core/store.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/store.ts -type StoreValue = string | number | boolean | null; - -export class Store { - get(key: string) { - return window.localStorage[key]; - } - - set(key: string, value: StoreValue) { - window.localStorage[key] = value; - } - - getBool(key: string, def: boolean): boolean { - if (def !== void 0 && !this.exists(key)) { - return def; - } - return window.localStorage[key] === 'true'; - } - - getObject(key: string): T | undefined; - getObject(key: string, def: T): T; - getObject(key: string, def?: T) { - let ret = def; - if (this.exists(key)) { - const json = window.localStorage[key]; - try { - ret = JSON.parse(json); - } catch (error) { - console.error(`Error parsing store object: ${key}. Returning default: ${def}. [${error}]`); - } - } - return ret; - } - - /* Returns true when successfully stored, throws error if not successfully stored */ - setObject(key: string, value: unknown) { - let json; - try { - json = JSON.stringify(value); - } catch (error) { - throw new Error(`Could not stringify object: ${key}. [${error}]`); - } - try { - this.set(key, json); - } catch (error) { - // Likely hitting storage quota - const errorToThrow = new Error(`Could not save item in localStorage: ${key}. [${error}]`); - if (error instanceof Error) { - errorToThrow.name = error.name; - } - throw errorToThrow; - } - return true; - } - - exists(key: string) { - return window.localStorage[key] !== void 0; - } - - delete(key: string) { - window.localStorage.removeItem(key); - } -} - -const store = new Store(); -export default store; diff --git a/packages/grafana-prometheus/src/gcopypaste/app/core/utils/query.ts b/packages/grafana-prometheus/src/gcopypaste/app/core/utils/query.ts deleted file mode 100644 index 28b4af48c0b..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/app/core/utils/query.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/core/utils/query.ts -import { DataQuery } from '@grafana/data'; - -export const getNextRefIdChar = (queries: DataQuery[]): string => { - for (let num = 0; ; num++) { - const refId = getRefId(num); - if (!queries.some((query) => query.refId === refId)) { - return refId; - } - } -}; - -function getRefId(num: number): string { - const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - - if (num < letters.length) { - return letters[num]; - } else { - return getRefId(Math.floor(num / letters.length) - 1) + letters[num % letters.length]; - } -} diff --git a/packages/grafana-prometheus/src/gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks.ts b/packages/grafana-prometheus/src/gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks.ts deleted file mode 100644 index 15ebe4229a9..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/app/features/datasources/__mocks__/dataSourcesMocks.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/features/datasources/__mocks__/dataSourcesMocks.ts -import { merge } from 'lodash'; - -import { DataSourceJsonData, DataSourceSettings } from '@grafana/data'; - -export const getMockDataSource = ( - overrides?: Partial> -): DataSourceSettings => - merge( - { - access: '', - basicAuth: false, - basicAuthUser: '', - withCredentials: false, - database: '', - id: 13, - uid: 'x', - isDefault: false, - jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' }, - name: 'gdev-prometheus', - typeName: 'Prometheus', - orgId: 1, - readOnly: false, - type: 'prometheus', - typeLogoUrl: 'packages/grafana-prometheus/src/img/prometheus_logo.svg', - url: '', - user: '', - secureJsonFields: {}, - }, - overrides - ); diff --git a/packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts b/packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts deleted file mode 100644 index a2d00ad34f4..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/app/features/live/data/amendTimeSeries.ts +++ /dev/null @@ -1,94 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/features/live/data/amendTimeSeries.ts -import { closestIdx } from '@grafana/data'; - -export type Table = [times: number[], ...values: any[][]]; - -// prevTable and nextTable are assumed sorted ASC on reference [0] arrays -// nextTable is assumed to be contiguous, only edges are checked for overlap -// ...so prev: [1,2,5] + next: [3,4,6] -> [1,2,3,4,6] -export function amendTable(prevTable: Table, nextTable: Table): Table { - let [prevTimes] = prevTable; - let [nextTimes] = nextTable; - - let pLen = prevTimes.length; - let pStart = prevTimes[0]; - let pEnd = prevTimes[pLen - 1]; - - let nLen = nextTimes.length; - let nStart = nextTimes[0]; - let nEnd = nextTimes[nLen - 1]; - - let outTable: Table; - - if (pLen) { - if (nLen) { - // append, no overlap - if (nStart > pEnd) { - outTable = prevTable.map((_, i) => prevTable[i].concat(nextTable[i])) as Table; - } - // prepend, no overlap - else if (nEnd < pStart) { - outTable = nextTable.map((_, i) => nextTable[i].concat(prevTable[i])) as Table; - } - // full replace - else if (nStart <= pStart && nEnd >= pEnd) { - outTable = nextTable; - } - // partial replace - else if (nStart > pStart && nEnd < pEnd) { - } - // append, with overlap - else if (nStart >= pStart) { - let idx = closestIdx(nStart, prevTimes); - idx = prevTimes[idx] < nStart ? idx - 1 : idx; - outTable = prevTable.map((_, i) => prevTable[i].slice(0, idx).concat(nextTable[i])) as Table; - } - // prepend, with overlap - else if (nEnd >= pStart) { - let idx = closestIdx(nEnd, prevTimes); - idx = prevTimes[idx] < nEnd ? idx : idx + 1; - outTable = nextTable.map((_, i) => nextTable[i].concat(prevTable[i].slice(idx))) as Table; - } - } else { - outTable = prevTable; - } - } else { - if (nLen) { - outTable = nextTable; - } else { - outTable = [[]]; - } - } - - return outTable!; -} - -export function trimTable(table: Table, fromTime: number, toTime: number): Table { - let [times, ...vals] = table; - let fromIdx: number | undefined; - let toIdx: number | undefined; - - // trim to bounds - if (times[0] < fromTime) { - fromIdx = closestIdx(fromTime, times); - - if (times[fromIdx] < fromTime) { - fromIdx++; - } - } - - if (times[times.length - 1] > toTime) { - toIdx = closestIdx(toTime, times); - - if (times[toIdx] > toTime) { - toIdx--; - } - } - - if (fromIdx != null || toIdx != null) { - times = times.slice(fromIdx ?? 0, toIdx); - vals = vals.map(vals2 => vals2.slice(fromIdx ?? 0, toIdx)); - } - - return [times, ...vals]; -} diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts deleted file mode 100644 index ea1578a7482..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/index.ts -import { Observable } from 'rxjs'; - -import { toEmitValues } from './toEmitValues'; -import { toEmitValuesWith } from './toEmitValuesWith'; -import { ObservableMatchers } from './types'; - -export const matchers: ObservableMatchers> = { - toEmitValues, - toEmitValuesWith, -}; diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.test.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.test.ts deleted file mode 100644 index bb0d9aa3c63..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValues.test.ts -import { interval, Observable, of, throwError } from 'rxjs'; -import { map, mergeMap, take } from 'rxjs/operators'; - -import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types'; - -describe('toEmitValues matcher', () => { - describe('failing tests', () => { - describe('passing null in expect', () => { - it('should fail', async () => { - const observable = null as unknown as Observable; - - const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects; - await rejects.toThrow(); - }); - }); - - describe('passing undefined in expect', () => { - it('should fail', async () => { - const observable = undefined as unknown as Observable; - - const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects; - await rejects.toThrow(); - }); - }); - - describe('passing number instead of Observable in expect', () => { - it('should fail', async () => { - const observable = 1 as unknown as Observable; - - const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects; - await rejects.toThrow(); - }); - }); - - describe('wrong number of emitted values', () => { - it('should fail', async () => { - const observable = interval(10).pipe(take(3)); - - const rejects = expect(() => expect(observable).toEmitValues([0, 1])).rejects; - await rejects.toThrow(); - }); - }); - - describe('wrong emitted values', () => { - it('should fail', async () => { - const observable = interval(10).pipe(take(3)); - - const rejects = expect(() => expect(observable).toEmitValues([1, 2, 3])).rejects; - await rejects.toThrow(); - }); - }); - - describe('wrong emitted value types', () => { - it('should fail', async () => { - const observable = interval(10).pipe(take(3)) as unknown as Observable; - - const rejects = expect(() => expect(observable).toEmitValues(['0', '1', '2'])).rejects; - await rejects.toThrow(); - }); - }); - - describe(`observable that does not complete within ${OBSERVABLE_TEST_TIMEOUT_IN_MS}ms`, () => { - it('should fail', async () => { - const observable = interval(600); - - const rejects = expect(() => expect(observable).toEmitValues([0])).rejects; - await rejects.toThrow(); - }); - }); - }); - - describe('passing tests', () => { - describe('correct emitted values', () => { - it('should pass with correct message', async () => { - const observable = interval(10).pipe(take(3)); - await expect(observable).toEmitValues([0, 1, 2]); - }); - }); - - describe('using nested arrays', () => { - it('should pass with correct message', async () => { - const observable = interval(10).pipe( - map((interval) => [{ text: interval.toString(), value: interval }]), - take(3) - ); - await expect(observable).toEmitValues([ - [{ text: '0', value: 0 }], - [{ text: '1', value: 1 }], - [{ text: '2', value: 2 }], - ]); - }); - }); - - describe('using nested objects', () => { - it('should pass with correct message', async () => { - const observable = interval(10).pipe( - map((interval) => ({ inner: { text: interval.toString(), value: interval } })), - take(3) - ); - await expect(observable).toEmitValues([ - { inner: { text: '0', value: 0 } }, - { inner: { text: '1', value: 1 } }, - { inner: { text: '2', value: 2 } }, - ]); - }); - }); - - describe('correct emitted values with throw', () => { - it('should pass with correct message', async () => { - const observable = interval(10).pipe( - map((interval) => { - if (interval > 1) { - throw 'an error'; - } - - return interval; - }) - ) as unknown as Observable; - - await expect(observable).toEmitValues([0, 1, 'an error']); - }); - }); - - describe('correct emitted values with throwError', () => { - it('should pass with correct message', async () => { - const observable = interval(10).pipe( - mergeMap((interval) => { - if (interval === 1) { - return throwError('an error'); - } - - return of(interval); - }) - ) as unknown as Observable; - - await expect(observable).toEmitValues([0, 'an error']); - }); - }); - }); -}); diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.ts deleted file mode 100644 index 5d87c1f342d..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValues.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValues.ts -import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; -import { isEqual } from 'lodash'; -import { Observable, Subscription } from 'rxjs'; - -import { expectObservable, forceObservableCompletion } from './utils'; - -function passMessage(received: unknown[], expected: unknown[]) { - return `${matcherHint('.not.toEmitValues')} - - Expected observable to emit values: - ${printExpected(expected)} - Received: - ${printReceived(received)} - `; -} - -function failMessage(received: unknown[], expected: unknown[]) { - return `${matcherHint('.toEmitValues')} - - Expected observable to emit values: - ${printExpected(expected)} - Received: - ${printReceived(received)} - `; -} - -function tryExpectations(received: unknown[], expected: unknown[]): jest.CustomMatcherResult { - try { - if (received.length !== expected.length) { - return { - pass: false, - message: () => failMessage(received, expected), - }; - } - - for (let index = 0; index < received.length; index++) { - const left = received[index]; - const right = expected[index]; - - if (!isEqual(left, right)) { - return { - pass: false, - message: () => failMessage(received, expected), - }; - } - } - - return { - pass: true, - message: () => passMessage(received, expected), - }; - } catch (err) { - const message = err instanceof Error ? err.message : 'An unknown error occurred'; - return { - pass: false, - message: () => message, - }; - } -} - -export function toEmitValues(received: Observable, expected: unknown[]): Promise { - const failsChecks = expectObservable(received); - if (failsChecks) { - return Promise.resolve(failsChecks); - } - - return new Promise((resolve) => { - const receivedValues: unknown[] = []; - const subscription = new Subscription(); - - subscription.add( - received.subscribe({ - next: (value) => { - receivedValues.push(value); - }, - error: (err) => { - receivedValues.push(err); - subscription.unsubscribe(); - resolve(tryExpectations(receivedValues, expected)); - }, - complete: () => { - subscription.unsubscribe(); - resolve(tryExpectations(receivedValues, expected)); - }, - }) - ); - - forceObservableCompletion(subscription, resolve); - }); -} diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.test.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.test.ts deleted file mode 100644 index 352c8237665..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValuesWith.test.ts -import { interval, Observable, of, throwError } from 'rxjs'; -import { map, mergeMap, take } from 'rxjs/operators'; - -import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types'; - -describe('toEmitValuesWith matcher', () => { - describe('failing tests', () => { - describe('passing null in expect', () => { - it('should fail with correct message', async () => { - const observable = null as unknown as Observable; - - const rejects = expect(() => - expect(observable).toEmitValuesWith((received) => { - expect(received).toEqual([1, 2, 3]); - }) - ).rejects; - - await rejects.toThrow(); - }); - }); - - describe('passing undefined in expect', () => { - it('should fail with correct message', async () => { - const observable = undefined as unknown as Observable; - - const rejects = expect(() => - expect(observable).toEmitValuesWith((received) => { - expect(received).toEqual([1, 2, 3]); - }) - ).rejects; - - await rejects.toThrow(); - }); - }); - - describe('passing number instead of Observable in expect', () => { - it('should fail with correct message', async () => { - const observable = 1 as unknown as Observable; - - const rejects = expect(() => - expect(observable).toEmitValuesWith((received) => { - expect(received).toEqual([1, 2, 3]); - }) - ).rejects; - - await rejects.toThrow(); - }); - }); - - describe('wrong number of emitted values', () => { - it('should fail with correct message', async () => { - const observable = interval(10).pipe(take(3)); - - const rejects = expect(() => - expect(observable).toEmitValuesWith((received) => { - expect(received).toEqual([0, 1]); - }) - ).rejects; - - await rejects.toThrow(); - }); - }); - - describe('wrong emitted values', () => { - it('should fail with correct message', async () => { - const observable = interval(10).pipe(take(3)); - - const rejects = expect(() => - expect(observable).toEmitValuesWith((received) => { - expect(received).toEqual([1, 2, 3]); - }) - ).rejects; - - await rejects.toThrow(); - }); - }); - - describe('wrong emitted value types', () => { - it('should fail with correct message', async () => { - const observable = interval(10).pipe(take(3)) as unknown as Observable; - - const rejects = expect(() => - expect(observable).toEmitValuesWith((received) => { - expect(received).toEqual(['0', '1', '2']); - }) - ).rejects; - - await rejects.toThrow(); - }); - }); - - describe(`observable that does not complete within ${OBSERVABLE_TEST_TIMEOUT_IN_MS}ms`, () => { - it('should fail with correct message', async () => { - const observable = interval(600); - - const rejects = expect(() => - expect(observable).toEmitValuesWith((received) => { - expect(received).toEqual([0]); - }) - ).rejects; - - await rejects.toThrow(); - }); - }); - }); - - describe('passing tests', () => { - describe('correct emitted values', () => { - it('should pass with correct message', async () => { - const observable = interval(10).pipe(take(3)); - await expect(observable).toEmitValuesWith((received) => { - expect(received).toEqual([0, 1, 2]); - }); - }); - }); - - describe('correct emitted values with throw', () => { - it('should pass with correct message', async () => { - const observable = interval(10).pipe( - map((interval) => { - if (interval > 1) { - throw 'an error'; - } - - return interval; - }) - ); - - await expect(observable).toEmitValuesWith((received) => { - expect(received).toEqual([0, 1, 'an error']); - }); - }); - }); - - describe('correct emitted values with throwError', () => { - it('should pass with correct message', async () => { - const observable = interval(10).pipe( - mergeMap((interval) => { - if (interval === 1) { - return throwError('an error'); - } - - return of(interval); - }) - ); - - await expect(observable).toEmitValuesWith((received) => { - expect(received).toEqual([0, 'an error']); - }); - }); - }); - }); -}); diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts deleted file mode 100644 index 2c647baea5b..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/toEmitValuesWith.ts +++ /dev/null @@ -1,63 +0,0 @@ -// Core Grafana history -import { matcherHint, printReceived } from 'jest-matcher-utils'; -import { Observable, Subscription } from 'rxjs'; - -import { expectObservable, forceObservableCompletion } from './utils'; - -function tryExpectations(received: unknown[], expectations: (received: unknown[]) => void): jest.CustomMatcherResult { - try { - expectations(received); - return { - pass: true, - message: () => `${matcherHint('.not.toEmitValues')} - - Expected observable to complete with - ${printReceived(received)} - `, - }; - } catch (err) { - return { - pass: false, - message: () => 'failed ' + err, - }; - } -} - -/** - * Collect all the values emitted by the observables (also errors) and pass them to the expectations functions after - * the observable ended (or emitted error). If Observable does not complete within OBSERVABLE_TEST_TIMEOUT_IN_MS the - * test fails. - */ -export function toEmitValuesWith( - received: Observable, - expectations: (actual: any[]) => void -): Promise { - const failsChecks = expectObservable(received); - if (failsChecks) { - return Promise.resolve(failsChecks); - } - - return new Promise((resolve) => { - const receivedValues: any[] = []; - const subscription = new Subscription(); - - subscription.add( - received.subscribe({ - next: (value) => { - receivedValues.push(value); - }, - error: (err) => { - receivedValues.push(err); - subscription.unsubscribe(); - resolve(tryExpectations(receivedValues, expectations)); - }, - complete: () => { - subscription.unsubscribe(); - resolve(tryExpectations(receivedValues, expectations)); - }, - }) - ); - - forceObservableCompletion(subscription, resolve); - }); -} diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/types.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/types.ts deleted file mode 100644 index 953b5ff86da..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/toEmitValuesWith.ts -import { Observable } from 'rxjs'; - -export const OBSERVABLE_TEST_TIMEOUT_IN_MS = 1000; - -export type ObservableType = T extends Observable ? V : never; - -export interface ObservableMatchers extends jest.ExpectExtendMap { - toEmitValues>(received: T, expected: E[]): Promise; - toEmitValuesWith>( - received: T, - expectations: (received: E[]) => void - ): Promise; -} diff --git a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts b/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts deleted file mode 100644 index 3915cc13a4f..00000000000 --- a/packages/grafana-prometheus/src/gcopypaste/public/test/matchers/utils.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/test/matchers/utils.ts -import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; -import { asapScheduler, Subscription, timer, isObservable } from 'rxjs'; - -import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types'; - -export function forceObservableCompletion(subscription: Subscription, resolve: (args: any) => void) { - const timeoutObservable = timer(OBSERVABLE_TEST_TIMEOUT_IN_MS, asapScheduler); - - subscription.add( - timeoutObservable.subscribe(() => { - subscription.unsubscribe(); - resolve({ - pass: false, - message: () => - `${matcherHint('.toEmitValues')} - - Expected ${printReceived('Observable')} to be ${printExpected( - `completed within ${OBSERVABLE_TEST_TIMEOUT_IN_MS}ms` - )} but it did not.`, - }); - }) - ); -} - -export function expectObservableToBeDefined(received: unknown): jest.CustomMatcherResult | null { - if (received) { - return null; - } - - return { - pass: false, - message: () => `${matcherHint('.toEmitValues')} - -Expected ${printReceived(received)} to be ${printExpected('defined')}.`, - }; -} - -export function expectObservableToBeObservable(received: unknown): jest.CustomMatcherResult | null { - if (isObservable(received)) { - return null; - } - - return { - pass: false, - message: () => `${matcherHint('.toEmitValues')} - -Expected ${printReceived(received)} to be ${printExpected('an Observable')}.`, - }; -} - -export function expectObservable(received: unknown): jest.CustomMatcherResult | null { - const toBeDefined = expectObservableToBeDefined(received); - if (toBeDefined) { - return toBeDefined; - } - - const toBeObservable = expectObservableToBeObservable(received); - if (toBeObservable) { - return toBeObservable; - } - - return null; -} diff --git a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx index 1fb721fdfad..a98cc3a44c1 100644 --- a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx +++ b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx @@ -3,11 +3,10 @@ import { css } from '@emotion/css'; import { capitalize } from 'lodash'; import { useMemo, useState } from 'react'; -import { CoreApp, DataQuery, GrafanaTheme2 } from '@grafana/data'; +import { CoreApp, DataQuery, getNextRefId, GrafanaTheme2 } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import { Button, Collapse, Modal, useStyles2 } from '@grafana/ui'; -import { getNextRefIdChar } from '../gcopypaste/app/core/utils/query'; import { PromQuery } from '../types'; import { promQueryModeller } from './PromQueryModeller'; @@ -59,7 +58,7 @@ export const QueryPatternsModal = (props: Props) => { if (hasNewQueryOption && selectAsNewQuery) { onAddQuery({ ...query, - refId: getNextRefIdChar(queries ?? [query]), + refId: getNextRefId(queries ?? [query]), expr: promQueryModeller.renderQuery(visualQuery.query), }); } else { diff --git a/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.test.tsx index 55151ae2800..8647feee695 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/LabelFilters.test.tsx @@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ComponentProps } from 'react'; -import { selectOptionInTest } from '../../gcopypaste/test/helpers/selectOptionInTest'; +import { selectOptionInTest } from '../../test/helpers/selectOptionInTest'; import { getLabelSelects } from '../testUtils'; import { LabelFilters, MISSING_LABEL_FILTER_ERROR_MESSAGE, LabelFiltersProps } from './LabelFilters'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.test.tsx index b6654dadafb..61d6c4dfda2 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderOptions.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { CoreApp } from '@grafana/data'; -import { selectOptionInTest } from '../../gcopypaste/test/helpers/selectOptionInTest'; +import { selectOptionInTest } from '../../test/helpers/selectOptionInTest'; import { PromQuery } from '../../types'; import { getQueryWithDefaults } from '../state'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditorAutocompleteInfo.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditorAutocompleteInfo.test.tsx index 42ae5cb2284..3a2d762db70 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditorAutocompleteInfo.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryCodeEditorAutocompleteInfo.test.tsx @@ -30,18 +30,6 @@ jest.mock('../../components/monaco-query-field/MonacoQueryFieldWrapper', () => { }; }); -jest.mock('../../gcopypaste/app/core/store', () => { - return { - get() { - return undefined; - }, - set() {}, - getObject(key: string, defaultValue: unknown) { - return defaultValue; - }, - }; -}); - jest.mock('@grafana/runtime', () => { return { ...jest.requireActual('@grafana/runtime'), diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.test.tsx index dcba07eb367..6cfbf555999 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryEditorSelector.test.tsx @@ -24,18 +24,6 @@ jest.mock('../../components/monaco-query-field/MonacoQueryFieldWrapper', () => { }; }); -jest.mock('app/core/store', () => { - return { - get() { - return undefined; - }, - set() {}, - getObject(key: string, defaultValue: unknown) { - return defaultValue; - }, - }; -}); - jest.mock('@grafana/runtime', () => { return { ...jest.requireActual('@grafana/runtime'), diff --git a/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.tsx b/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.tsx index 7c9304f8fc7..4993cb51127 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/promQail/PromQail.tsx @@ -3,12 +3,11 @@ import { css, cx } from '@emotion/css'; import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { useEffect, useReducer, useRef, useState } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, store } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import { Alert, Button, Checkbox, Input, Spinner, useTheme2 } from '@grafana/ui'; import { PrometheusDatasource } from '../../../datasource'; -import store from '../../../gcopypaste/app/core/store'; import { PromVisualQuery } from '../../types'; import { QuerySuggestionContainer } from './QuerySuggestionContainer'; diff --git a/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.ts b/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.ts index 7dfc732b025..f0df6046d58 100644 --- a/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.ts +++ b/packages/grafana-prometheus/src/querybuilder/hooks/useFlag.ts @@ -1,7 +1,7 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/hooks/useFlag.ts import { useCallback, useState } from 'react'; -import store from '../../gcopypaste/app/core/store'; +import { store } from '@grafana/data'; export const promQueryEditorExplainKey = 'PrometheusQueryEditorExplainDefault'; diff --git a/packages/grafana-prometheus/src/querybuilder/state.ts b/packages/grafana-prometheus/src/querybuilder/state.ts index fddda52c406..915acb4aa60 100644 --- a/packages/grafana-prometheus/src/querybuilder/state.ts +++ b/packages/grafana-prometheus/src/querybuilder/state.ts @@ -1,7 +1,6 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/state.ts -import { CoreApp } from '@grafana/data'; +import { CoreApp, store } from '@grafana/data'; -import store from '../gcopypaste/app/core/store'; import { LegendFormatMode, PromQuery } from '../types'; import { QueryEditorMode } from './shared/types'; diff --git a/packages/grafana-prometheus/src/querycache/QueryCache.ts b/packages/grafana-prometheus/src/querycache/QueryCache.ts index f4b955b82a6..4758e4cff58 100644 --- a/packages/grafana-prometheus/src/querycache/QueryCache.ts +++ b/packages/grafana-prometheus/src/querycache/QueryCache.ts @@ -1,5 +1,6 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querycache/QueryCache.ts import { + amendTable, DataFrame, DataQueryRequest, dateTime, @@ -8,9 +9,10 @@ import { incrRoundDn, isValidDuration, parseDuration, + Table, + trimTable, } from '@grafana/data'; -import { amendTable, Table, trimTable } from '../gcopypaste/app/features/live/data/amendTimeSeries'; import { PromQuery } from '../types'; // dashboardUID + panelId + refId diff --git a/packages/grafana-prometheus/src/__mocks__/datasource.ts b/packages/grafana-prometheus/src/test/__mocks__/datasource.ts similarity index 71% rename from packages/grafana-prometheus/src/__mocks__/datasource.ts rename to packages/grafana-prometheus/src/test/__mocks__/datasource.ts index 9237f1b65f0..e4e4fb21e3c 100644 --- a/packages/grafana-prometheus/src/__mocks__/datasource.ts +++ b/packages/grafana-prometheus/src/test/__mocks__/datasource.ts @@ -1,6 +1,53 @@ -import { CoreApp, DataQueryRequest, dateTime, rangeUtil, TimeRange } from '@grafana/data'; +import { merge } from 'lodash'; -import { PromQuery } from '../types'; +import { + CoreApp, + DataQueryRequest, + DataSourceJsonData, + DataSourceSettings, + dateTime, + rangeUtil, + TimeRange, +} from '@grafana/data'; + +import { PromOptions, PromQuery } from '../../types'; + +export const getMockDataSource = ( + overrides?: Partial> +): DataSourceSettings => + merge( + { + access: '', + basicAuth: false, + basicAuthUser: '', + withCredentials: false, + database: '', + id: 13, + uid: 'x', + isDefault: false, + jsonData: { authType: 'credentials', defaultRegion: 'eu-west-2' }, + name: 'gdev-prometheus', + typeName: 'Prometheus', + orgId: 1, + readOnly: false, + type: 'prometheus', + typeLogoUrl: 'packages/grafana-prometheus/src/img/prometheus_logo.svg', + url: '', + user: '', + secureJsonFields: {}, + }, + overrides + ); + +export function createDefaultConfigOptions(): DataSourceSettings { + return getMockDataSource({ + jsonData: { + timeInterval: '1m', + queryTimeout: '1m', + httpMethod: 'GET', + }, + }); +} export function createDataRequest( targets: PromQuery[], diff --git a/packages/grafana-prometheus/src/gcopypaste/test/helpers/selectOptionInTest.ts b/packages/grafana-prometheus/src/test/helpers/selectOptionInTest.ts similarity index 100% rename from packages/grafana-prometheus/src/gcopypaste/test/helpers/selectOptionInTest.ts rename to packages/grafana-prometheus/src/test/helpers/selectOptionInTest.ts From 0bbaed187c979f88c7e52b4eac2c059b81de339b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:16:00 +0000 Subject: [PATCH 16/39] Update dependency @grafana/scenes to v5.3.6 --- yarn.lock | 80 +++++++------------------------------------------------ 1 file changed, 9 insertions(+), 71 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9f1bc4c5284..af9fe5b04fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -285,14 +285,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.0, @babel/helper-plugin-utils@npm:^7.24.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": - version: 7.24.5 - resolution: "@babel/helper-plugin-utils@npm:7.24.5" - checksum: 10/6e11ca5da73e6bd366848236568c311ac10e433fc2034a6fe6243af28419b07c93b4386f87bbc940aa058b7c83f370ef58f3b0fd598106be040d21a3d1c14276 - languageName: node - linkType: hard - -"@babel/helper-plugin-utils@npm:^7.24.7": +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.0, @babel/helper-plugin-utils@npm:^7.24.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": version: 7.24.7 resolution: "@babel/helper-plugin-utils@npm:7.24.7" checksum: 10/dad51622f0123fdba4e2d40a81a6b7d6ef4b1491b2f92fd9749447a36bde809106cf117358705057a2adc8fd73d5dc090222e0561b1213dae8601c8367f5aac8 @@ -2457,16 +2450,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/core@npm:^1.6.0": - version: 1.6.0 - resolution: "@floating-ui/core@npm:1.6.0" - dependencies: - "@floating-ui/utils": "npm:^0.2.1" - checksum: 10/d6a47cacde193cd8ccb4c268b91ccc4ca254dffaec6242b07fd9bcde526044cc976d27933a7917f9a671de0a0e27f8d358f46400677dbd0c8199de293e9746e1 - languageName: node - linkType: hard - -"@floating-ui/dom@npm:^1.0.0": +"@floating-ui/dom@npm:^1.0.0, @floating-ui/dom@npm:^1.0.1": version: 1.6.5 resolution: "@floating-ui/dom@npm:1.6.5" dependencies: @@ -2476,16 +2460,6 @@ __metadata: languageName: node linkType: hard -"@floating-ui/dom@npm:^1.0.1": - version: 1.6.1 - resolution: "@floating-ui/dom@npm:1.6.1" - dependencies: - "@floating-ui/core": "npm:^1.6.0" - "@floating-ui/utils": "npm:^0.2.1" - checksum: 10/c010feb55be37662eb4cc8d0a22e21359c25247bbdcd9557617fd305cf08c8f020435b17e4b4f410201ba9abe3a0dd96b5c42d56e85f7a5e11e7d30b85afc116 - languageName: node - linkType: hard - "@floating-ui/react-dom@npm:^2.1.0": version: 2.1.0 resolution: "@floating-ui/react-dom@npm:2.1.0" @@ -2512,7 +2486,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.0, @floating-ui/utils@npm:^0.2.1": +"@floating-ui/utils@npm:^0.2.0": version: 0.2.1 resolution: "@floating-ui/utils@npm:0.2.1" checksum: 10/33c9ab346e7b05c5a1e6a95bc902aafcfc2c9d513a147e2491468843bd5607531b06d0b9aa56aa491cbf22a6c2495c18ccfc4c0344baec54a689a7bb8e4898d6 @@ -3581,8 +3555,8 @@ __metadata: linkType: soft "@grafana/scenes@npm:^5.3.4": - version: 5.3.4 - resolution: "@grafana/scenes@npm:5.3.4" + version: 5.3.6 + resolution: "@grafana/scenes@npm:5.3.6" dependencies: "@grafana/e2e-selectors": "npm:^11.0.0" "@leeoniya/ufuzzy": "npm:^1.0.14" @@ -3597,7 +3571,7 @@ __metadata: "@grafana/ui": ^10.4.1 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/a312258eeb22c9d78f2d17404bba2a50e749e113fc0ad1af1d0e42f647ff823097c3065da2fc1efe35b55fcff6ebd26f306fa121a226817ccc9edd8a12400301 + checksum: 10/d487f6f1c53f4dba1562925f4d69c75b5882ce0cdb5cdf95eb6998c3f4fcf13e597200e44e47ff9105bee1e1be23007e568bbe52c5049f92e27f9a14abe00178 languageName: node linkType: hard @@ -5745,7 +5719,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-portal@npm:1.0.4": +"@radix-ui/react-portal@npm:1.0.4, @radix-ui/react-portal@npm:^1.0.1": version: 1.0.4 resolution: "@radix-ui/react-portal@npm:1.0.4" dependencies: @@ -5765,26 +5739,6 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-portal@npm:^1.0.1": - version: 1.0.3 - resolution: "@radix-ui/react-portal@npm:1.0.3" - dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-primitive": "npm:1.0.3" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10/d352bcd6ad65eb43c9e0d72d0755c2aae85e03fb287770866262be3a2d5302b2885aee3cd99f2bbf62ecd14fcb1460703f1dcdc40351f77ad887b931c6f0012a - languageName: node - linkType: hard - "@radix-ui/react-presence@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/react-presence@npm:1.0.1" @@ -18194,16 +18148,7 @@ __metadata: languageName: node linkType: hard -"i18next@npm:^23.0.0, i18next@npm:^23.5.1": - version: 23.11.3 - resolution: "i18next@npm:23.11.3" - dependencies: - "@babel/runtime": "npm:^7.23.2" - checksum: 10/9d562ade19d0beba16683ff94967a6dedc0a32ce335d203c5a160f075ac5a9a7a9adb164085a6b7b69328568bc932a65b92664834c2bf3e15d8f3bff90f15353 - languageName: node - linkType: hard - -"i18next@npm:^23.11.5": +"i18next@npm:^23.0.0, i18next@npm:^23.11.5, i18next@npm:^23.5.1": version: 23.11.5 resolution: "i18next@npm:23.11.5" dependencies: @@ -29577,20 +29522,13 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.18.2": +"type-fest@npm:^4.18.2, type-fest@npm:^4.9.0": version: 4.18.3 resolution: "type-fest@npm:4.18.3" checksum: 10/eb750920d0ef3639177f581edd6489d972c5c5827abb602a9c9662889aad148a7d558257e36c563f1beb81a2e417faec52ecec9799b28531d8335856f91e6dff languageName: node linkType: hard -"type-fest@npm:^4.9.0": - version: 4.10.2 - resolution: "type-fest@npm:4.10.2" - checksum: 10/2b1ad1270d9fabeeb506ba831d513caeb05bfc852e5e012511d785ce9dc68d773fe0a42bddf857a362c7f3406244809c5b8a698b743bb7617d4a8c470672087f - languageName: node - linkType: hard - "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" From acf5c7c6ba52d1e75f003f757fce3255d06e1cd4 Mon Sep 17 00:00:00 2001 From: Sergej-Vlasov <37613182+Sergej-Vlasov@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:43:44 +0300 Subject: [PATCH 17/39] DashboardScene: Activate refresh picker manually when time controls are hidden (#89952) --------- Co-authored-by: Dominik Prokop Co-authored-by: Ivan Ortega --- package.json | 2 +- .../scene/DashboardControls.tsx | 15 +++ .../scene/DashboardSceneRenderer.tsx | 2 +- yarn.lock | 121 +++++++++++------- 4 files changed, 95 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index cccce011e3b..fd8e8ce52fc 100644 --- a/package.json +++ b/package.json @@ -261,7 +261,7 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "^5.3.4", + "@grafana/scenes": "5.3.5", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index a289695953b..b4396867d6b 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -14,6 +14,7 @@ import { sceneGraph, SceneObjectUrlSyncConfig, SceneObjectUrlValues, + CancelActivationHandler, } from '@grafana/scenes'; import { Box, Stack, useStyles2 } from '@grafana/ui'; @@ -74,6 +75,20 @@ export class DashboardControls extends SceneObjectBase { refreshPicker: state.refreshPicker ?? new SceneRefreshPicker({}), ...state, }); + + this.addActivationHandler(() => { + let refreshPickerDeactivation: CancelActivationHandler | undefined; + + if (this.state.hideTimeControls) { + refreshPickerDeactivation = this.state.refreshPicker.activate(); + } + + return () => { + if (refreshPickerDeactivation) { + refreshPickerDeactivation(); + } + }; + }); } /** diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx index 0e220a4e448..8c50aa0585c 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx @@ -68,7 +68,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps {scopes && !meta.dashboardNotFound && } - {controls && hasControls && ( + {controls && (
diff --git a/yarn.lock b/yarn.lock index af9fe5b04fd..7b2268ecc40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -285,7 +285,14 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.0, @babel/helper-plugin-utils@npm:^7.24.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.0, @babel/helper-plugin-utils@npm:^7.24.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": + version: 7.24.5 + resolution: "@babel/helper-plugin-utils@npm:7.24.5" + checksum: 10/6e11ca5da73e6bd366848236568c311ac10e433fc2034a6fe6243af28419b07c93b4386f87bbc940aa058b7c83f370ef58f3b0fd598106be040d21a3d1c14276 + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-plugin-utils@npm:7.24.7" checksum: 10/dad51622f0123fdba4e2d40a81a6b7d6ef4b1491b2f92fd9749447a36bde809106cf117358705057a2adc8fd73d5dc090222e0561b1213dae8601c8367f5aac8 @@ -2450,7 +2457,16 @@ __metadata: languageName: node linkType: hard -"@floating-ui/dom@npm:^1.0.0, @floating-ui/dom@npm:^1.0.1": +"@floating-ui/core@npm:^1.6.0": + version: 1.6.0 + resolution: "@floating-ui/core@npm:1.6.0" + dependencies: + "@floating-ui/utils": "npm:^0.2.1" + checksum: 10/d6a47cacde193cd8ccb4c268b91ccc4ca254dffaec6242b07fd9bcde526044cc976d27933a7917f9a671de0a0e27f8d358f46400677dbd0c8199de293e9746e1 + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.0.0": version: 1.6.5 resolution: "@floating-ui/dom@npm:1.6.5" dependencies: @@ -2460,6 +2476,16 @@ __metadata: languageName: node linkType: hard +"@floating-ui/dom@npm:^1.0.1": + version: 1.6.1 + resolution: "@floating-ui/dom@npm:1.6.1" + dependencies: + "@floating-ui/core": "npm:^1.6.0" + "@floating-ui/utils": "npm:^0.2.1" + checksum: 10/c010feb55be37662eb4cc8d0a22e21359c25247bbdcd9557617fd305cf08c8f020435b17e4b4f410201ba9abe3a0dd96b5c42d56e85f7a5e11e7d30b85afc116 + languageName: node + linkType: hard + "@floating-ui/react-dom@npm:^2.1.0": version: 2.1.0 resolution: "@floating-ui/react-dom@npm:2.1.0" @@ -2486,7 +2512,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.0": +"@floating-ui/utils@npm:^0.2.0, @floating-ui/utils@npm:^0.2.1": version: 0.2.1 resolution: "@floating-ui/utils@npm:0.2.1" checksum: 10/33c9ab346e7b05c5a1e6a95bc902aafcfc2c9d513a147e2491468843bd5607531b06d0b9aa56aa491cbf22a6c2495c18ccfc4c0344baec54a689a7bb8e4898d6 @@ -3100,13 +3126,13 @@ __metadata: linkType: soft "@grafana/e2e-selectors@npm:^11.0.0": - version: 11.0.0 - resolution: "@grafana/e2e-selectors@npm:11.0.0" + version: 11.1.0 + resolution: "@grafana/e2e-selectors@npm:11.1.0" dependencies: "@grafana/tsconfig": "npm:^1.3.0-rc1" - tslib: "npm:2.6.2" - typescript: "npm:5.3.3" - checksum: 10/0e327c5afc342bca9be46b0b3fb7b55d69e8b7b6e9912be3255ce52abcb8832265246a0e6b2f877112a752b6e44f6f7edf5c4d2a32b5edc84800d19c880dfc6c + tslib: "npm:2.6.3" + typescript: "npm:5.4.5" + checksum: 10/010a32e8b562d0da83b008646b9928a96a79957096eed713aa67b227d8ad6055d22cc0ec26f87fd9839cfb28344d0012f49c3c823defc6e91f4ab05ed7d8c465 languageName: node linkType: hard @@ -3554,9 +3580,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^5.3.4": - version: 5.3.6 - resolution: "@grafana/scenes@npm:5.3.6" +"@grafana/scenes@npm:5.3.5": + version: 5.3.5 + resolution: "@grafana/scenes@npm:5.3.5" dependencies: "@grafana/e2e-selectors": "npm:^11.0.0" "@leeoniya/ufuzzy": "npm:^1.0.14" @@ -3571,7 +3597,7 @@ __metadata: "@grafana/ui": ^10.4.1 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/d487f6f1c53f4dba1562925f4d69c75b5882ce0cdb5cdf95eb6998c3f4fcf13e597200e44e47ff9105bee1e1be23007e568bbe52c5049f92e27f9a14abe00178 + checksum: 10/2ffbbda4a90e9b0f17c6a4d9b210b97321cc531427c1b11c75b32c72631c4122950bc310bb103cb29f17a50a98844fd91ae689872625614b0284befc61081eb5 languageName: node linkType: hard @@ -5719,7 +5745,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-portal@npm:1.0.4, @radix-ui/react-portal@npm:^1.0.1": +"@radix-ui/react-portal@npm:1.0.4": version: 1.0.4 resolution: "@radix-ui/react-portal@npm:1.0.4" dependencies: @@ -5739,6 +5765,26 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-portal@npm:^1.0.1": + version: 1.0.3 + resolution: "@radix-ui/react-portal@npm:1.0.3" + dependencies: + "@babel/runtime": "npm:^7.13.10" + "@radix-ui/react-primitive": "npm:1.0.3" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/d352bcd6ad65eb43c9e0d72d0755c2aae85e03fb287770866262be3a2d5302b2885aee3cd99f2bbf62ecd14fcb1460703f1dcdc40351f77ad887b931c6f0012a + languageName: node + linkType: hard + "@radix-ui/react-presence@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/react-presence@npm:1.0.1" @@ -17099,7 +17145,7 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:^5.3.4" + "@grafana/scenes": "npm:5.3.5" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^1.3.0-rc1" @@ -18148,7 +18194,16 @@ __metadata: languageName: node linkType: hard -"i18next@npm:^23.0.0, i18next@npm:^23.11.5, i18next@npm:^23.5.1": +"i18next@npm:^23.0.0, i18next@npm:^23.5.1": + version: 23.11.3 + resolution: "i18next@npm:23.11.3" + dependencies: + "@babel/runtime": "npm:^7.23.2" + checksum: 10/9d562ade19d0beba16683ff94967a6dedc0a32ce335d203c5a160f075ac5a9a7a9adb164085a6b7b69328568bc932a65b92664834c2bf3e15d8f3bff90f15353 + languageName: node + linkType: hard + +"i18next@npm:^23.11.5": version: 23.11.5 resolution: "i18next@npm:23.11.5" dependencies: @@ -29377,13 +29432,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.6.2": - version: 2.6.2 - resolution: "tslib@npm:2.6.2" - checksum: 10/bd26c22d36736513980091a1e356378e8b662ded04204453d353a7f34a4c21ed0afc59b5f90719d4ba756e581a162ecbf93118dc9c6be5acf70aa309188166ca - languageName: node - linkType: hard - "tslib@npm:2.6.3, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.6.2": version: 2.6.3 resolution: "tslib@npm:2.6.3" @@ -29522,13 +29570,20 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.18.2, type-fest@npm:^4.9.0": +"type-fest@npm:^4.18.2": version: 4.18.3 resolution: "type-fest@npm:4.18.3" checksum: 10/eb750920d0ef3639177f581edd6489d972c5c5827abb602a9c9662889aad148a7d558257e36c563f1beb81a2e417faec52ecec9799b28531d8335856f91e6dff languageName: node linkType: hard +"type-fest@npm:^4.9.0": + version: 4.10.2 + resolution: "type-fest@npm:4.10.2" + checksum: 10/2b1ad1270d9fabeeb506ba831d513caeb05bfc852e5e012511d785ce9dc68d773fe0a42bddf857a362c7f3406244809c5b8a698b743bb7617d4a8c470672087f + languageName: node + linkType: hard + "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -29622,16 +29677,6 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.3.3": - version: 5.3.3 - resolution: "typescript@npm:5.3.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/6e4e6a14a50c222b3d14d4ea2f729e79f972fa536ac1522b91202a9a65af3605c2928c4a790a4a50aa13694d461c479ba92cedaeb1e7b190aadaa4e4b96b8e18 - languageName: node - linkType: hard - "typescript@npm:5.4.5, typescript@npm:>=2.7, typescript@npm:>=3 < 6, typescript@npm:^5.0.0, typescript@npm:^5.0.4, typescript@npm:^5.2.2": version: 5.4.5 resolution: "typescript@npm:5.4.5" @@ -29652,16 +29697,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.3.3#optional!builtin": - version: 5.3.3 - resolution: "typescript@patch:typescript@npm%3A5.3.3#optional!builtin::version=5.3.3&hash=e012d7" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/c93786fcc9a70718ba1e3819bab56064ead5817004d1b8186f8ca66165f3a2d0100fee91fa64c840dcd45f994ca5d615d8e1f566d39a7470fc1e014dbb4cf15d - languageName: node - linkType: hard - "typescript@patch:typescript@npm%3A5.4.5#optional!builtin, typescript@patch:typescript@npm%3A>=2.7#optional!builtin, typescript@patch:typescript@npm%3A>=3 < 6#optional!builtin, typescript@patch:typescript@npm%3A^5.0.0#optional!builtin, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin, typescript@patch:typescript@npm%3A^5.2.2#optional!builtin": version: 5.4.5 resolution: "typescript@patch:typescript@npm%3A5.4.5#optional!builtin::version=5.4.5&hash=5adc0c" From dc163d10cc198c51df2a96f29855a1468ec09ee8 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 3 Jul 2024 08:59:09 -0700 Subject: [PATCH 18/39] add resource store abstraction --- Makefile | 1 + pkg/apimachinery/utils/meta.go | 126 + pkg/apimachinery/utils/meta_test.go | 16 + pkg/services/apiserver/options/storage.go | 19 +- pkg/services/apiserver/service.go | 34 + pkg/storage/unified/apistore/restoptions.go | 87 + pkg/storage/unified/apistore/storage.go | 546 ++++ pkg/storage/unified/apistore/stream.go | 203 ++ .../unified/apistore/test/watch_test.go | 371 +++ pkg/storage/unified/entitybridge/decoder.go | 119 + .../unified/entitybridge/entitybridge.go | 272 ++ pkg/storage/unified/resource/buf.gen.yaml | 10 + pkg/storage/unified/resource/buf.yaml | 7 + pkg/storage/unified/resource/cdk_backend.go | 298 +++ .../unified/resource/client_wrapper.go | 30 + pkg/storage/unified/resource/deleted.go | 37 + pkg/storage/unified/resource/event.go | 92 + pkg/storage/unified/resource/go.mod | 45 +- pkg/storage/unified/resource/go.sum | 74 + pkg/storage/unified/resource/hooks.go | 45 + pkg/storage/unified/resource/keys.go | 17 + pkg/storage/unified/resource/keys_test.go | 21 + pkg/storage/unified/resource/metrics.go | 41 + pkg/storage/unified/resource/noop.go | 41 + pkg/storage/unified/resource/resource.pb.go | 2274 +++++++++++++++++ pkg/storage/unified/resource/resource.proto | 315 +++ .../unified/resource/resource_grpc.pb.go | 445 ++++ pkg/storage/unified/resource/rv.go | 16 + pkg/storage/unified/resource/server.go | 562 ++++ pkg/storage/unified/resource/server_test.go | 145 ++ .../resource/testdata/01_create_playlist.json | 23 + pkg/storage/unified/resource/validation.go | 25 + 32 files changed, 6346 insertions(+), 11 deletions(-) create mode 100644 pkg/storage/unified/apistore/restoptions.go create mode 100644 pkg/storage/unified/apistore/storage.go create mode 100644 pkg/storage/unified/apistore/stream.go create mode 100644 pkg/storage/unified/apistore/test/watch_test.go create mode 100644 pkg/storage/unified/entitybridge/decoder.go create mode 100644 pkg/storage/unified/entitybridge/entitybridge.go create mode 100644 pkg/storage/unified/resource/buf.gen.yaml create mode 100644 pkg/storage/unified/resource/buf.yaml create mode 100644 pkg/storage/unified/resource/cdk_backend.go create mode 100644 pkg/storage/unified/resource/client_wrapper.go create mode 100644 pkg/storage/unified/resource/deleted.go create mode 100644 pkg/storage/unified/resource/event.go create mode 100644 pkg/storage/unified/resource/hooks.go create mode 100644 pkg/storage/unified/resource/keys.go create mode 100644 pkg/storage/unified/resource/keys_test.go create mode 100644 pkg/storage/unified/resource/metrics.go create mode 100644 pkg/storage/unified/resource/noop.go create mode 100644 pkg/storage/unified/resource/resource.pb.go create mode 100644 pkg/storage/unified/resource/resource.proto create mode 100644 pkg/storage/unified/resource/resource_grpc.pb.go create mode 100644 pkg/storage/unified/resource/rv.go create mode 100644 pkg/storage/unified/resource/server.go create mode 100644 pkg/storage/unified/resource/server_test.go create mode 100644 pkg/storage/unified/resource/testdata/01_create_playlist.json create mode 100644 pkg/storage/unified/resource/validation.go diff --git a/Makefile b/Makefile index 58860400b35..cabcd9c4cce 100644 --- a/Makefile +++ b/Makefile @@ -381,6 +381,7 @@ protobuf: ## Compile protobuf definitions buf generate pkg/plugins/backendplugin/pluginextensionv2 --template pkg/plugins/backendplugin/pluginextensionv2/buf.gen.yaml buf generate pkg/plugins/backendplugin/secretsmanagerplugin --template pkg/plugins/backendplugin/secretsmanagerplugin/buf.gen.yaml buf generate pkg/services/store/entity --template pkg/services/store/entity/buf.gen.yaml + buf generate pkg/storage/unified/resource --template pkg/storage/unified/resource/buf.gen.yaml .PHONY: clean clean: ## Clean up intermediate build artifacts. diff --git a/pkg/apimachinery/utils/meta.go b/pkg/apimachinery/utils/meta.go index fa04df432c7..b6ba87a902b 100644 --- a/pkg/apimachinery/utils/meta.go +++ b/pkg/apimachinery/utils/meta.go @@ -1,9 +1,12 @@ package utils import ( + "bytes" "fmt" + "mime" "reflect" "strconv" + "strings" "time" "k8s.io/apimachinery/pkg/api/meta" @@ -20,6 +23,8 @@ const AnnoKeyUpdatedTimestamp = "grafana.app/updatedTimestamp" const AnnoKeyUpdatedBy = "grafana.app/updatedBy" const AnnoKeyFolder = "grafana.app/folder" const AnnoKeySlug = "grafana.app/slug" +const AnnoKeyBlob = "grafana.app/blob" +const AnnoKeyMessage = "grafana.app/message" // Identify where values came from @@ -53,6 +58,7 @@ type GrafanaMetaAccessor interface { metav1.Object GetGroupVersionKind() schema.GroupVersionKind + GetRuntimeObject() (runtime.Object, bool) // Helper to get resource versions as int64, however this is not required // See: https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions @@ -68,11 +74,16 @@ type GrafanaMetaAccessor interface { SetUpdatedBy(user string) GetFolder() string SetFolder(uid string) + GetMessage() string + SetMessage(msg string) SetAnnotation(key string, val string) GetSlug() string SetSlug(v string) + SetBlob(v *BlobInfo) + GetBlob() *BlobInfo + GetOriginInfo() (*ResourceOriginInfo, error) SetOriginInfo(info *ResourceOriginInfo) GetOriginName() string @@ -80,6 +91,8 @@ type GrafanaMetaAccessor interface { GetOriginHash() string GetOriginTimestamp() (*time.Time, error) + GetSpec() (any, error) + // Find a title in the object // This will reflect the object and try to get: // * spec.title @@ -123,6 +136,11 @@ func (m *grafanaMetaAccessor) GetResourceVersionInt64() (int64, error) { return strconv.ParseInt(v, 10, 64) } +func (m *grafanaMetaAccessor) GetRuntimeObject() (runtime.Object, bool) { + obj, ok := m.raw.(runtime.Object) + return obj, ok +} + func (m *grafanaMetaAccessor) SetResourceVersionInt64(rv int64) { m.obj.SetResourceVersion(strconv.FormatInt(rv, 10)) } @@ -192,6 +210,17 @@ func (m *grafanaMetaAccessor) SetUpdatedBy(user string) { m.SetAnnotation(AnnoKeyUpdatedBy, user) } +func (m *grafanaMetaAccessor) GetBlob() *BlobInfo { + return ParseBlobInfo(m.get(AnnoKeyBlob)) +} + +func (m *grafanaMetaAccessor) SetBlob(info *BlobInfo) { + if info == nil { + m.SetAnnotation(AnnoKeyBlob, "") // delete + } + m.SetAnnotation(AnnoKeyBlob, info.String()) +} + func (m *grafanaMetaAccessor) GetFolder() string { return m.get(AnnoKeyFolder) } @@ -200,6 +229,14 @@ func (m *grafanaMetaAccessor) SetFolder(uid string) { m.SetAnnotation(AnnoKeyFolder, uid) } +func (m *grafanaMetaAccessor) GetMessage() string { + return m.get(AnnoKeyMessage) +} + +func (m *grafanaMetaAccessor) SetMessage(uid string) { + m.SetAnnotation(AnnoKeyMessage, uid) +} + func (m *grafanaMetaAccessor) GetSlug() string { return m.get(AnnoKeySlug) } @@ -457,6 +494,16 @@ func (m *grafanaMetaAccessor) GetGroupVersionKind() schema.GroupVersionKind { return gvk } +func (m *grafanaMetaAccessor) GetSpec() (spec any, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("error reading spec") + } + }() + spec = m.r.FieldByName("Spec").Interface() + return +} + func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string { // look for Spec.Title or Spec.Name spec := m.r.FieldByName("Spec") @@ -477,3 +524,82 @@ func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string { } return defaultTitle } + +type BlobInfo struct { + UID string `json:"uid"` + Size int64 `json:"size,omitempty"` + Hash string `json:"hash,omitempty"` + MimeType string `json:"mime,omitempty"` + Charset string `json:"charset,omitempty"` // content type = mime+charset +} + +// Content type is mime + charset +func (b *BlobInfo) SetContentType(v string) { + var params map[string]string + var err error + + b.Charset = "" + b.MimeType, params, err = mime.ParseMediaType(v) + if err != nil { + return + } + b.Charset = params["charset"] +} + +// Content type is mime + charset +func (b *BlobInfo) ContentType() string { + sb := bytes.NewBufferString(b.MimeType) + if b.Charset != "" { + sb.WriteString("; charset=") + sb.WriteString(b.Charset) + } + return sb.String() +} + +func (b *BlobInfo) String() string { + sb := bytes.NewBufferString(b.UID) + if b.Size > 0 { + sb.WriteString(fmt.Sprintf("; size=%d", b.Size)) + } + if b.Hash != "" { + sb.WriteString("; hash=") + sb.WriteString(b.Hash) + } + if b.MimeType != "" { + sb.WriteString("; mime=") + sb.WriteString(b.MimeType) + } + if b.Charset != "" { + sb.WriteString("; charset=") + sb.WriteString(b.Charset) + } + return sb.String() +} + +func ParseBlobInfo(v string) *BlobInfo { + if v == "" { + return nil + } + info := &BlobInfo{} + for i, part := range strings.Split(v, ";") { + if i == 0 { + info.UID = part + continue + } + kv := strings.Split(strings.TrimSpace(part), "=") + if len(kv) == 2 { + val := kv[1] + switch kv[0] { + case "size": + info.Size, _ = strconv.ParseInt(val, 10, 64) + case "hash": + info.Hash = val + case "mime": + info.MimeType = val + case "charset": + info.Charset = val + } + } + } + return info +} diff --git a/pkg/apimachinery/utils/meta_test.go b/pkg/apimachinery/utils/meta_test.go index 0e0b81db7f0..b769c2a2866 100644 --- a/pkg/apimachinery/utils/meta_test.go +++ b/pkg/apimachinery/utils/meta_test.go @@ -172,6 +172,14 @@ func TestMetaAccessor(t *testing.T) { require.Equal(t, int64(12345), rv) }) + t.Run("blob info", func(t *testing.T) { + info := &utils.BlobInfo{UID: "AAA", Size: 123, Hash: "xyz", MimeType: "application/json", Charset: "utf-8"} + anno := info.String() + require.Equal(t, "AAA; size=123; hash=xyz; mime=application/json; charset=utf-8", anno) + copy := utils.ParseBlobInfo(anno) + require.Equal(t, info, copy) + }) + t.Run("find titles", func(t *testing.T) { // with a k8s object that has Spec.Title obj := &TestResource{ @@ -220,5 +228,13 @@ func TestMetaAccessor(t *testing.T) { }, obj2.GetAnnotations()) require.Equal(t, "xxx", meta.FindTitle("xxx")) + + rt, ok := meta.GetRuntimeObject() + require.Equal(t, obj2, rt) + require.True(t, ok) + + spec, err := meta.GetSpec() + require.Equal(t, obj2.Spec, spec) + require.NoError(t, err) }) } diff --git a/pkg/services/apiserver/options/storage.go b/pkg/services/apiserver/options/storage.go index 110215b52a5..27568c0f96f 100644 --- a/pkg/services/apiserver/options/storage.go +++ b/pkg/services/apiserver/options/storage.go @@ -4,20 +4,23 @@ import ( "fmt" "net" - grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/spf13/pflag" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/options" + + grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" ) type StorageType string const ( - StorageTypeFile StorageType = "file" - StorageTypeEtcd StorageType = "etcd" - StorageTypeLegacy StorageType = "legacy" - StorageTypeUnified StorageType = "unified" - StorageTypeUnifiedGrpc StorageType = "unified-grpc" + StorageTypeFile StorageType = "file" + StorageTypeEtcd StorageType = "etcd" + StorageTypeLegacy StorageType = "legacy" + StorageTypeUnified StorageType = "unified" + StorageTypeUnifiedGrpc StorageType = "unified-grpc" + StorageTypeUnifiedNext StorageType = "unified-next" + StorageTypeUnifiedNextGrpc StorageType = "unified-next-grpc" ) type StorageOptions struct { @@ -43,10 +46,10 @@ func (o *StorageOptions) AddFlags(fs *pflag.FlagSet) { func (o *StorageOptions) Validate() []error { errs := []error{} switch o.StorageType { - case StorageTypeFile, StorageTypeEtcd, StorageTypeLegacy, StorageTypeUnified, StorageTypeUnifiedGrpc: + case StorageTypeFile, StorageTypeEtcd, StorageTypeLegacy, StorageTypeUnified, StorageTypeUnifiedGrpc, StorageTypeUnifiedNext, StorageTypeUnifiedNextGrpc: // no-op default: - errs = append(errs, fmt.Errorf("--grafana-apiserver-storage-type must be one of %s, %s, %s, %s, %s", StorageTypeFile, StorageTypeEtcd, StorageTypeLegacy, StorageTypeUnified, StorageTypeUnifiedGrpc)) + errs = append(errs, fmt.Errorf("--grafana-apiserver-storage-type must be one of %s, %s, %s, %s, %s, %s, %s", StorageTypeFile, StorageTypeEtcd, StorageTypeLegacy, StorageTypeUnified, StorageTypeUnifiedGrpc, StorageTypeUnifiedNext, StorageTypeUnifiedNextGrpc)) } if _, _, err := net.SplitHostPort(o.Address); err != nil { diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 972bfb2f029..5be784f1f57 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -44,6 +44,9 @@ import ( "github.com/grafana/grafana/pkg/services/store/entity/db/dbimpl" "github.com/grafana/grafana/pkg/services/store/entity/sqlstash" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/unified/apistore" + "github.com/grafana/grafana/pkg/storage/unified/entitybridge" + "github.com/grafana/grafana/pkg/storage/unified/resource" ) var ( @@ -199,6 +202,7 @@ func (s *service) RegisterAPI(b builder.APIGroupBuilder) { s.builders = append(s.builders, b) } +// nolint:gocyclo func (s *service) start(ctx context.Context) error { defer close(s.startedCh) @@ -258,6 +262,36 @@ func (s *service) start(ctx context.Context) error { return err } + case grafanaapiserveroptions.StorageTypeUnifiedNext: + if !s.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorage) { + return fmt.Errorf("unified storage requires the unifiedStorage feature flag") + } + + server, err := entitybridge.ProvideResourceServer(s.db, s.cfg, s.features, s.tracing) + if err != nil { + return err + } + serverConfig.Config.RESTOptionsGetter = apistore.NewRESTOptionsGetterForServer(server, + o.RecommendedOptions.Etcd.StorageConfig.Codec) + + case grafanaapiserveroptions.StorageTypeUnifiedNextGrpc: + if !s.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorage) { + return fmt.Errorf("unified storage requires the unifiedStorage feature flag") + } + // Create a connection to the gRPC server + conn, err := grpc.NewClient(o.StorageOptions.Address, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return err + } + + // TODO: determine when to close the connection, we cannot defer it here + // defer conn.Close() + + // Create a client instance + client := resource.NewResourceStoreClientGRPC(conn) + + serverConfig.Config.RESTOptionsGetter = apistore.NewRESTOptionsGetter(client, o.RecommendedOptions.Etcd.StorageConfig.Codec) + case grafanaapiserveroptions.StorageTypeUnified: if !s.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorage) { return fmt.Errorf("unified storage requires the unifiedStorage feature flag") diff --git a/pkg/storage/unified/apistore/restoptions.go b/pkg/storage/unified/apistore/restoptions.go new file mode 100644 index 00000000000..3346331094a --- /dev/null +++ b/pkg/storage/unified/apistore/restoptions.go @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package apistore + +import ( + "path" + "time" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/storagebackend" + "k8s.io/apiserver/pkg/storage/storagebackend/factory" + flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request" + "k8s.io/client-go/tools/cache" + + "github.com/grafana/grafana/pkg/storage/unified/resource" +) + +var _ generic.RESTOptionsGetter = (*RESTOptionsGetter)(nil) + +type RESTOptionsGetter struct { + client resource.ResourceStoreClient + Codec runtime.Codec +} + +func NewRESTOptionsGetterForServer(server resource.ResourceServer, codec runtime.Codec) *RESTOptionsGetter { + return &RESTOptionsGetter{ + client: resource.NewLocalResourceStoreClient(server), + Codec: codec, + } +} + +func NewRESTOptionsGetter(client resource.ResourceStoreClient, codec runtime.Codec) *RESTOptionsGetter { + return &RESTOptionsGetter{ + client: client, + Codec: codec, + } +} + +func (f *RESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) { + storageConfig := &storagebackend.ConfigForResource{ + Config: storagebackend.Config{ + Type: "custom", + Prefix: "", + Transport: storagebackend.TransportConfig{ + ServerList: []string{ + // ??? string(connectionInfo), + }, + }, + Codec: f.Codec, + EncodeVersioner: nil, + Transformer: nil, + CompactionInterval: 0, + CountMetricPollPeriod: 0, + DBMetricPollInterval: 0, + HealthcheckTimeout: 0, + ReadycheckTimeout: 0, + StorageObjectCountTracker: nil, + }, + GroupResource: resource, + } + + ret := generic.RESTOptions{ + StorageConfig: storageConfig, + Decorator: func( + config *storagebackend.ConfigForResource, + resourcePrefix string, + keyFunc func(obj runtime.Object) (string, error), + newFunc func() runtime.Object, + newListFunc func() runtime.Object, + getAttrsFunc storage.AttrFunc, + trigger storage.IndexerFuncs, + indexers *cache.Indexers, + ) (storage.Interface, factory.DestroyFunc, error) { + return NewStorage(config, resource, f.client, f.Codec, keyFunc, newFunc, newListFunc, getAttrsFunc) + }, + DeleteCollectionWorkers: 0, + EnableGarbageCollection: false, + ResourcePrefix: path.Join(storageConfig.Prefix, resource.Group, resource.Resource), + CountMetricPollPeriod: 1 * time.Second, + StorageObjectCountTracker: flowcontrolrequest.NewStorageObjectCountTracker(), + } + + return ret, nil +} diff --git a/pkg/storage/unified/apistore/storage.go b/pkg/storage/unified/apistore/storage.go new file mode 100644 index 00000000000..c4cd32f244b --- /dev/null +++ b/pkg/storage/unified/apistore/storage.go @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Provenance-includes-location: https://github.com/kubernetes-sigs/apiserver-runtime/blob/main/pkg/experimental/storage/filepath/jsonfile_rest.go +// Provenance-includes-license: Apache-2.0 +// Provenance-includes-copyright: The Kubernetes Authors. + +package apistore + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "reflect" + "strconv" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/storagebackend" + "k8s.io/apiserver/pkg/storage/storagebackend/factory" + + grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" + + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/storage/unified/resource" +) + +const SortByKey = "grafana.app/sortBy" + +var _ storage.Interface = (*Storage)(nil) + +// Storage implements storage.Interface and stores resources in unified storage +type Storage struct { + config *storagebackend.ConfigForResource + store resource.ResourceStoreClient + gr schema.GroupResource + codec runtime.Codec + keyFunc func(obj runtime.Object) (string, error) + newFunc func() runtime.Object + newListFunc func() runtime.Object + getAttrsFunc storage.AttrFunc + // trigger storage.IndexerFuncs + // indexers *cache.Indexers +} + +func NewStorage( + config *storagebackend.ConfigForResource, + gr schema.GroupResource, + store resource.ResourceStoreClient, + codec runtime.Codec, + keyFunc func(obj runtime.Object) (string, error), + newFunc func() runtime.Object, + newListFunc func() runtime.Object, + getAttrsFunc storage.AttrFunc, +) (storage.Interface, factory.DestroyFunc, error) { + return &Storage{ + config: config, + gr: gr, + codec: codec, + store: store, + keyFunc: keyFunc, + newFunc: newFunc, + newListFunc: newListFunc, + getAttrsFunc: getAttrsFunc, + }, nil, nil +} + +func errorWrap(status *resource.StatusResult) error { + if status != nil { + return &apierrors.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: status.Code, + Reason: metav1.StatusReason(status.Reason), + Message: status.Message, + }} + } + return nil +} + +func getKey(val string) (*resource.ResourceKey, error) { + k, err := grafanaregistry.ParseKey(val) + if err != nil { + return nil, err + } + if k.Group == "" { + return nil, apierrors.NewInternalError(fmt.Errorf("missing group in request")) + } + if k.Resource == "" { + return nil, apierrors.NewInternalError(fmt.Errorf("missing resource in request")) + } + return &resource.ResourceKey{ + Namespace: k.Namespace, + Group: k.Group, + Resource: k.Resource, + Name: k.Name, + }, err +} + +// Create adds a new object at a key unless it already exists. 'ttl' is time-to-live +// in seconds (0 means forever). If no error is returned and out is not nil, out will be +// set to the read value from database. +func (s *Storage) Create(ctx context.Context, key string, obj runtime.Object, out runtime.Object, ttl uint64) error { + k, err := getKey(key) + if err != nil { + return err + } + + err = s.Versioner().PrepareObjectForStorage(obj) + if err != nil { + return err + } + + var buf bytes.Buffer + err = s.codec.Encode(obj, &buf) + if err != nil { + return err + } + + cmd := &resource.CreateRequest{ + Key: k, + Value: buf.Bytes(), + } + + // TODO?? blob from context? + + rsp, err := s.store.Create(ctx, cmd) + if err != nil { + return err + } + err = errorWrap(rsp.Status) + if err != nil { + return err + } + + if rsp.Status != nil { + return fmt.Errorf("error in status %+v", rsp.Status) + } + + // Create into the out value + _, _, err = s.codec.Decode(rsp.Value, nil, out) + if err != nil { + return err + } + after, err := utils.MetaAccessor(out) + if err != nil { + return err + } + after.SetResourceVersionInt64(rsp.ResourceVersion) + return nil +} + +// Delete removes the specified key and returns the value that existed at that spot. +// If key didn't exist, it will return NotFound storage error. +// If 'cachedExistingObject' is non-nil, it can be used as a suggestion about the +// current version of the object to avoid read operation from storage to get it. +// However, the implementations have to retry in case suggestion is stale. +func (s *Storage) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc, cachedExistingObject runtime.Object) error { + k, err := getKey(key) + if err != nil { + return err + } + + if validateDeletion != nil { + return fmt.Errorf("not supported (validate deletion)") + } + + cmd := &resource.DeleteRequest{Key: k} + if preconditions != nil { + if preconditions.ResourceVersion != nil { + cmd.ResourceVersion, err = strconv.ParseInt(*preconditions.ResourceVersion, 10, 64) + if err != nil { + return err + } + } + if preconditions.UID != nil { + cmd.Uid = string(*preconditions.UID) + } + } + + rsp, err := s.store.Delete(ctx, cmd) + if err != nil { + return err + } + err = errorWrap(rsp.Status) + if err != nil { + return err + } + return nil +} + +// Watch begins watching the specified key. Events are decoded into API objects, +// and any items selected by 'p' are sent down to returned watch.Interface. +// resourceVersion may be used to specify what version to begin watching, +// which should be the current resourceVersion, and no longer rv+1 +// (e.g. reconnecting without missing any updates). +// If resource version is "0", this interface will get current object at given key +// and send it in an "ADDED" event, before watch starts. +func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) { + listopts, _, err := toListRequest(key, opts) + if err != nil { + return nil, err + } + if listopts == nil { + return watch.NewEmptyWatch(), nil + } + + cmd := &resource.WatchRequest{ + Since: listopts.ResourceVersion, + Options: listopts.Options, + SendInitialEvents: false, + AllowWatchBookmarks: opts.Predicate.AllowWatchBookmarks, + } + if opts.SendInitialEvents != nil { + cmd.SendInitialEvents = *opts.SendInitialEvents + } + + client, err := s.store.Watch(ctx, cmd) + if err != nil { + // if the context was canceled, just return a new empty watch + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.EOF) { + return watch.NewEmptyWatch(), nil + } + return nil, err + } + + reporter := apierrors.NewClientErrorReporter(500, "WATCH", "") + decoder := &streamDecoder{ + client: client, + newFunc: s.newFunc, + opts: opts, + codec: s.codec, + } + + return watch.NewStreamWatcher(decoder, reporter), nil +} + +// Get decodes object found at key into objPtr. On a not found error, will either +// return a zero object of the requested type, or an error, depending on 'opts.ignoreNotFound'. +// Treats empty responses and nil response nodes exactly like a not found error. +// The returned contents may be delayed, but it is guaranteed that they will +// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'. +func (s *Storage) Get(ctx context.Context, key string, opts storage.GetOptions, objPtr runtime.Object) error { + var err error + req := &resource.ReadRequest{} + req.Key, err = getKey(key) + if err != nil { + return err + } + + if opts.ResourceVersion != "" { + req.ResourceVersion, err = strconv.ParseInt(opts.ResourceVersion, 10, 64) + if err != nil { + return err + } + } + + rsp, err := s.store.Read(ctx, req) + if err != nil { + return err + } + err = errorWrap(rsp.Status) + if err != nil { + return err + } + + _, _, err = s.codec.Decode(rsp.Value, &schema.GroupVersionKind{}, objPtr) + if err != nil { + return err + } + obj, err := utils.MetaAccessor(objPtr) + if err != nil { + return err + } + obj.SetResourceVersionInt64(rsp.ResourceVersion) + return nil +} + +func toListRequest(key string, opts storage.ListOptions) (*resource.ListRequest, storage.SelectionPredicate, error) { + predicate := opts.Predicate + k, err := getKey(key) + if err != nil { + return nil, predicate, err + } + req := &resource.ListRequest{ + Limit: opts.Predicate.Limit, + Options: &resource.ListOptions{ + Key: k, + }, + NextPageToken: predicate.Continue, + } + + if opts.Predicate.Label != nil && !opts.Predicate.Label.Empty() { + requirements, selectable := opts.Predicate.Label.Requirements() + if !selectable { + return nil, predicate, nil // not selectable + } + + for _, r := range requirements { + v := r.Key() + + // TODO?? sorting in list not supported + if v == SortByKey { + if r.Operator() != selection.Equals { + return nil, predicate, apierrors.NewBadRequest("invalid sort operation // " + r.String()) + } + parts := strings.Split(v, " ") + if len(parts) != 2 { + return nil, predicate, apierrors.NewBadRequest("invalid sort operation // " + r.String()) + } + sort := &resource.Sort{Field: parts[0]} + switch parts[1] { + case "ASC": + sort.Order = resource.Sort_ASC + case "DESC": + sort.Order = resource.Sort_DESC + default: + return nil, predicate, apierrors.NewBadRequest("invalid sort order // " + r.String()) + } + // TODO! Must update the predicate! + continue + } + + req.Options.Labels = append(req.Options.Labels, &resource.Requirement{ + Key: v, + Operator: string(r.Operator()), + Values: r.Values().List(), + }) + } + } + + if opts.ResourceVersion != "" { + rv, err := strconv.ParseInt(opts.ResourceVersion, 10, 64) + if err != nil { + return nil, predicate, apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %s", opts.ResourceVersion)) + } + req.ResourceVersion = rv + } + + switch opts.ResourceVersionMatch { + case "", metav1.ResourceVersionMatchNotOlderThan: + req.VersionMatch = resource.ResourceVersionMatch_NotOlderThan + case metav1.ResourceVersionMatchExact: + req.VersionMatch = resource.ResourceVersionMatch_Exact + default: + return nil, predicate, apierrors.NewBadRequest( + fmt.Sprintf("unsupported version match: %v", opts.ResourceVersionMatch), + ) + } + + return req, predicate, nil +} + +// GetList unmarshalls objects found at key into a *List api object (an object +// that satisfies runtime.IsList definition). +// If 'opts.Recursive' is false, 'key' is used as an exact match. If `opts.Recursive' +// is true, 'key' is used as a prefix. +// The returned contents may be delayed, but it is guaranteed that they will +// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'. +func (s *Storage) GetList(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + req, predicate, err := toListRequest(key, opts) + if err != nil { + return err + } + + rsp, err := s.store.List(ctx, req) + if err != nil { + return err + } + + listPtr, err := meta.GetItemsPtr(listObj) + if err != nil { + return err + } + v, err := conversion.EnforcePtr(listPtr) + if err != nil { + return err + } + + for _, item := range rsp.Items { + tmp := s.newFunc() + + tmp, _, err = s.codec.Decode(item.Value, nil, tmp) + if err != nil { + return err + } + obj, err := utils.MetaAccessor(tmp) + if err != nil { + return err + } + obj.SetResourceVersionInt64(item.ResourceVersion) + + // apply any predicates not handled in storage + matches, err := predicate.Matches(tmp) + if err != nil { + return apierrors.NewInternalError(err) + } + if !matches { + continue + } + + v.Set(reflect.Append(v, reflect.ValueOf(tmp).Elem())) + } + + listAccessor, err := meta.ListAccessor(listObj) + if err != nil { + return err + } + if rsp.NextPageToken != "" { + listAccessor.SetContinue(rsp.NextPageToken) + } + if rsp.RemainingItemCount > 0 { + listAccessor.SetRemainingItemCount(&rsp.RemainingItemCount) + } + if rsp.ResourceVersion > 0 { + listAccessor.SetResourceVersion(strconv.FormatInt(rsp.ResourceVersion, 10)) + } + return nil +} + +// GuaranteedUpdate keeps calling 'tryUpdate()' to update key 'key' (of type 'destination') +// retrying the update until success if there is index conflict. +// Note that object passed to tryUpdate may change across invocations of tryUpdate() if +// other writers are simultaneously updating it, so tryUpdate() needs to take into account +// the current contents of the object when deciding how the update object should look. +// If the key doesn't exist, it will return NotFound storage error if ignoreNotFound=false +// else `destination` will be set to the zero value of it's type. +// If the eventual successful invocation of `tryUpdate` returns an output with the same serialized +// contents as the input, it won't perform any update, but instead set `destination` to an object with those +// contents. +// If 'cachedExistingObject' is non-nil, it can be used as a suggestion about the +// current version of the object to avoid read operation from storage to get it. +// However, the implementations have to retry in case suggestion is stale. +func (s *Storage) GuaranteedUpdate( + ctx context.Context, + key string, + destination runtime.Object, + ignoreNotFound bool, + preconditions *storage.Preconditions, + tryUpdate storage.UpdateFunc, + cachedExistingObject runtime.Object, +) error { + k, err := getKey(key) + if err != nil { + return err + } + + // Get the current version + err = s.Get(ctx, key, storage.GetOptions{}, destination) + if err != nil { + if ignoreNotFound && apierrors.IsNotFound(err) { + // destination is already set to zero value + // we'll create the resource + } else { + return err + } + } + + accessor, err := utils.MetaAccessor(destination) + if err != nil { + return err + } + + // Early optimistic locking failure + previousVersion, _ := strconv.ParseInt(accessor.GetResourceVersion(), 10, 64) + if preconditions != nil { + if preconditions.ResourceVersion != nil { + rv, err := strconv.ParseInt(*preconditions.ResourceVersion, 10, 64) + if err != nil { + return err + } + if rv != previousVersion { + return fmt.Errorf("optimistic locking mismatch (previousVersion mismatch)") + } + } + + if preconditions.UID != nil { + if accessor.GetUID() != *preconditions.UID { + return fmt.Errorf("optimistic locking mismatch (UID mismatch)") + } + } + } + + res := &storage.ResponseMeta{} + updatedObj, _, err := tryUpdate(destination, *res) + if err != nil { + var statusErr *apierrors.StatusError + if errors.As(err, &statusErr) { + // For now, forbidden may come from a mutation handler + if statusErr.ErrStatus.Reason == metav1.StatusReasonForbidden { + return statusErr + } + } + return apierrors.NewInternalError( + fmt.Errorf("could not successfully update object. key=%s, err=%s", k.String(), err.Error()), + ) + } + + var buf bytes.Buffer + err = s.codec.Encode(updatedObj, &buf) + if err != nil { + return err + } + + req := &resource.UpdateRequest{Key: k, Value: buf.Bytes()} + rsp, err := s.store.Update(ctx, req) + if err != nil { + return err + } + err = errorWrap(rsp.Status) + if err != nil { + return err + } + + // Read the mutated fields the response field + _, _, err = s.codec.Decode(rsp.Value, nil, destination) + if err != nil { + return err + } + accessor, err = utils.MetaAccessor(destination) + if err != nil { + return err + } + accessor.SetResourceVersionInt64(rsp.ResourceVersion) + return nil +} + +// Count returns number of different entries under the key (generally being path prefix). +func (s *Storage) Count(key string) (int64, error) { + return 0, nil +} + +func (s *Storage) Versioner() storage.Versioner { + return &storage.APIObjectVersioner{} +} + +func (s *Storage) RequestWatchProgress(ctx context.Context) error { + return nil +} diff --git a/pkg/storage/unified/apistore/stream.go b/pkg/storage/unified/apistore/stream.go new file mode 100644 index 00000000000..e5c66b59b22 --- /dev/null +++ b/pkg/storage/unified/apistore/stream.go @@ -0,0 +1,203 @@ +package apistore + +import ( + "errors" + "fmt" + "io" + + grpcCodes "google.golang.org/grpc/codes" + grpcStatus "google.golang.org/grpc/status" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/storage" + "k8s.io/klog/v2" + + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/storage/unified/resource" +) + +type streamDecoder struct { + client resource.ResourceStore_WatchClient + newFunc func() runtime.Object + opts storage.ListOptions + codec runtime.Codec +} + +func (d *streamDecoder) toObject(w *resource.WatchEvent_Resource) (runtime.Object, error) { + obj, _, err := d.codec.Decode(w.Value, nil, d.newFunc()) + if err == nil { + accessor, err := utils.MetaAccessor(obj) + if err != nil { + return nil, err + } + accessor.SetResourceVersionInt64(w.Version) + } + return obj, err +} + +func (d *streamDecoder) Decode() (action watch.EventType, object runtime.Object, err error) { +decode: + for { + err := d.client.Context().Err() + if err != nil { + klog.Errorf("client: context error: %s\n", err) + return watch.Error, nil, err + } + + evt, err := d.client.Recv() + if errors.Is(err, io.EOF) { + return watch.Error, nil, err + } + + if grpcStatus.Code(err) == grpcCodes.Canceled { + return watch.Error, nil, err + } + + if err != nil { + klog.Errorf("client: error receiving result: %s", err) + return watch.Error, nil, err + } + + // Error event + if evt.Type == resource.WatchEvent_ERROR { + err = fmt.Errorf("stream error") + klog.Errorf("client: error receiving result: %s", err) + return watch.Error, nil, err + } + + if evt.Resource == nil { + klog.Errorf("client: received nil \n") + continue decode + } + + if evt.Type == resource.WatchEvent_BOOKMARK { + obj := d.newFunc() + + // here k8s expects an empty object with just resource version and k8s.io/initial-events-end annotation + accessor, err := utils.MetaAccessor(obj) + if err != nil { + klog.Errorf("error getting object accessor: %s", err) + return watch.Error, nil, err + } + + accessor.SetResourceVersionInt64(evt.Resource.Version) + accessor.SetAnnotations(map[string]string{"k8s.io/initial-events-end": "true"}) + return watch.Bookmark, obj, nil + } + + obj, err := d.toObject(evt.Resource) + if err != nil { + klog.Errorf("error decoding entity: %s", err) + return watch.Error, nil, err + } + + var watchAction watch.EventType + switch evt.Type { + case resource.WatchEvent_ADDED: + // apply any predicates not handled in storage + matches, err := d.opts.Predicate.Matches(obj) + if err != nil { + klog.Errorf("error matching object: %s", err) + return watch.Error, nil, err + } + if !matches { + continue decode + } + + watchAction = watch.Added + case resource.WatchEvent_MODIFIED: + watchAction = watch.Modified + + // apply any predicates not handled in storage + matches, err := d.opts.Predicate.Matches(obj) + if err != nil { + klog.Errorf("error matching object: %s", err) + return watch.Error, nil, err + } + + // if we have a previous object, check if it matches + prevMatches := false + var prevObj runtime.Object + if evt.Previous != nil { + prevObj, err = d.toObject(evt.Previous) + if err != nil { + klog.Errorf("error decoding entity: %s", err) + return watch.Error, nil, err + } + + // apply any predicates not handled in storage + prevMatches, err = d.opts.Predicate.Matches(prevObj) + if err != nil { + klog.Errorf("error matching object: %s", err) + return watch.Error, nil, err + } + } + + if !matches { + if !prevMatches { + continue decode + } + + // if the object didn't match, send a Deleted event + watchAction = watch.Deleted + + // here k8s expects the previous object but with the new resource version + obj = prevObj + + accessor, err := utils.MetaAccessor(obj) + if err != nil { + klog.Errorf("error getting object accessor: %s", err) + return watch.Error, nil, err + } + + accessor.SetResourceVersionInt64(evt.Resource.Version) + } else if !prevMatches { + // if the object didn't previously match, send an Added event + watchAction = watch.Added + } + case resource.WatchEvent_DELETED: + watchAction = watch.Deleted + + // if we have a previous object, return that in the deleted event + if evt.Previous != nil { + obj, err = d.toObject(evt.Previous) + if err != nil { + klog.Errorf("error decoding entity: %s", err) + return watch.Error, nil, err + } + + // here k8s expects the previous object but with the new resource version + accessor, err := utils.MetaAccessor(obj) + if err != nil { + klog.Errorf("error getting object accessor: %s", err) + return watch.Error, nil, err + } + + accessor.SetResourceVersionInt64(evt.Resource.Version) + } + + // apply any predicates not handled in storage + matches, err := d.opts.Predicate.Matches(obj) + if err != nil { + klog.Errorf("error matching object: %s", err) + return watch.Error, nil, err + } + if !matches { + continue decode + } + default: + watchAction = watch.Error + } + + return watchAction, obj, nil + } +} + +func (d *streamDecoder) Close() { + err := d.client.CloseSend() + if err != nil { + klog.Errorf("error closing watch stream: %s", err) + } +} + +var _ watch.Decoder = (*streamDecoder)(nil) diff --git a/pkg/storage/unified/apistore/test/watch_test.go b/pkg/storage/unified/apistore/test/watch_test.go new file mode 100644 index 00000000000..cc525c3a62d --- /dev/null +++ b/pkg/storage/unified/apistore/test/watch_test.go @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher_test.go +// Provenance-includes-license: Apache-2.0 +// Provenance-includes-copyright: The Kubernetes Authors. + +package test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/apitesting" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/apis/example" + examplev1 "k8s.io/apiserver/pkg/apis/example/v1" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/storagebackend" + "k8s.io/apiserver/pkg/storage/storagebackend/factory" + storagetesting "k8s.io/apiserver/pkg/storage/testing" + + grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/apiserver/storage/entity" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/sqlstore" + entityStore "github.com/grafana/grafana/pkg/services/store/entity" + "github.com/grafana/grafana/pkg/services/store/entity/db/dbimpl" + "github.com/grafana/grafana/pkg/services/store/entity/sqlstash" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tests/testinfra" + "github.com/grafana/grafana/pkg/tests/testsuite" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +func TestMain(m *testing.M) { + testsuite.Run(m) +} + +func createTestContext(t *testing.T) (entityStore.EntityStoreClient, factory.DestroyFunc) { + t.Helper() + + grafDir, cfgPath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + EnableFeatureToggles: []string{ + featuremgmt.FlagGrpcServer, + featuremgmt.FlagUnifiedStorage, + }, + AppModeProduction: false, // required for migrations to run + GRPCServerAddress: "127.0.0.1:0", // :0 for choosing the port automatically + }) + + cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{Config: cfgPath, HomePath: grafDir}) + assert.NoError(t, err) + + featureManager, err := featuremgmt.ProvideManagerService(cfg) + assert.NoError(t, err) + + featureToggles := featuremgmt.ProvideToggles(featureManager) + + db := sqlstore.InitTestDBWithMigration(t, nil, sqlstore.InitTestDBOpt{EnsureDefaultOrgAndUser: false}) + require.NoError(t, err) + + eDB, err := dbimpl.ProvideEntityDB(db, cfg, featureToggles, nil) + require.NoError(t, err) + + err = eDB.Init() + require.NoError(t, err) + + traceConfig, err := tracing.ParseTracingConfig(cfg) + require.NoError(t, err) + tracer, err := tracing.ProvideService(traceConfig) + require.NoError(t, err) + store, err := sqlstash.ProvideSQLEntityServer(eDB, tracer) + require.NoError(t, err) + + client := entityStore.NewEntityStoreClientLocal(store) + + return client, func() { store.Stop() } +} + +func init() { + metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) + utilruntime.Must(example.AddToScheme(scheme)) + utilruntime.Must(examplev1.AddToScheme(scheme)) +} + +type setupOptions struct { + codec runtime.Codec + newFunc func() runtime.Object + newListFunc func() runtime.Object + prefix string + resourcePrefix string + groupResource schema.GroupResource +} + +type setupOption func(*setupOptions, *testing.T) + +func withDefaults(options *setupOptions, t *testing.T) { + options.codec = apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion) + options.newFunc = newPod + options.newListFunc = newPodList + options.prefix = t.TempDir() + options.resourcePrefix = "/pods" + options.groupResource = schema.GroupResource{Resource: "pods"} +} + +var _ setupOption = withDefaults + +func testSetup(t *testing.T, opts ...setupOption) (context.Context, storage.Interface, factory.DestroyFunc, error) { + setupOpts := setupOptions{} + opts = append([]setupOption{withDefaults}, opts...) + for _, opt := range opts { + opt(&setupOpts, t) + } + + config := storagebackend.NewDefaultConfig(setupOpts.prefix, setupOpts.codec) + + client, destroyFunc := createTestContext(t) + + store, _, err := entity.NewStorage( + config.ForResource(setupOpts.groupResource), + setupOpts.groupResource, + client, + setupOpts.codec, + func(obj runtime.Object) (string, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return "", err + } + keyFn := grafanaregistry.NamespaceKeyFunc(setupOpts.groupResource) + return keyFn(genericapirequest.WithNamespace(genericapirequest.NewContext(), accessor.GetNamespace()), accessor.GetName()) + }, + setupOpts.newFunc, + setupOpts.newListFunc, + storage.DefaultNamespaceScopedAttr, + ) + if err != nil { + return nil, nil, nil, err + } + + ctx := context.Background() + + return ctx, store, destroyFunc, nil +} + +func TestIntegrationWatch(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatch(ctx, t, store) +} + +func TestIntegrationClusterScopedWatch(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestClusterScopedWatch(ctx, t, store) +} + +func TestIntegrationNamespaceScopedWatch(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestNamespaceScopedWatch(ctx, t, store) +} + +func TestIntegrationDeleteTriggerWatch(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestDeleteTriggerWatch(ctx, t, store) +} + +func TestIntegrationWatchFromZero(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchFromZero(ctx, t, store, nil) +} + +// TestWatchFromNonZero tests that +// - watch from non-0 should just watch changes after given version +func TestIntegrationWatchFromNonZero(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchFromNonZero(ctx, t, store) +} + +/* +// TODO this times out, we need to buffer events +func TestIntegrationDelayedWatchDelivery(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestDelayedWatchDelivery(ctx, t, store) +} +*/ + +/* func TestIntegrationWatchError(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, _ := testSetup(t) + storagetesting.RunTestWatchError(ctx, t, &storeWithPrefixTransformer{store}) +} */ + +func TestIntegrationWatchContextCancel(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchContextCancel(ctx, t, store) +} + +func TestIntegrationWatcherTimeout(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatcherTimeout(ctx, t, store) +} + +func TestIntegrationWatchDeleteEventObjectHaveLatestRV(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchDeleteEventObjectHaveLatestRV(ctx, t, store) +} + +// TODO: enable when we support flow control and priority fairness +/* func TestIntegrationWatchInitializationSignal(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchInitializationSignal(ctx, t, store) +} */ + +/* func TestIntegrationProgressNotify(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunOptionalTestProgressNotify(ctx, t, store) +} */ + +// TestWatchDispatchBookmarkEvents makes sure that +// setting allowWatchBookmarks query param against +// etcd implementation doesn't have any effect. +func TestIntegrationWatchDispatchBookmarkEvents(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchDispatchBookmarkEvents(ctx, t, store, false) +} + +func TestIntegrationSendInitialEventsBackwardCompatibility(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunSendInitialEventsBackwardCompatibility(ctx, t, store) +} + +// TODO this test times out +func TestIntegrationEtcdWatchSemantics(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Skip("In maintenance") + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunWatchSemantics(ctx, t, store) +} + +/* +// TODO this test times out +func TestIntegrationEtcdWatchSemanticInitialEventsExtended(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunWatchSemanticInitialEventsExtended(ctx, t, store) +} +*/ + +func newPod() runtime.Object { + return &example.Pod{} +} + +func newPodList() runtime.Object { + return &example.PodList{} +} diff --git a/pkg/storage/unified/entitybridge/decoder.go b/pkg/storage/unified/entitybridge/decoder.go new file mode 100644 index 00000000000..3e7335cffd9 --- /dev/null +++ b/pkg/storage/unified/entitybridge/decoder.go @@ -0,0 +1,119 @@ +package entitybridge + +import ( + "errors" + "io" + "time" + + grpcCodes "google.golang.org/grpc/codes" + grpcStatus "google.golang.org/grpc/status" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/klog/v2" + + "github.com/grafana/grafana/pkg/apimachinery/utils" + entitystore "github.com/grafana/grafana/pkg/services/apiserver/storage/entity" + "github.com/grafana/grafana/pkg/services/store/entity" + "github.com/grafana/grafana/pkg/storage/unified/resource" +) + +type decoder struct { + client entity.EntityStore_WatchClient +} + +// Any errors will end the stream +func (d *decoder) next() (*resource.WrittenEvent, error) { +decode: + for { + err := d.client.Context().Err() + if err != nil { + klog.Errorf("client: context error: %s\n", err) + return nil, err + } + + rsp, err := d.client.Recv() + if errors.Is(err, io.EOF) { + return nil, err + } + + if grpcStatus.Code(err) == grpcCodes.Canceled { + return nil, err + } + + if err != nil { + klog.Errorf("client: error receiving result: %s", err) + return nil, err + } + + if rsp.Entity == nil { + klog.Errorf("client: received nil entity\n") + continue decode + } + + event := resource.WriteEvent{ + Key: &resource.ResourceKey{ + Group: rsp.Entity.Namespace, + Resource: rsp.Entity.Resource, + Namespace: rsp.Entity.Namespace, + Name: rsp.Entity.Name, + }, + } + + switch rsp.Entity.Action { + case entity.Entity_CREATED: + event.Type = resource.WatchEvent_ADDED + case entity.Entity_UPDATED: + event.Type = resource.WatchEvent_MODIFIED + case entity.Entity_DELETED: + event.Type = resource.WatchEvent_DELETED + default: + klog.Errorf("unsupported action\n") + continue decode + } + + // Now decode the bytes into an object + obj := &unstructured.Unstructured{} + err = entitystore.EntityToRuntimeObject(rsp.Entity, obj, unstructured.UnstructuredJSONScheme) + if err != nil { + klog.Errorf("error decoding entity: %s", err) + return nil, err + } + + event.Value, err = obj.MarshalJSON() + if err != nil { + return nil, err + } + event.Object, err = utils.MetaAccessor(obj) + if err != nil { + return nil, err + } + + // Decode the old value + if rsp.Previous != nil { + err = entitystore.EntityToRuntimeObject(rsp.Previous, obj, unstructured.UnstructuredJSONScheme) + if err != nil { + klog.Errorf("error decoding entity: %s", err) + return nil, err + } + event.ObjectOld, err = utils.MetaAccessor(obj) + if err != nil { + return nil, err + } + event.PreviousRV, err = event.ObjectOld.GetResourceVersionInt64() + if err != nil { + return nil, err + } + } + return &resource.WrittenEvent{ + ResourceVersion: rsp.Entity.ResourceVersion, + Timestamp: time.Now().UnixMilli(), + WriteEvent: event, + }, nil + } +} + +func (d *decoder) close() { + err := d.client.CloseSend() + if err != nil { + klog.Errorf("error closing watch stream: %s", err) + } +} diff --git a/pkg/storage/unified/entitybridge/entitybridge.go b/pkg/storage/unified/entitybridge/entitybridge.go new file mode 100644 index 00000000000..820d7c0174f --- /dev/null +++ b/pkg/storage/unified/entitybridge/entitybridge.go @@ -0,0 +1,272 @@ +package entitybridge + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "gocloud.dev/blob/fileblob" + "k8s.io/klog/v2" + + grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/store/entity" + "github.com/grafana/grafana/pkg/services/store/entity/db/dbimpl" + "github.com/grafana/grafana/pkg/services/store/entity/sqlstash" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/unified/resource" +) + +// Creates a ResourceServer using the existing entity tables +// NOTE: most of the field values are ignored +func ProvideResourceServer(db db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer tracing.Tracer) (resource.ResourceServer, error) { + opts := resource.ResourceServerOptions{ + Tracer: tracer, + } + + useEntitySQL := true // save in SQL (but watch not working) + + if useEntitySQL { + eDB, err := dbimpl.ProvideEntityDB(db, cfg, features, tracer) + if err != nil { + return nil, err + } + + server, err := sqlstash.ProvideSQLEntityServer(eDB, tracer) + if err != nil { + return nil, err + } + client := entity.NewEntityStoreClientLocal(server) + + // Use this bridge as the resource store + bridge := &entityBridge{ + server: server, + client: client, + } + opts.Backend = bridge + opts.Diagnostics = bridge + opts.Lifecycle = bridge + } else { + dir := filepath.Join(cfg.DataPath, "unistore", "resource") + if err := os.MkdirAll(dir, 0o750); err != nil { + return nil, err + } + + bucket, err := fileblob.OpenBucket(dir, &fileblob.Options{ + CreateDir: true, + Metadata: fileblob.MetadataDontWrite, // skip + }) + if err != nil { + return nil, err + } + opts.Backend, err = resource.NewCDKBackend(context.Background(), resource.CDKBackendOptions{ + Tracer: tracer, + Bucket: bucket, + }) + if err != nil { + return nil, err + } + } + return resource.NewResourceServer(opts) +} + +// This is only created if we use the entity implementation +type entityBridge struct { + client entity.EntityStoreClient + + // When running directly + // (we need the explicit version so we have access to init+stop) + server sqlstash.SqlEntityServer +} + +// Init implements ResourceServer. +func (b *entityBridge) Init() error { + if b.server != nil { + return b.server.Init() + } + return nil +} + +// Stop implements ResourceServer. +func (b *entityBridge) Stop() { + if b.server != nil { + b.server.Stop() + } +} + +// Convert resource key to the entity key +func toEntityKey(key *resource.ResourceKey) string { + e := grafanaregistry.Key{ + Group: key.Group, + Resource: key.Resource, + Namespace: key.Namespace, + Name: key.Name, + } + return e.String() +} + +func (b *entityBridge) WriteEvent(ctx context.Context, event resource.WriteEvent) (int64, error) { + key := toEntityKey(event.Key) + + // Delete does not need to create an entity first + if event.Type == resource.WatchEvent_DELETED { + rsp, err := b.client.Delete(ctx, &entity.DeleteEntityRequest{ + Key: key, + PreviousVersion: event.PreviousRV, + }) + if err != nil { + return 0, err + } + return rsp.Entity.ResourceVersion, err + } + + gvr := event.Object.GetGroupVersionKind() + obj := event.Object + msg := &entity.Entity{ + Key: key, + Group: event.Key.Group, + Resource: event.Key.Resource, + Namespace: event.Key.Namespace, + Name: event.Key.Name, + Guid: string(event.Object.GetUID()), + GroupVersion: gvr.Version, + + Folder: obj.GetFolder(), + Body: event.Value, + Message: event.Object.GetMessage(), + + Labels: obj.GetLabels(), + Size: int64(len(event.Value)), + } + + switch event.Type { + case resource.WatchEvent_ADDED: + msg.Action = entity.Entity_CREATED + rsp, err := b.client.Create(ctx, &entity.CreateEntityRequest{Entity: msg}) + if err != nil { + return 0, err + } + return rsp.Entity.ResourceVersion, err + + case resource.WatchEvent_MODIFIED: + msg.Action = entity.Entity_UPDATED + rsp, err := b.client.Update(ctx, &entity.UpdateEntityRequest{ + Entity: msg, + PreviousVersion: event.PreviousRV, + }) + if err != nil { + return 0, err + } + return rsp.Entity.ResourceVersion, err + + default: + } + + return 0, fmt.Errorf("unsupported operation: %s", event.Type.String()) +} + +func (b *entityBridge) WatchWriteEvents(ctx context.Context) (<-chan *resource.WrittenEvent, error) { + client, err := b.client.Watch(ctx) + if err != nil { + return nil, err + } + + req := &entity.EntityWatchRequest{ + Action: entity.EntityWatchRequest_START, + Labels: map[string]string{}, + WithBody: true, + WithStatus: true, + SendInitialEvents: false, + } + + err = client.Send(req) + if err != nil { + err2 := client.CloseSend() + if err2 != nil { + klog.Errorf("watch close failed: %s\n", err2) + } + return nil, err + } + + reader := &decoder{client} + stream := make(chan *resource.WrittenEvent, 10) + go func() { + for { + evt, err := reader.next() + if err != nil { + reader.close() + close(stream) + return + } + stream <- evt + } + }() + return stream, nil +} + +// IsHealthy implements ResourceServer. +func (b *entityBridge) IsHealthy(ctx context.Context, req *resource.HealthCheckRequest) (*resource.HealthCheckResponse, error) { + rsp, err := b.client.IsHealthy(ctx, &entity.HealthCheckRequest{ + Service: req.Service, // ?? + }) + if err != nil { + return nil, err + } + return &resource.HealthCheckResponse{ + Status: resource.HealthCheckResponse_ServingStatus(rsp.Status), + }, nil +} + +// Read implements ResourceServer. +func (b *entityBridge) Read(ctx context.Context, req *resource.ReadRequest) (*resource.ReadResponse, error) { + v, err := b.client.Read(ctx, &entity.ReadEntityRequest{ + Key: toEntityKey(req.Key), + WithBody: true, + }) + if err != nil { + return nil, err + } + return &resource.ReadResponse{ + ResourceVersion: v.ResourceVersion, + Value: v.Body, + }, nil +} + +// List implements ResourceServer. +func (b *entityBridge) PrepareList(ctx context.Context, req *resource.ListRequest) (*resource.ListResponse, error) { + key := req.Options.Key + query := &entity.EntityListRequest{ + NextPageToken: req.NextPageToken, + Limit: req.Limit, + Key: []string{toEntityKey(key)}, + WithBody: true, + } + + // Assumes everything is equals + if len(req.Options.Labels) > 0 { + query.Labels = make(map[string]string) + for _, q := range req.Options.Labels { + query.Labels[q.Key] = q.Values[0] + } + } + + found, err := b.client.List(ctx, query) + if err != nil { + return nil, err + } + + rsp := &resource.ListResponse{ + ResourceVersion: found.ResourceVersion, + NextPageToken: found.NextPageToken, + } + for _, item := range found.Results { + rsp.Items = append(rsp.Items, &resource.ResourceWrapper{ + ResourceVersion: item.ResourceVersion, + Value: item.Body, + }) + } + return rsp, nil +} diff --git a/pkg/storage/unified/resource/buf.gen.yaml b/pkg/storage/unified/resource/buf.gen.yaml new file mode 100644 index 00000000000..f0d8393e9ac --- /dev/null +++ b/pkg/storage/unified/resource/buf.gen.yaml @@ -0,0 +1,10 @@ +version: v1 +plugins: + - plugin: go + out: pkg/storage/unified/resource + opt: paths=source_relative + - plugin: go-grpc + out: pkg/storage/unified/resource + opt: + - paths=source_relative + - require_unimplemented_servers=false diff --git a/pkg/storage/unified/resource/buf.yaml b/pkg/storage/unified/resource/buf.yaml new file mode 100644 index 00000000000..1a5194568a9 --- /dev/null +++ b/pkg/storage/unified/resource/buf.yaml @@ -0,0 +1,7 @@ +version: v1 +breaking: + use: + - FILE +lint: + use: + - DEFAULT diff --git a/pkg/storage/unified/resource/cdk_backend.go b/pkg/storage/unified/resource/cdk_backend.go new file mode 100644 index 00000000000..3d3415131ff --- /dev/null +++ b/pkg/storage/unified/resource/cdk_backend.go @@ -0,0 +1,298 @@ +package resource + +import ( + "bytes" + context "context" + "fmt" + "io" + "sort" + "strconv" + "strings" + "sync" + "time" + + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" + "gocloud.dev/blob" + _ "gocloud.dev/blob/fileblob" + _ "gocloud.dev/blob/memblob" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type CDKBackendOptions struct { + Tracer trace.Tracer + Bucket *blob.Bucket + RootFolder string + + NextResourceVersion NextResourceVersion +} + +func NewCDKBackend(ctx context.Context, opts CDKBackendOptions) (StorageBackend, error) { + if opts.Tracer == nil { + opts.Tracer = noop.NewTracerProvider().Tracer("cdk-appending-store") + } + + if opts.Bucket == nil { + return nil, fmt.Errorf("missing bucket") + } + + found, _, err := opts.Bucket.ListPage(ctx, blob.FirstPageToken, 1, &blob.ListOptions{ + Prefix: opts.RootFolder, + Delimiter: "/", + }) + if err != nil { + return nil, err + } + if found == nil { + return nil, fmt.Errorf("the root folder does not exist") + } + + // This is not safe when running in HA! + if opts.NextResourceVersion == nil { + opts.NextResourceVersion = newResourceVersionCounter(time.Now().UnixMilli()) + } + + return &cdkBackend{ + tracer: opts.Tracer, + bucket: opts.Bucket, + root: opts.RootFolder, + nextRV: opts.NextResourceVersion, + }, nil +} + +type cdkBackend struct { + tracer trace.Tracer + bucket *blob.Bucket + root string + nextRV NextResourceVersion + mutex sync.Mutex + + // Typically one... the server wrapper + subscribers []chan *WrittenEvent +} + +func (s *cdkBackend) getPath(key *ResourceKey, rv int64) string { + var buffer bytes.Buffer + buffer.WriteString(s.root) + + if key.Group == "" { + return buffer.String() + } + buffer.WriteString(key.Group) + + if key.Resource == "" { + return buffer.String() + } + buffer.WriteString("/") + buffer.WriteString(key.Resource) + + if key.Namespace == "" { + if key.Name == "" { + return buffer.String() + } + buffer.WriteString("/__cluster__") + } else { + buffer.WriteString("/") + buffer.WriteString(key.Namespace) + } + + if key.Name == "" { + return buffer.String() + } + buffer.WriteString("/") + buffer.WriteString(key.Name) + + if rv > 0 { + buffer.WriteString(fmt.Sprintf("/%d.json", rv)) + } + return buffer.String() +} + +func (s *cdkBackend) WriteEvent(ctx context.Context, event WriteEvent) (rv int64, err error) { + // Scope the lock + { + s.mutex.Lock() + defer s.mutex.Unlock() + + rv = s.nextRV() + err = s.bucket.WriteAll(ctx, s.getPath(event.Key, rv), event.Value, &blob.WriterOptions{ + ContentType: "application/json", + }) + } + + // Async notify all subscribers + if s.subscribers != nil { + go func() { + write := &WrittenEvent{ + WriteEvent: event, + + Timestamp: time.Now().UnixMilli(), + ResourceVersion: rv, + } + for _, sub := range s.subscribers { + sub <- write + } + }() + } + + return rv, err +} + +// Read implements ResourceStoreServer. +func (s *cdkBackend) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, error) { + rv := req.ResourceVersion + + path := s.getPath(req.Key, req.ResourceVersion) + if rv < 1 { + iter := s.bucket.List(&blob.ListOptions{Prefix: path + "/", Delimiter: "/"}) + for { + obj, err := iter.Next(ctx) + if err == io.EOF { + break + } + if strings.HasSuffix(obj.Key, ".json") { + idx := strings.LastIndex(obj.Key, "/") + 1 + edx := strings.LastIndex(obj.Key, ".") + if idx > 0 { + v, err := strconv.ParseInt(obj.Key[idx:edx], 10, 64) + if err == nil && v > rv { + rv = v + path = obj.Key // find the path with biggest resource version + } + } + } + } + } + + raw, err := s.bucket.ReadAll(ctx, path) + if err == nil && bytes.Contains(raw, []byte(`"DeletedMarker"`)) { + tmp := &unstructured.Unstructured{} + err = tmp.UnmarshalJSON(raw) + if err == nil && tmp.GetKind() == "DeletedMarker" { + return nil, apierrors.NewNotFound(schema.GroupResource{ + Group: req.Key.Group, + Resource: req.Key.Resource, + }, req.Key.Name) + } + } + + return &ReadResponse{ + ResourceVersion: rv, + Value: raw, + }, err +} + +// List implements AppendingStore. +func (s *cdkBackend) PrepareList(ctx context.Context, req *ListRequest) (*ListResponse, error) { + resources, err := buildTree(ctx, s, req.Options.Key) + if err != nil { + return nil, err + } + + rsp := &ListResponse{} + for _, item := range resources { + latest := item.versions[0] + raw, err := s.bucket.ReadAll(ctx, latest.key) + if err != nil { + return nil, err + } + rsp.Items = append(rsp.Items, &ResourceWrapper{ + ResourceVersion: latest.rv, + Value: raw, + }) + } + return rsp, nil +} + +// Watch implements AppendingStore. +func (s *cdkBackend) WatchWriteEvents(ctx context.Context) (<-chan *WrittenEvent, error) { + stream := make(chan *WrittenEvent, 10) + { + s.mutex.Lock() + defer s.mutex.Unlock() + + // Add the event stream + s.subscribers = append(s.subscribers, stream) + } + + // Wait for context done + go func() { + // Wait till the context is done + <-ctx.Done() + + // Then remove the subscription + s.mutex.Lock() + defer s.mutex.Unlock() + + // Copy all streams without our listener + subs := []chan *WrittenEvent{} + for _, sub := range s.subscribers { + if sub != stream { + subs = append(subs, sub) + } + } + s.subscribers = subs + }() + return stream, nil +} + +// group > resource > namespace > name > versions +type cdkResource struct { + prefix string + versions []cdkVersion +} +type cdkVersion struct { + rv int64 + key string +} + +func buildTree(ctx context.Context, s *cdkBackend, key *ResourceKey) ([]cdkResource, error) { + byPrefix := make(map[string]*cdkResource) + + path := s.getPath(key, 0) + iter := s.bucket.List(&blob.ListOptions{Prefix: path, Delimiter: ""}) // "" is recursive + for { + obj, err := iter.Next(ctx) + if err == io.EOF { + break + } + if strings.HasSuffix(obj.Key, ".json") { + idx := strings.LastIndex(obj.Key, "/") + 1 + edx := strings.LastIndex(obj.Key, ".") + if idx > 0 { + rv, err := strconv.ParseInt(obj.Key[idx:edx], 10, 64) + if err == nil { + prefix := obj.Key[:idx] + res, ok := byPrefix[prefix] + if !ok { + res = &cdkResource{prefix: prefix} + byPrefix[prefix] = res + } + + res.versions = append(res.versions, cdkVersion{ + rv: rv, + key: obj.Key, + }) + } + } + } + } + + // Now sort all versions + resources := make([]cdkResource, 0, len(byPrefix)) + for _, res := range byPrefix { + sort.Slice(res.versions, func(i, j int) bool { + return res.versions[i].rv > res.versions[j].rv + }) + resources = append(resources, *res) + } + sort.Slice(resources, func(i, j int) bool { + a := resources[i].versions[0].rv + b := resources[j].versions[0].rv + return a > b + }) + + return resources, nil +} diff --git a/pkg/storage/unified/resource/client_wrapper.go b/pkg/storage/unified/resource/client_wrapper.go new file mode 100644 index 00000000000..7ad67e937d8 --- /dev/null +++ b/pkg/storage/unified/resource/client_wrapper.go @@ -0,0 +1,30 @@ +package resource + +import ( + "github.com/fullstorydev/grpchan" + "github.com/fullstorydev/grpchan/inprocgrpc" + grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" + "google.golang.org/grpc" + + grpcUtils "github.com/grafana/grafana/pkg/storage/unified/resource/grpc" +) + +func NewLocalResourceStoreClient(server ResourceStoreServer) ResourceStoreClient { + channel := &inprocgrpc.Channel{} + + auth := &grpcUtils.Authenticator{} + + channel.RegisterService( + grpchan.InterceptServer( + &ResourceStore_ServiceDesc, + grpcAuth.UnaryServerInterceptor(auth.Authenticate), + grpcAuth.StreamServerInterceptor(auth.Authenticate), + ), + server, + ) + return NewResourceStoreClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)) +} + +func NewResourceStoreClientGRPC(channel *grpc.ClientConn) ResourceStoreClient { + return NewResourceStoreClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor)) +} diff --git a/pkg/storage/unified/resource/deleted.go b/pkg/storage/unified/resource/deleted.go new file mode 100644 index 00000000000..8a943060335 --- /dev/null +++ b/pkg/storage/unified/resource/deleted.go @@ -0,0 +1,37 @@ +package resource + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// This object is written when an object is deleted +type DeletedMarker struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeletedMarker) DeepCopyInto(out *DeletedMarker) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeletedMarker. +func (in *DeletedMarker) DeepCopy() *DeletedMarker { + if in == nil { + return nil + } + out := new(DeletedMarker) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DeletedMarker) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/pkg/storage/unified/resource/event.go b/pkg/storage/unified/resource/event.go new file mode 100644 index 00000000000..fbc2f76c60f --- /dev/null +++ b/pkg/storage/unified/resource/event.go @@ -0,0 +1,92 @@ +package resource + +import ( + context "context" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" +) + +type WriteEvent struct { + Type WatchEvent_Type // ADDED, MODIFIED, DELETED + Key *ResourceKey // the request key + PreviousRV int64 // only for Update+Delete + + // The json payload (without resourceVersion) + Value []byte + + // Access real fields + Object utils.GrafanaMetaAccessor + + // Access to the old metadata + ObjectOld utils.GrafanaMetaAccessor +} + +// WriteEvents after they include a resource version +type WrittenEvent struct { + WriteEvent + + // The resource version + ResourceVersion int64 + + // Timestamp when the event is created + Timestamp int64 +} + +// A function to write events +type EventAppender = func(context.Context, *WriteEvent) (int64, error) + +type writeEventBuilder struct { + EventID int64 + Key *ResourceKey // the request key + Type WatchEvent_Type + + Requester identity.Requester + Object *unstructured.Unstructured + + // Access the raw metadata values + Meta utils.GrafanaMetaAccessor + OldMeta utils.GrafanaMetaAccessor +} + +func newEventFromBytes(value, oldValue []byte) (*writeEventBuilder, error) { + builder := &writeEventBuilder{ + Object: &unstructured.Unstructured{}, + } + err := builder.Object.UnmarshalJSON(value) + if err != nil { + return nil, err + } + builder.Meta, err = utils.MetaAccessor(builder.Object) + if err != nil { + return nil, err + } + + if oldValue == nil { + builder.Type = WatchEvent_ADDED + } else { + builder.Type = WatchEvent_MODIFIED + + temp := &unstructured.Unstructured{} + err = temp.UnmarshalJSON(oldValue) + if err != nil { + return nil, err + } + builder.OldMeta, err = utils.MetaAccessor(temp) + if err != nil { + return nil, err + } + } + return builder, nil +} + +func (b *writeEventBuilder) toEvent() (event WriteEvent, err error) { + event.Key = b.Key + event.Type = b.Type + event.ObjectOld = b.OldMeta + event.Object = b.Meta + event.Value, err = b.Object.MarshalJSON() + return // includes the named values +} diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 52164669af4..4574bb9f95f 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -3,24 +3,63 @@ module github.com/grafana/grafana/pkg/storage/unified/resource go 1.21.10 require ( + github.com/fullstorydev/grpchan v1.1.1 + github.com/google/uuid v1.6.0 github.com/grafana/authlib v0.0.0-20240611075137-331cbe4e840f + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 + github.com/prometheus/client_golang v1.19.0 github.com/stretchr/testify v1.9.0 + go.opentelemetry.io/otel/trace v1.26.0 + gocloud.dev v0.25.0 google.golang.org/grpc v1.64.0 + google.golang.org/protobuf v1.34.1 + k8s.io/apimachinery v0.29.3 ) require ( + cloud.google.com/go v0.112.1 // indirect + cloud.google.com/go/storage v1.38.0 // indirect + github.com/aws/aws-sdk-go v1.51.31 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bufbuild/protocompile v0.4.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect + github.com/jhump/protoreflect v1.15.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.53.0 // indirect + github.com/prometheus/procfs v0.14.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect + go.opentelemetry.io/otel v1.26.0 // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/api v0.176.0 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/protobuf v1.34.1 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index f8a48ef5de5..d1d127ec502 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -1,20 +1,94 @@ +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go/auth v0.2.2 h1:gmxNJs4YZYcw6YvKRtVBaF2fyUE6UrWPyzU8jHvYfmI= +cloud.google.com/go/auth/oauth2adapt v0.2.1 h1:VSPmMmUlT8CkIZ2PzD9AlLN+R3+D1clXMWHHa6vG/Ag= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= +github.com/aws/aws-sdk-go v1.51.31 h1:4TM+sNc+Dzs7wY1sJ0+J8i60c6rkgnKP1pvPx8ghsSY= +github.com/aws/aws-sdk-go-v2 v1.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= +github.com/aws/aws-sdk-go-v2/config v1.15.3 h1:5AlQD0jhVXlGzwo+VORKiUuogkG7pQcLJNzIzK7eodw= +github.com/aws/aws-sdk-go-v2/credentials v1.11.2 h1:RQQ5fzclAKJyY5TvF+fkjJEwzK4hnxQCLOu5JXzDmQo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 h1:LWPg5zjHV9oz/myQr4wMs0gi4CjnDN/ILmyZUFYXZsU= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3 h1:ir7iEq78s4txFGgwcLqD6q9IIPzTQNRJXulJd9h/zQo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 h1:onz/VaaxZ7Z4V+WIN9Txly9XLTmoOh1oJ8XcAC3pako= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 h1:9stUQR/u2KXU6HkFJYlqnZEjBnbgrVbG6I5HN09xZh0= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 h1:by9P+oy3P/CwggN4ClnW2D4oL91QV7pBzBICi1chZvQ= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3 h1:I0dcwWitE752hVSMrsLCxqNQ+UdEp3nACx2bYNMQq+k= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 h1:Gh1Gpyh01Yvn7ilO/b/hr01WgNpaszfbKMUgqM186xQ= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3 h1:BKjwCJPnANbkwQ8vzSbaZDKawwagDubrH/z/c0X+kbQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3 h1:rMPtwA7zzkSQZhhz9U3/SoIDz/NZ7Q+iRn4EIO8rSyU= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 h1:frW4ikGcxfAEDfmQqWgMLp+F1n4nRo9sF39OcIb5BkQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 h1:cJGRyzCSVwZC7zZZ1xbx9m32UnrKydRYhOvcD1NYP9Q= +github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= github.com/grafana/authlib v0.0.0-20240611075137-331cbe4e840f h1:hvRCAv+TgcHu3i/Sd7lFJx84iEtgzDCYuk7OWeXatD0= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= +github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +gocloud.dev v0.25.0 h1:Y7vDq8xj7SyM848KXf32Krda2e6jQ4CLh/mTeCSqXtk= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +google.golang.org/api v0.176.0 h1:dHj1/yv5Dm/eQTXiP9hNCRT3xzJHWXeNdRq29XbMxoE= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/pkg/storage/unified/resource/hooks.go b/pkg/storage/unified/resource/hooks.go new file mode 100644 index 00000000000..44c4a2dca42 --- /dev/null +++ b/pkg/storage/unified/resource/hooks.go @@ -0,0 +1,45 @@ +package resource + +import ( + context "context" + "fmt" + + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +type WriteAccessHooks struct { + // Check if a user has access to write folders + // When this is nil, no resources can have folders configured + Folder func(ctx context.Context, user identity.Requester, uid string) bool + + // When configured, this will make sure a user is allowed to save to a given origin + Origin func(ctx context.Context, user identity.Requester, origin string) bool +} + +type LifecycleHooks interface { + // Called once at initialization + Init() error + + // Stop function -- after calling this, any additional storage functions may error + Stop() +} + +func (a *WriteAccessHooks) CanWriteFolder(ctx context.Context, user identity.Requester, uid string) error { + if a.Folder == nil { + return fmt.Errorf("writing folders is not supported") + } + if !a.Folder(ctx, user, uid) { + return fmt.Errorf("not allowed to write resource to folder") + } + return nil +} + +func (a *WriteAccessHooks) CanWriteOrigin(ctx context.Context, user identity.Requester, uid string) error { + if a.Origin == nil || uid == "UI" { + return nil // default to OK + } + if !a.Origin(ctx, user, uid) { + return fmt.Errorf("not allowed to write resource at origin") + } + return nil +} diff --git a/pkg/storage/unified/resource/keys.go b/pkg/storage/unified/resource/keys.go new file mode 100644 index 00000000000..319cf107f5b --- /dev/null +++ b/pkg/storage/unified/resource/keys.go @@ -0,0 +1,17 @@ +package resource + +func matchesQueryKey(query *ResourceKey, key *ResourceKey) bool { + if query.Group != key.Group { + return false + } + if query.Resource != key.Resource { + return false + } + if query.Namespace != "" && query.Namespace != key.Namespace { + return false + } + if query.Name != "" && query.Name != key.Name { + return false + } + return true +} diff --git a/pkg/storage/unified/resource/keys_test.go b/pkg/storage/unified/resource/keys_test.go new file mode 100644 index 00000000000..deaaff5bedb --- /dev/null +++ b/pkg/storage/unified/resource/keys_test.go @@ -0,0 +1,21 @@ +package resource + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestKeyMatching(t *testing.T) { + t.Run("key matching", func(t *testing.T) { + require.True(t, matchesQueryKey(&ResourceKey{ + Group: "ggg", + Resource: "rrr", + Namespace: "ns", + }, &ResourceKey{ + Group: "ggg", + Resource: "rrr", + Namespace: "ns", + })) + }) +} diff --git a/pkg/storage/unified/resource/metrics.go b/pkg/storage/unified/resource/metrics.go new file mode 100644 index 00000000000..be187eba7b3 --- /dev/null +++ b/pkg/storage/unified/resource/metrics.go @@ -0,0 +1,41 @@ +package resource + +import ( + "sync" + + "github.com/prometheus/client_golang/prometheus" +) + +var ( + once sync.Once + StorageServerMetrics *StorageApiMetrics +) + +type StorageApiMetrics struct { + OptimisticLockFailed *prometheus.CounterVec +} + +func NewStorageMetrics() *StorageApiMetrics { + once.Do(func() { + StorageServerMetrics = &StorageApiMetrics{ + OptimisticLockFailed: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "resource_storage", + Name: "optimistic_lock_failed", + Help: "count of optimistic locks failed", + }, + []string{"action"}, + ), + } + }) + + return StorageServerMetrics +} + +func (s *StorageApiMetrics) Collect(ch chan<- prometheus.Metric) { + s.OptimisticLockFailed.Collect(ch) +} + +func (s *StorageApiMetrics) Describe(ch chan<- *prometheus.Desc) { + s.OptimisticLockFailed.Describe(ch) +} diff --git a/pkg/storage/unified/resource/noop.go b/pkg/storage/unified/resource/noop.go new file mode 100644 index 00000000000..92778817ba1 --- /dev/null +++ b/pkg/storage/unified/resource/noop.go @@ -0,0 +1,41 @@ +package resource + +import ( + "context" +) + +var ( + _ DiagnosticsServer = &noopService{} + _ LifecycleHooks = &noopService{} +) + +// noopService is a helper implementation to simplify tests +// It does nothing except return errors when asked to do anything real +type noopService struct{} + +// Init implements ResourceServer. +func (n *noopService) Init() error { + return nil +} + +// Stop implements ResourceServer. +func (n *noopService) Stop() { + // nothing +} + +// IsHealthy implements ResourceServer. +func (n *noopService) IsHealthy(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) { + return &HealthCheckResponse{ + Status: HealthCheckResponse_SERVING, + }, nil +} + +// Read implements ResourceServer. +func (n *noopService) Read(context.Context, *ReadRequest) (*ReadResponse, error) { + return nil, ErrNotImplementedYet +} + +// List implements ResourceServer. +func (n *noopService) List(context.Context, *ListRequest) (*ListResponse, error) { + return nil, ErrNotImplementedYet +} diff --git a/pkg/storage/unified/resource/resource.pb.go b/pkg/storage/unified/resource/resource.pb.go new file mode 100644 index 00000000000..4f46525bdff --- /dev/null +++ b/pkg/storage/unified/resource/resource.pb.go @@ -0,0 +1,2274 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.1 +// protoc (unknown) +// source: resource.proto + +package resource + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ResourceVersionMatch int32 + +const ( + ResourceVersionMatch_NotOlderThan ResourceVersionMatch = 0 + ResourceVersionMatch_Exact ResourceVersionMatch = 1 +) + +// Enum value maps for ResourceVersionMatch. +var ( + ResourceVersionMatch_name = map[int32]string{ + 0: "NotOlderThan", + 1: "Exact", + } + ResourceVersionMatch_value = map[string]int32{ + "NotOlderThan": 0, + "Exact": 1, + } +) + +func (x ResourceVersionMatch) Enum() *ResourceVersionMatch { + p := new(ResourceVersionMatch) + *p = x + return p +} + +func (x ResourceVersionMatch) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ResourceVersionMatch) Descriptor() protoreflect.EnumDescriptor { + return file_resource_proto_enumTypes[0].Descriptor() +} + +func (ResourceVersionMatch) Type() protoreflect.EnumType { + return &file_resource_proto_enumTypes[0] +} + +func (x ResourceVersionMatch) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ResourceVersionMatch.Descriptor instead. +func (ResourceVersionMatch) EnumDescriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{0} +} + +type Sort_Order int32 + +const ( + Sort_ASC Sort_Order = 0 + Sort_DESC Sort_Order = 1 +) + +// Enum value maps for Sort_Order. +var ( + Sort_Order_name = map[int32]string{ + 0: "ASC", + 1: "DESC", + } + Sort_Order_value = map[string]int32{ + "ASC": 0, + "DESC": 1, + } +) + +func (x Sort_Order) Enum() *Sort_Order { + p := new(Sort_Order) + *p = x + return p +} + +func (x Sort_Order) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Sort_Order) Descriptor() protoreflect.EnumDescriptor { + return file_resource_proto_enumTypes[1].Descriptor() +} + +func (Sort_Order) Type() protoreflect.EnumType { + return &file_resource_proto_enumTypes[1] +} + +func (x Sort_Order) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Sort_Order.Descriptor instead. +func (Sort_Order) EnumDescriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{13, 0} +} + +type WatchEvent_Type int32 + +const ( + WatchEvent_UNKNOWN WatchEvent_Type = 0 + WatchEvent_ADDED WatchEvent_Type = 1 + WatchEvent_MODIFIED WatchEvent_Type = 2 + WatchEvent_DELETED WatchEvent_Type = 3 + WatchEvent_BOOKMARK WatchEvent_Type = 4 + WatchEvent_ERROR WatchEvent_Type = 5 +) + +// Enum value maps for WatchEvent_Type. +var ( + WatchEvent_Type_name = map[int32]string{ + 0: "UNKNOWN", + 1: "ADDED", + 2: "MODIFIED", + 3: "DELETED", + 4: "BOOKMARK", + 5: "ERROR", + } + WatchEvent_Type_value = map[string]int32{ + "UNKNOWN": 0, + "ADDED": 1, + "MODIFIED": 2, + "DELETED": 3, + "BOOKMARK": 4, + "ERROR": 5, + } +) + +func (x WatchEvent_Type) Enum() *WatchEvent_Type { + p := new(WatchEvent_Type) + *p = x + return p +} + +func (x WatchEvent_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (WatchEvent_Type) Descriptor() protoreflect.EnumDescriptor { + return file_resource_proto_enumTypes[2].Descriptor() +} + +func (WatchEvent_Type) Type() protoreflect.EnumType { + return &file_resource_proto_enumTypes[2] +} + +func (x WatchEvent_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use WatchEvent_Type.Descriptor instead. +func (WatchEvent_Type) EnumDescriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{18, 0} +} + +type HealthCheckResponse_ServingStatus int32 + +const ( + HealthCheckResponse_UNKNOWN HealthCheckResponse_ServingStatus = 0 + HealthCheckResponse_SERVING HealthCheckResponse_ServingStatus = 1 + HealthCheckResponse_NOT_SERVING HealthCheckResponse_ServingStatus = 2 + HealthCheckResponse_SERVICE_UNKNOWN HealthCheckResponse_ServingStatus = 3 // Used only by the Watch method. +) + +// Enum value maps for HealthCheckResponse_ServingStatus. +var ( + HealthCheckResponse_ServingStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SERVING", + 2: "NOT_SERVING", + 3: "SERVICE_UNKNOWN", + } + HealthCheckResponse_ServingStatus_value = map[string]int32{ + "UNKNOWN": 0, + "SERVING": 1, + "NOT_SERVING": 2, + "SERVICE_UNKNOWN": 3, + } +) + +func (x HealthCheckResponse_ServingStatus) Enum() *HealthCheckResponse_ServingStatus { + p := new(HealthCheckResponse_ServingStatus) + *p = x + return p +} + +func (x HealthCheckResponse_ServingStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (HealthCheckResponse_ServingStatus) Descriptor() protoreflect.EnumDescriptor { + return file_resource_proto_enumTypes[3].Descriptor() +} + +func (HealthCheckResponse_ServingStatus) Type() protoreflect.EnumType { + return &file_resource_proto_enumTypes[3] +} + +func (x HealthCheckResponse_ServingStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use HealthCheckResponse_ServingStatus.Descriptor instead. +func (HealthCheckResponse_ServingStatus) EnumDescriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{20, 0} +} + +type ResourceKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Namespace (tenant) + Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + // Resource Group + Group string `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"` + // The resource type + Resource string `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + // Resource identifier (unique within namespace+group+resource) + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *ResourceKey) Reset() { + *x = ResourceKey{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceKey) ProtoMessage() {} + +func (x *ResourceKey) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceKey.ProtoReflect.Descriptor instead. +func (*ResourceKey) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{0} +} + +func (x *ResourceKey) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *ResourceKey) GetGroup() string { + if x != nil { + return x.Group + } + return "" +} + +func (x *ResourceKey) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *ResourceKey) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ResourceWrapper struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The resource version + ResourceVersion int64 `protobuf:"varint,1,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // Full kubernetes json bytes (although the resource version may not be accurate) + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *ResourceWrapper) Reset() { + *x = ResourceWrapper{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceWrapper) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceWrapper) ProtoMessage() {} + +func (x *ResourceWrapper) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceWrapper.ProtoReflect.Descriptor instead. +func (*ResourceWrapper) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{1} +} + +func (x *ResourceWrapper) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ResourceWrapper) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +// The history and trash commands need access to commit messages +type ResourceMeta struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The resource version + ResourceVersion int64 `protobuf:"varint,1,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // Size of the full resource body + Size int32 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` + // Hash for the resource + Hash string `protobuf:"bytes,4,opt,name=hash,proto3" json:"hash,omitempty"` + // The kubernetes metadata section (not the full resource) + // https://github.com/kubernetes/kubernetes/blob/v1.30.2/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go#L1496 + PartialObjectMeta []byte `protobuf:"bytes,6,opt,name=partial_object_meta,json=partialObjectMeta,proto3" json:"partial_object_meta,omitempty"` +} + +func (x *ResourceMeta) Reset() { + *x = ResourceMeta{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceMeta) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceMeta) ProtoMessage() {} + +func (x *ResourceMeta) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceMeta.ProtoReflect.Descriptor instead. +func (*ResourceMeta) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{2} +} + +func (x *ResourceMeta) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ResourceMeta) GetSize() int32 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *ResourceMeta) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +func (x *ResourceMeta) GetPartialObjectMeta() []byte { + if x != nil { + return x.PartialObjectMeta + } + return nil +} + +// Status structure is copied from: +// https://github.com/kubernetes/apimachinery/blob/v0.30.1/pkg/apis/meta/v1/generated.proto#L979 +type StatusResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status of the operation. + // One of: "Success" or "Failure". + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // A human-readable description of the status of this operation. + // +optional + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + // A machine-readable description of why this operation is in the + // "Failure" status. If this value is empty there + // is no information available. A Reason clarifies an HTTP status + // code but does not override it. + // +optional + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` + // Suggested HTTP return code for this status, 0 if not set. + // +optional + Code int32 `protobuf:"varint,4,opt,name=code,proto3" json:"code,omitempty"` +} + +func (x *StatusResult) Reset() { + *x = StatusResult{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatusResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusResult) ProtoMessage() {} + +func (x *StatusResult) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusResult.ProtoReflect.Descriptor instead. +func (*StatusResult) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{3} +} + +func (x *StatusResult) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *StatusResult) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *StatusResult) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *StatusResult) GetCode() int32 { + if x != nil { + return x.Code + } + return 0 +} + +type CreateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Requires group+resource to be configuired + // If name is not set, a unique name will be generated + // The resourceVersion should not be set + Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // The resource JSON. + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *CreateRequest) Reset() { + *x = CreateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRequest) ProtoMessage() {} + +func (x *CreateRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRequest.ProtoReflect.Descriptor instead. +func (*CreateRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{4} +} + +func (x *CreateRequest) GetKey() *ResourceKey { + if x != nil { + return x.Key + } + return nil +} + +func (x *CreateRequest) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type CreateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status code + Status *StatusResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // The updated resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // The resource JSON. With managed annotations included + Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *CreateResponse) Reset() { + *x = CreateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateResponse) ProtoMessage() {} + +func (x *CreateResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateResponse.ProtoReflect.Descriptor instead. +func (*CreateResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateResponse) GetStatus() *StatusResult { + if x != nil { + return x.Status + } + return nil +} + +func (x *CreateResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *CreateResponse) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type UpdateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Full key must be set + Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // The current resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // The resource JSON. + Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *UpdateRequest) Reset() { + *x = UpdateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRequest) ProtoMessage() {} + +func (x *UpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRequest.ProtoReflect.Descriptor instead. +func (*UpdateRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{6} +} + +func (x *UpdateRequest) GetKey() *ResourceKey { + if x != nil { + return x.Key + } + return nil +} + +func (x *UpdateRequest) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *UpdateRequest) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type UpdateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status code + Status *StatusResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // The updated resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // The resource JSON. With managed annotations included + Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *UpdateResponse) Reset() { + *x = UpdateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateResponse) ProtoMessage() {} + +func (x *UpdateResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateResponse.ProtoReflect.Descriptor instead. +func (*UpdateResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{7} +} + +func (x *UpdateResponse) GetStatus() *StatusResult { + if x != nil { + return x.Status + } + return nil +} + +func (x *UpdateResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *UpdateResponse) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type DeleteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // The current resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // Preconditions: make sure the uid matches the current saved value + // +optional + Uid string `protobuf:"bytes,3,opt,name=uid,proto3" json:"uid,omitempty"` +} + +func (x *DeleteRequest) Reset() { + *x = DeleteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRequest) ProtoMessage() {} + +func (x *DeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. +func (*DeleteRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{8} +} + +func (x *DeleteRequest) GetKey() *ResourceKey { + if x != nil { + return x.Key + } + return nil +} + +func (x *DeleteRequest) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *DeleteRequest) GetUid() string { + if x != nil { + return x.Uid + } + return "" +} + +type DeleteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status code + Status *StatusResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // The new resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // The deleted payload + Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *DeleteResponse) Reset() { + *x = DeleteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteResponse) ProtoMessage() {} + +func (x *DeleteResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead. +func (*DeleteResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{9} +} + +func (x *DeleteResponse) GetStatus() *StatusResult { + if x != nil { + return x.Status + } + return nil +} + +func (x *DeleteResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *DeleteResponse) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type ReadRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // Optionally pick an explicit resource version + ResourceVersion int64 `protobuf:"varint,3,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` +} + +func (x *ReadRequest) Reset() { + *x = ReadRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReadRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadRequest) ProtoMessage() {} + +func (x *ReadRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadRequest.ProtoReflect.Descriptor instead. +func (*ReadRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{10} +} + +func (x *ReadRequest) GetKey() *ResourceKey { + if x != nil { + return x.Key + } + return nil +} + +func (x *ReadRequest) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +type ReadResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status code + Status *StatusResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // The new resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // The properties + Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *ReadResponse) Reset() { + *x = ReadResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReadResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadResponse) ProtoMessage() {} + +func (x *ReadResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadResponse.ProtoReflect.Descriptor instead. +func (*ReadResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{11} +} + +func (x *ReadResponse) GetStatus() *StatusResult { + if x != nil { + return x.Status + } + return nil +} + +func (x *ReadResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ReadResponse) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +// The label filtering requirements: +// https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/labels/selector.go#L141 +type Requirement struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Operator string `protobuf:"bytes,2,opt,name=operator,proto3" json:"operator,omitempty"` // See https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/selection/operator.go#L21 + Values []string `protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty"` // typically one value, but depends on the operator +} + +func (x *Requirement) Reset() { + *x = Requirement{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Requirement) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Requirement) ProtoMessage() {} + +func (x *Requirement) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Requirement.ProtoReflect.Descriptor instead. +func (*Requirement) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{12} +} + +func (x *Requirement) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Requirement) GetOperator() string { + if x != nil { + return x.Operator + } + return "" +} + +func (x *Requirement) GetValues() []string { + if x != nil { + return x.Values + } + return nil +} + +type Sort struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"` + Order Sort_Order `protobuf:"varint,2,opt,name=order,proto3,enum=resource.Sort_Order" json:"order,omitempty"` +} + +func (x *Sort) Reset() { + *x = Sort{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Sort) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Sort) ProtoMessage() {} + +func (x *Sort) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Sort.ProtoReflect.Descriptor instead. +func (*Sort) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{13} +} + +func (x *Sort) GetField() string { + if x != nil { + return x.Field + } + return "" +} + +func (x *Sort) GetOrder() Sort_Order { + if x != nil { + return x.Order + } + return Sort_ASC +} + +type ListOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Namespace+Group+Resource+etc + Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // (best effort) Match label + // Allowed to send more results than actually match because the filter will be appled + // to the resutls agin in the client. That time with the full field selector + Labels []*Requirement `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty"` +} + +func (x *ListOptions) Reset() { + *x = ListOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListOptions) ProtoMessage() {} + +func (x *ListOptions) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListOptions.ProtoReflect.Descriptor instead. +func (*ListOptions) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{14} +} + +func (x *ListOptions) GetKey() *ResourceKey { + if x != nil { + return x.Key + } + return nil +} + +func (x *ListOptions) GetLabels() []*Requirement { + if x != nil { + return x.Labels + } + return nil +} + +type ListRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Starting from the requested page (other query parameters must match!) + NextPageToken string `protobuf:"bytes,1,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + // The resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // List options + VersionMatch ResourceVersionMatch `protobuf:"varint,3,opt,name=version_match,json=versionMatch,proto3,enum=resource.ResourceVersionMatch" json:"version_match,omitempty"` + // Maximum number of items to return + // NOTE responses will also be limited by the response payload size + Limit int64 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + // Filtering + Options *ListOptions `protobuf:"bytes,5,opt,name=options,proto3" json:"options,omitempty"` +} + +func (x *ListRequest) Reset() { + *x = ListRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRequest) ProtoMessage() {} + +func (x *ListRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. +func (*ListRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{15} +} + +func (x *ListRequest) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +func (x *ListRequest) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ListRequest) GetVersionMatch() ResourceVersionMatch { + if x != nil { + return x.VersionMatch + } + return ResourceVersionMatch_NotOlderThan +} + +func (x *ListRequest) GetLimit() int64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListRequest) GetOptions() *ListOptions { + if x != nil { + return x.Options + } + return nil +} + +type ListResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*ResourceWrapper `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + // When more results exist, pass this in the next request + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + // ResourceVersion of the list response + ResourceVersion int64 `protobuf:"varint,3,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // remainingItemCount is the number of subsequent items in the list which are not included in this + // list response. If the list request contained label or field selectors, then the number of + // remaining items is unknown and the field will be left unset and omitted during serialization. + // If the list is complete (either because it is not chunking or because this is the last chunk), + // then there are no more remaining items and this field will be left unset and omitted during + // serialization. + // + // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients + // should not rely on the remainingItemCount to be set or to be exact. + // +optional + RemainingItemCount int64 `protobuf:"varint,4,opt,name=remaining_item_count,json=remainingItemCount,proto3" json:"remaining_item_count,omitempty"` // 0 won't be set either (no next page token) +} + +func (x *ListResponse) Reset() { + *x = ListResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListResponse) ProtoMessage() {} + +func (x *ListResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. +func (*ListResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{16} +} + +func (x *ListResponse) GetItems() []*ResourceWrapper { + if x != nil { + return x.Items + } + return nil +} + +func (x *ListResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +func (x *ListResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ListResponse) GetRemainingItemCount() int64 { + if x != nil { + return x.RemainingItemCount + } + return 0 +} + +type WatchRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // ResourceVersion of last changes. Empty will default to full history + Since int64 `protobuf:"varint,1,opt,name=since,proto3" json:"since,omitempty"` + // Additional options + Options *ListOptions `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` + // Return initial events + SendInitialEvents bool `protobuf:"varint,4,opt,name=send_initial_events,json=sendInitialEvents,proto3" json:"send_initial_events,omitempty"` + // When done with initial events, send a bookmark event + AllowWatchBookmarks bool `protobuf:"varint,5,opt,name=allow_watch_bookmarks,json=allowWatchBookmarks,proto3" json:"allow_watch_bookmarks,omitempty"` +} + +func (x *WatchRequest) Reset() { + *x = WatchRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WatchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchRequest) ProtoMessage() {} + +func (x *WatchRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchRequest.ProtoReflect.Descriptor instead. +func (*WatchRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{17} +} + +func (x *WatchRequest) GetSince() int64 { + if x != nil { + return x.Since + } + return 0 +} + +func (x *WatchRequest) GetOptions() *ListOptions { + if x != nil { + return x.Options + } + return nil +} + +func (x *WatchRequest) GetSendInitialEvents() bool { + if x != nil { + return x.SendInitialEvents + } + return false +} + +func (x *WatchRequest) GetAllowWatchBookmarks() bool { + if x != nil { + return x.AllowWatchBookmarks + } + return false +} + +type WatchEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Timestamp the event was sent + Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // Timestamp the event was sent + Type WatchEvent_Type `protobuf:"varint,2,opt,name=type,proto3,enum=resource.WatchEvent_Type" json:"type,omitempty"` + // Resource version for the object + Resource *WatchEvent_Resource `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + // Previous resource version (for update+delete) + Previous *WatchEvent_Resource `protobuf:"bytes,4,opt,name=previous,proto3" json:"previous,omitempty"` +} + +func (x *WatchEvent) Reset() { + *x = WatchEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WatchEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchEvent) ProtoMessage() {} + +func (x *WatchEvent) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchEvent.ProtoReflect.Descriptor instead. +func (*WatchEvent) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{18} +} + +func (x *WatchEvent) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *WatchEvent) GetType() WatchEvent_Type { + if x != nil { + return x.Type + } + return WatchEvent_UNKNOWN +} + +func (x *WatchEvent) GetResource() *WatchEvent_Resource { + if x != nil { + return x.Resource + } + return nil +} + +func (x *WatchEvent) GetPrevious() *WatchEvent_Resource { + if x != nil { + return x.Previous + } + return nil +} + +type HealthCheckRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` +} + +func (x *HealthCheckRequest) Reset() { + *x = HealthCheckRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HealthCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthCheckRequest) ProtoMessage() {} + +func (x *HealthCheckRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthCheckRequest.ProtoReflect.Descriptor instead. +func (*HealthCheckRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{19} +} + +func (x *HealthCheckRequest) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +type HealthCheckResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,proto3,enum=resource.HealthCheckResponse_ServingStatus" json:"status,omitempty"` +} + +func (x *HealthCheckResponse) Reset() { + *x = HealthCheckResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HealthCheckResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthCheckResponse) ProtoMessage() {} + +func (x *HealthCheckResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthCheckResponse.ProtoReflect.Descriptor instead. +func (*HealthCheckResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{20} +} + +func (x *HealthCheckResponse) GetStatus() HealthCheckResponse_ServingStatus { + if x != nil { + return x.Status + } + return HealthCheckResponse_UNKNOWN +} + +type WatchEvent_Resource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version int64 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *WatchEvent_Resource) Reset() { + *x = WatchEvent_Resource{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WatchEvent_Resource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchEvent_Resource) ProtoMessage() {} + +func (x *WatchEvent_Resource) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchEvent_Resource.ProtoReflect.Descriptor instead. +func (*WatchEvent_Resource) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{18, 0} +} + +func (x *WatchEvent_Resource) GetVersion() int64 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *WatchEvent_Resource) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +var File_resource_proto protoreflect.FileDescriptor + +var file_resource_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x71, 0x0a, 0x0b, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x52, 0x0a, + 0x0f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, + 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x22, 0x91, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, + 0x74, 0x61, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, + 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x69, 0x7a, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, + 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x11, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x22, 0x6c, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, + 0x6f, 0x64, 0x65, 0x22, 0x4e, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0x81, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x79, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x22, 0x81, 0x01, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x75, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x22, 0x81, 0x01, + 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x22, 0x61, 0x0a, 0x0b, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x7f, 0x0a, 0x0c, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x53, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x04, 0x53, 0x6f, + 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x2a, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x22, 0x1a, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x07, 0x0a, + 0x03, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x45, 0x53, 0x43, 0x10, 0x01, + 0x22, 0x65, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x22, 0xec, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x0d, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x1e, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, + 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc4, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, + 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, + 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x72, + 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, + 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x74, 0x65, 0x6d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xb9, 0x01, + 0x0a, 0x0c, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, + 0x69, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x11, 0x73, 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x77, + 0x61, 0x74, 0x63, 0x68, 0x5f, 0x62, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x57, 0x61, 0x74, 0x63, 0x68, + 0x42, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x22, 0xdf, 0x02, 0x0a, 0x0a, 0x57, 0x61, + 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x39, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, + 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x08, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x1a, 0x3a, 0x0a, 0x08, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x52, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, + 0x41, 0x44, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x4f, 0x44, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, + 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x4f, 0x4f, 0x4b, 0x4d, 0x41, 0x52, 0x4b, 0x10, 0x04, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0x2e, 0x0a, 0x12, 0x48, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0xab, 0x01, 0x0a, 0x13, + 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4f, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, + 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, + 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x2a, 0x33, 0x0a, 0x14, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, + 0x68, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, + 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x10, 0x01, 0x32, 0xed, + 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, + 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, + 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x32, 0x57, + 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, + 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, + 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_resource_proto_rawDescOnce sync.Once + file_resource_proto_rawDescData = file_resource_proto_rawDesc +) + +func file_resource_proto_rawDescGZIP() []byte { + file_resource_proto_rawDescOnce.Do(func() { + file_resource_proto_rawDescData = protoimpl.X.CompressGZIP(file_resource_proto_rawDescData) + }) + return file_resource_proto_rawDescData +} + +var file_resource_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_resource_proto_goTypes = []interface{}{ + (ResourceVersionMatch)(0), // 0: resource.ResourceVersionMatch + (Sort_Order)(0), // 1: resource.Sort.Order + (WatchEvent_Type)(0), // 2: resource.WatchEvent.Type + (HealthCheckResponse_ServingStatus)(0), // 3: resource.HealthCheckResponse.ServingStatus + (*ResourceKey)(nil), // 4: resource.ResourceKey + (*ResourceWrapper)(nil), // 5: resource.ResourceWrapper + (*ResourceMeta)(nil), // 6: resource.ResourceMeta + (*StatusResult)(nil), // 7: resource.StatusResult + (*CreateRequest)(nil), // 8: resource.CreateRequest + (*CreateResponse)(nil), // 9: resource.CreateResponse + (*UpdateRequest)(nil), // 10: resource.UpdateRequest + (*UpdateResponse)(nil), // 11: resource.UpdateResponse + (*DeleteRequest)(nil), // 12: resource.DeleteRequest + (*DeleteResponse)(nil), // 13: resource.DeleteResponse + (*ReadRequest)(nil), // 14: resource.ReadRequest + (*ReadResponse)(nil), // 15: resource.ReadResponse + (*Requirement)(nil), // 16: resource.Requirement + (*Sort)(nil), // 17: resource.Sort + (*ListOptions)(nil), // 18: resource.ListOptions + (*ListRequest)(nil), // 19: resource.ListRequest + (*ListResponse)(nil), // 20: resource.ListResponse + (*WatchRequest)(nil), // 21: resource.WatchRequest + (*WatchEvent)(nil), // 22: resource.WatchEvent + (*HealthCheckRequest)(nil), // 23: resource.HealthCheckRequest + (*HealthCheckResponse)(nil), // 24: resource.HealthCheckResponse + (*WatchEvent_Resource)(nil), // 25: resource.WatchEvent.Resource +} +var file_resource_proto_depIdxs = []int32{ + 4, // 0: resource.CreateRequest.key:type_name -> resource.ResourceKey + 7, // 1: resource.CreateResponse.status:type_name -> resource.StatusResult + 4, // 2: resource.UpdateRequest.key:type_name -> resource.ResourceKey + 7, // 3: resource.UpdateResponse.status:type_name -> resource.StatusResult + 4, // 4: resource.DeleteRequest.key:type_name -> resource.ResourceKey + 7, // 5: resource.DeleteResponse.status:type_name -> resource.StatusResult + 4, // 6: resource.ReadRequest.key:type_name -> resource.ResourceKey + 7, // 7: resource.ReadResponse.status:type_name -> resource.StatusResult + 1, // 8: resource.Sort.order:type_name -> resource.Sort.Order + 4, // 9: resource.ListOptions.key:type_name -> resource.ResourceKey + 16, // 10: resource.ListOptions.labels:type_name -> resource.Requirement + 0, // 11: resource.ListRequest.version_match:type_name -> resource.ResourceVersionMatch + 18, // 12: resource.ListRequest.options:type_name -> resource.ListOptions + 5, // 13: resource.ListResponse.items:type_name -> resource.ResourceWrapper + 18, // 14: resource.WatchRequest.options:type_name -> resource.ListOptions + 2, // 15: resource.WatchEvent.type:type_name -> resource.WatchEvent.Type + 25, // 16: resource.WatchEvent.resource:type_name -> resource.WatchEvent.Resource + 25, // 17: resource.WatchEvent.previous:type_name -> resource.WatchEvent.Resource + 3, // 18: resource.HealthCheckResponse.status:type_name -> resource.HealthCheckResponse.ServingStatus + 14, // 19: resource.ResourceStore.Read:input_type -> resource.ReadRequest + 8, // 20: resource.ResourceStore.Create:input_type -> resource.CreateRequest + 10, // 21: resource.ResourceStore.Update:input_type -> resource.UpdateRequest + 12, // 22: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest + 19, // 23: resource.ResourceStore.List:input_type -> resource.ListRequest + 21, // 24: resource.ResourceStore.Watch:input_type -> resource.WatchRequest + 23, // 25: resource.Diagnostics.IsHealthy:input_type -> resource.HealthCheckRequest + 15, // 26: resource.ResourceStore.Read:output_type -> resource.ReadResponse + 9, // 27: resource.ResourceStore.Create:output_type -> resource.CreateResponse + 11, // 28: resource.ResourceStore.Update:output_type -> resource.UpdateResponse + 13, // 29: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse + 20, // 30: resource.ResourceStore.List:output_type -> resource.ListResponse + 22, // 31: resource.ResourceStore.Watch:output_type -> resource.WatchEvent + 24, // 32: resource.Diagnostics.IsHealthy:output_type -> resource.HealthCheckResponse + 26, // [26:33] is the sub-list for method output_type + 19, // [19:26] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name +} + +func init() { file_resource_proto_init() } +func file_resource_proto_init() { + if File_resource_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_resource_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceWrapper); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceMeta); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReadRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReadResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Requirement); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Sort); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WatchRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WatchEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HealthCheckRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HealthCheckResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WatchEvent_Resource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_resource_proto_rawDesc, + NumEnums: 4, + NumMessages: 22, + NumExtensions: 0, + NumServices: 2, + }, + GoTypes: file_resource_proto_goTypes, + DependencyIndexes: file_resource_proto_depIdxs, + EnumInfos: file_resource_proto_enumTypes, + MessageInfos: file_resource_proto_msgTypes, + }.Build() + File_resource_proto = out.File + file_resource_proto_rawDesc = nil + file_resource_proto_goTypes = nil + file_resource_proto_depIdxs = nil +} diff --git a/pkg/storage/unified/resource/resource.proto b/pkg/storage/unified/resource/resource.proto new file mode 100644 index 00000000000..b7b741a8bdd --- /dev/null +++ b/pkg/storage/unified/resource/resource.proto @@ -0,0 +1,315 @@ +syntax = "proto3"; +package resource; + +option go_package = "github.com/grafana/grafana/pkg/storage/unified/resource"; + +message ResourceKey { + // Namespace (tenant) + string namespace = 2; + // Resource Group + string group = 1; + // The resource type + string resource = 3; + // Resource identifier (unique within namespace+group+resource) + string name = 4; +} + +message ResourceWrapper { + // The resource version + int64 resource_version = 1; + + // Full kubernetes json bytes (although the resource version may not be accurate) + bytes value = 2; +} + +// The history and trash commands need access to commit messages +message ResourceMeta { + // The resource version + int64 resource_version = 1; + + // Size of the full resource body + int32 size = 3; + + // Hash for the resource + string hash = 4; + + // The kubernetes metadata section (not the full resource) + // https://github.com/kubernetes/kubernetes/blob/v1.30.2/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go#L1496 + bytes partial_object_meta = 6; +} + +// Status structure is copied from: +// https://github.com/kubernetes/apimachinery/blob/v0.30.1/pkg/apis/meta/v1/generated.proto#L979 +message StatusResult { + // Status of the operation. + // One of: "Success" or "Failure". + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + string status = 1; + // A human-readable description of the status of this operation. + // +optional + string message = 2; + // A machine-readable description of why this operation is in the + // "Failure" status. If this value is empty there + // is no information available. A Reason clarifies an HTTP status + // code but does not override it. + // +optional + string reason = 3; + // Suggested HTTP return code for this status, 0 if not set. + // +optional + int32 code = 4; +} + +// ---------------------------------- +// CRUD Objects +// ---------------------------------- + +message CreateRequest { + // Requires group+resource to be configuired + // If name is not set, a unique name will be generated + // The resourceVersion should not be set + ResourceKey key = 1; + + // The resource JSON. + bytes value = 2; +} + +message CreateResponse { + // Status code + StatusResult status = 1; + + // The updated resource version + int64 resource_version = 2; + + // The resource JSON. With managed annotations included + bytes value = 3; +} + +message UpdateRequest { + // Full key must be set + ResourceKey key = 1; + + // The current resource version + int64 resource_version = 2; + + // The resource JSON. + bytes value = 3; +} + +message UpdateResponse { + // Status code + StatusResult status = 1; + + // The updated resource version + int64 resource_version = 2; + + // The resource JSON. With managed annotations included + bytes value = 3; +} + +message DeleteRequest { + ResourceKey key = 1; + + // The current resource version + int64 resource_version = 2; + + // Preconditions: make sure the uid matches the current saved value + // +optional + string uid = 3; +} + +message DeleteResponse { + // Status code + StatusResult status = 1; + + // The new resource version + int64 resource_version = 2; + + // The deleted payload + bytes value = 3; +} + +message ReadRequest { + ResourceKey key = 1; + + // Optionally pick an explicit resource version + int64 resource_version = 3; +} + +message ReadResponse { + // Status code + StatusResult status = 1; + + // The new resource version + int64 resource_version = 2; + + // The properties + bytes value = 3; +} + +// ---------------------------------- +// List Request/Response +// ---------------------------------- + +// The label filtering requirements: +// https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/labels/selector.go#L141 +message Requirement { + string key = 1; + string operator = 2; // See https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/selection/operator.go#L21 + repeated string values = 3; // typically one value, but depends on the operator +} + +message Sort { + enum Order { + ASC = 0; + DESC = 1; + } + string field = 1; + Order order = 2; +} + +message ListOptions { + // Namespace+Group+Resource+etc + ResourceKey key = 1; + + // (best effort) Match label + // Allowed to send more results than actually match because the filter will be appled + // to the resutls agin in the client. That time with the full field selector + repeated Requirement labels = 2; + + // TODO (later!) once we have a blob > search doc + // Match fields (not yet supported) + // metadata.name + // metadata.namespace + // repeated Requirement fields = 3; +} + +enum ResourceVersionMatch { + NotOlderThan = 0; + Exact = 1; +} + +message ListRequest { + // Starting from the requested page (other query parameters must match!) + string next_page_token = 1; + + // The resource version + int64 resource_version = 2; + + // List options + ResourceVersionMatch version_match = 3; + + // Maximum number of items to return + // NOTE responses will also be limited by the response payload size + int64 limit = 4; + + // Filtering + ListOptions options = 5; +} + +message ListResponse { + repeated ResourceWrapper items = 1; + + // When more results exist, pass this in the next request + string next_page_token = 2; + + // ResourceVersion of the list response + int64 resource_version = 3; + + // remainingItemCount is the number of subsequent items in the list which are not included in this + // list response. If the list request contained label or field selectors, then the number of + // remaining items is unknown and the field will be left unset and omitted during serialization. + // If the list is complete (either because it is not chunking or because this is the last chunk), + // then there are no more remaining items and this field will be left unset and omitted during + // serialization. + // + // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients + // should not rely on the remainingItemCount to be set or to be exact. + // +optional + int64 remaining_item_count = 4; // 0 won't be set either (no next page token) +} + +message WatchRequest { + // ResourceVersion of last changes. Empty will default to full history + int64 since = 1; + + // Additional options + ListOptions options = 3; + + // Return initial events + bool send_initial_events = 4; + + // When done with initial events, send a bookmark event + bool allow_watch_bookmarks = 5; +} + +message WatchEvent { + enum Type { + UNKNOWN = 0; + ADDED = 1; + MODIFIED = 2; + DELETED = 3; + BOOKMARK = 4; + ERROR = 5; + } + + message Resource { + int64 version = 1; + bytes value = 2; + } + + // Timestamp the event was sent + int64 timestamp = 1; + + // Timestamp the event was sent + Type type = 2; + + // Resource version for the object + Resource resource = 3; + + // Previous resource version (for update+delete) + Resource previous = 4; +} + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + SERVICE_UNKNOWN = 3; // Used only by the Watch method. + } + ServingStatus status = 1; +} + + +// This provides the CRUD+List+Watch support needed for a k8s apiserver +// The semantics and behaviors of this service are constrained by kubernetes +// This does not understand the resource schemas, only deals with json bytes +// Clients should not use this interface directly; it is for use in API Servers +service ResourceStore { + rpc Read(ReadRequest) returns (ReadResponse); + rpc Create(CreateRequest) returns (CreateResponse); + rpc Update(UpdateRequest) returns (UpdateResponse); + rpc Delete(DeleteRequest) returns (DeleteResponse); + + // The results *may* include values that should not be returned to the user + // This will perform best-effort filtering to increase performace. + // NOTE: storage.Interface is ultimatly responsible for the final filtering + rpc List(ListRequest) returns (ListResponse); + + // The results *may* include values that should not be returned to the user + // This will perform best-effort filtering to increase performace. + // NOTE: storage.Interface is ultimatly responsible for the final filtering + rpc Watch(WatchRequest) returns (stream WatchEvent); +} + +// Clients can use this service directly +// NOTE: This is read only, and no read afer write guarantees +service Diagnostics { + // Check if the service is healthy + rpc IsHealthy(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/pkg/storage/unified/resource/resource_grpc.pb.go b/pkg/storage/unified/resource/resource_grpc.pb.go new file mode 100644 index 00000000000..17b4d1c4c22 --- /dev/null +++ b/pkg/storage/unified/resource/resource_grpc.pb.go @@ -0,0 +1,445 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc (unknown) +// source: resource.proto + +package resource + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + ResourceStore_Read_FullMethodName = "/resource.ResourceStore/Read" + ResourceStore_Create_FullMethodName = "/resource.ResourceStore/Create" + ResourceStore_Update_FullMethodName = "/resource.ResourceStore/Update" + ResourceStore_Delete_FullMethodName = "/resource.ResourceStore/Delete" + ResourceStore_List_FullMethodName = "/resource.ResourceStore/List" + ResourceStore_Watch_FullMethodName = "/resource.ResourceStore/Watch" +) + +// ResourceStoreClient is the client API for ResourceStore service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// This provides the CRUD+List+Watch support needed for a k8s apiserver +// The semantics and behaviors of this service are constrained by kubernetes +// This does not understand the resource schemas, only deals with json bytes +// Clients should not use this interface directly; it is for use in API Servers +type ResourceStoreClient interface { + Read(ctx context.Context, in *ReadRequest, opts ...grpc.CallOption) (*ReadResponse, error) + Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) + Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*UpdateResponse, error) + Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) + // The results *may* include values that should not be returned to the user + // This will perform best-effort filtering to increase performace. + // NOTE: storage.Interface is ultimatly responsible for the final filtering + List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) + // The results *may* include values that should not be returned to the user + // This will perform best-effort filtering to increase performace. + // NOTE: storage.Interface is ultimatly responsible for the final filtering + Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (ResourceStore_WatchClient, error) +} + +type resourceStoreClient struct { + cc grpc.ClientConnInterface +} + +func NewResourceStoreClient(cc grpc.ClientConnInterface) ResourceStoreClient { + return &resourceStoreClient{cc} +} + +func (c *resourceStoreClient) Read(ctx context.Context, in *ReadRequest, opts ...grpc.CallOption) (*ReadResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReadResponse) + err := c.cc.Invoke(ctx, ResourceStore_Read_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateResponse) + err := c.cc.Invoke(ctx, ResourceStore_Create_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*UpdateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateResponse) + err := c.cc.Invoke(ctx, ResourceStore_Update_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteResponse) + err := c.cc.Invoke(ctx, ResourceStore_Delete_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListResponse) + err := c.cc.Invoke(ctx, ResourceStore_List_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (ResourceStore_WatchClient, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &ResourceStore_ServiceDesc.Streams[0], ResourceStore_Watch_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &resourceStoreWatchClient{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ResourceStore_WatchClient interface { + Recv() (*WatchEvent, error) + grpc.ClientStream +} + +type resourceStoreWatchClient struct { + grpc.ClientStream +} + +func (x *resourceStoreWatchClient) Recv() (*WatchEvent, error) { + m := new(WatchEvent) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// ResourceStoreServer is the server API for ResourceStore service. +// All implementations should embed UnimplementedResourceStoreServer +// for forward compatibility +// +// This provides the CRUD+List+Watch support needed for a k8s apiserver +// The semantics and behaviors of this service are constrained by kubernetes +// This does not understand the resource schemas, only deals with json bytes +// Clients should not use this interface directly; it is for use in API Servers +type ResourceStoreServer interface { + Read(context.Context, *ReadRequest) (*ReadResponse, error) + Create(context.Context, *CreateRequest) (*CreateResponse, error) + Update(context.Context, *UpdateRequest) (*UpdateResponse, error) + Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) + // The results *may* include values that should not be returned to the user + // This will perform best-effort filtering to increase performace. + // NOTE: storage.Interface is ultimatly responsible for the final filtering + List(context.Context, *ListRequest) (*ListResponse, error) + // The results *may* include values that should not be returned to the user + // This will perform best-effort filtering to increase performace. + // NOTE: storage.Interface is ultimatly responsible for the final filtering + Watch(*WatchRequest, ResourceStore_WatchServer) error +} + +// UnimplementedResourceStoreServer should be embedded to have forward compatible implementations. +type UnimplementedResourceStoreServer struct { +} + +func (UnimplementedResourceStoreServer) Read(context.Context, *ReadRequest) (*ReadResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Read not implemented") +} +func (UnimplementedResourceStoreServer) Create(context.Context, *CreateRequest) (*CreateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Create not implemented") +} +func (UnimplementedResourceStoreServer) Update(context.Context, *UpdateRequest) (*UpdateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") +} +func (UnimplementedResourceStoreServer) Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") +} +func (UnimplementedResourceStoreServer) List(context.Context, *ListRequest) (*ListResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method List not implemented") +} +func (UnimplementedResourceStoreServer) Watch(*WatchRequest, ResourceStore_WatchServer) error { + return status.Errorf(codes.Unimplemented, "method Watch not implemented") +} + +// UnsafeResourceStoreServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ResourceStoreServer will +// result in compilation errors. +type UnsafeResourceStoreServer interface { + mustEmbedUnimplementedResourceStoreServer() +} + +func RegisterResourceStoreServer(s grpc.ServiceRegistrar, srv ResourceStoreServer) { + s.RegisterService(&ResourceStore_ServiceDesc, srv) +} + +func _ResourceStore_Read_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReadRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).Read(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_Read_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).Read(ctx, req.(*ReadRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).Create(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_Create_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).Create(ctx, req.(*CreateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).Update(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_Update_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).Update(ctx, req.(*UpdateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_Delete_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).Delete(ctx, req.(*DeleteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).List(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_List_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).List(ctx, req.(*ListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_Watch_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(WatchRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ResourceStoreServer).Watch(m, &resourceStoreWatchServer{ServerStream: stream}) +} + +type ResourceStore_WatchServer interface { + Send(*WatchEvent) error + grpc.ServerStream +} + +type resourceStoreWatchServer struct { + grpc.ServerStream +} + +func (x *resourceStoreWatchServer) Send(m *WatchEvent) error { + return x.ServerStream.SendMsg(m) +} + +// ResourceStore_ServiceDesc is the grpc.ServiceDesc for ResourceStore service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ResourceStore_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "resource.ResourceStore", + HandlerType: (*ResourceStoreServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Read", + Handler: _ResourceStore_Read_Handler, + }, + { + MethodName: "Create", + Handler: _ResourceStore_Create_Handler, + }, + { + MethodName: "Update", + Handler: _ResourceStore_Update_Handler, + }, + { + MethodName: "Delete", + Handler: _ResourceStore_Delete_Handler, + }, + { + MethodName: "List", + Handler: _ResourceStore_List_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Watch", + Handler: _ResourceStore_Watch_Handler, + ServerStreams: true, + }, + }, + Metadata: "resource.proto", +} + +const ( + Diagnostics_IsHealthy_FullMethodName = "/resource.Diagnostics/IsHealthy" +) + +// DiagnosticsClient is the client API for Diagnostics service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// Clients can use this service directly +// NOTE: This is read only, and no read afer write guarantees +type DiagnosticsClient interface { + // Check if the service is healthy + IsHealthy(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) +} + +type diagnosticsClient struct { + cc grpc.ClientConnInterface +} + +func NewDiagnosticsClient(cc grpc.ClientConnInterface) DiagnosticsClient { + return &diagnosticsClient{cc} +} + +func (c *diagnosticsClient) IsHealthy(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HealthCheckResponse) + err := c.cc.Invoke(ctx, Diagnostics_IsHealthy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DiagnosticsServer is the server API for Diagnostics service. +// All implementations should embed UnimplementedDiagnosticsServer +// for forward compatibility +// +// Clients can use this service directly +// NOTE: This is read only, and no read afer write guarantees +type DiagnosticsServer interface { + // Check if the service is healthy + IsHealthy(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) +} + +// UnimplementedDiagnosticsServer should be embedded to have forward compatible implementations. +type UnimplementedDiagnosticsServer struct { +} + +func (UnimplementedDiagnosticsServer) IsHealthy(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method IsHealthy not implemented") +} + +// UnsafeDiagnosticsServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DiagnosticsServer will +// result in compilation errors. +type UnsafeDiagnosticsServer interface { + mustEmbedUnimplementedDiagnosticsServer() +} + +func RegisterDiagnosticsServer(s grpc.ServiceRegistrar, srv DiagnosticsServer) { + s.RegisterService(&Diagnostics_ServiceDesc, srv) +} + +func _Diagnostics_IsHealthy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HealthCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiagnosticsServer).IsHealthy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Diagnostics_IsHealthy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiagnosticsServer).IsHealthy(ctx, req.(*HealthCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Diagnostics_ServiceDesc is the grpc.ServiceDesc for Diagnostics service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Diagnostics_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "resource.Diagnostics", + HandlerType: (*DiagnosticsServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "IsHealthy", + Handler: _Diagnostics_IsHealthy_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "resource.proto", +} diff --git a/pkg/storage/unified/resource/rv.go b/pkg/storage/unified/resource/rv.go new file mode 100644 index 00000000000..88ec63e4567 --- /dev/null +++ b/pkg/storage/unified/resource/rv.go @@ -0,0 +1,16 @@ +package resource + +import "sync/atomic" + +// The kubernetes storage.Interface tests expect this to be a sequential progression +// SnowflakeIDs do not pass the off-the-shelf k8s tests, although they provide totally +// acceptable values. +type NextResourceVersion = func() int64 + +func newResourceVersionCounter(start int64) NextResourceVersion { + var counter atomic.Int64 + _ = counter.Swap(start + 1) + return func() int64 { + return counter.Add(1) + } +} diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go new file mode 100644 index 00000000000..2c4febdd1f3 --- /dev/null +++ b/pkg/storage/unified/resource/server.go @@ -0,0 +1,562 @@ +package resource + +import ( + context "context" + "encoding/json" + "errors" + "fmt" + "log/slog" + "sync" + "time" + + "github.com/google/uuid" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" +) + +// Package-level errors. +var ( + ErrNotFound = errors.New("entity not found") + ErrOptimisticLockingFailed = errors.New("optimistic locking failed") + ErrUserNotFoundInContext = errors.New("user not found in context") + ErrUnableToReadResourceJSON = errors.New("unable to read resource json") + ErrNotImplementedYet = errors.New("not implemented yet") +) + +// ResourceServer implements all services +type ResourceServer interface { + ResourceStoreServer + DiagnosticsServer + LifecycleHooks +} + +// The StorageBackend is an internal abstraction that supports interacting with +// the underlying raw storage medium. This interface is never exposed directly, +// it is provided by concrete instances that actually write values. +type StorageBackend interface { + // Write a Create/Update/Delete, + // NOTE: the contents of WriteEvent have been validated + // Return the revisionVersion for this event or error + WriteEvent(context.Context, WriteEvent) (int64, error) + + // Read a value from storage optionally at an explicit version + Read(context.Context, *ReadRequest) (*ReadResponse, error) + + // When the ResourceServer executes a List request, it will first + // query the backend for potential results. All results will be + // checked against the kubernetes requirements before finally returning + // results. The list options can be used to improve performance + // but are the the final answer. + PrepareList(context.Context, *ListRequest) (*ListResponse, error) + + // Get all events from the store + // For HA setups, this will be more events than the local WriteEvent above! + WatchWriteEvents(ctx context.Context) (<-chan *WrittenEvent, error) +} + +type ResourceServerOptions struct { + // OTel tracer + Tracer trace.Tracer + + // Real storage backend + Backend StorageBackend + + // Diagnostics + Diagnostics DiagnosticsServer + + // Check if a user has access to write folders + // When this is nil, no resources can have folders configured + WriteAccess WriteAccessHooks + + // Callbacks for startup and shutdown + Lifecycle LifecycleHooks + + // Get the current time in unix millis + Now func() int64 +} + +func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) { + if opts.Tracer == nil { + opts.Tracer = noop.NewTracerProvider().Tracer("resource-server") + } + + if opts.Backend == nil { + return nil, fmt.Errorf("missing Backend implementation") + } + if opts.Diagnostics == nil { + opts.Diagnostics = &noopService{} + } + if opts.Now == nil { + opts.Now = func() int64 { + return time.Now().UnixMilli() + } + } + + // Make this cancelable + ctx, cancel := context.WithCancel(identity.WithRequester(context.Background(), + &identity.StaticRequester{ + Namespace: identity.NamespaceServiceAccount, + Login: "watcher", // admin user for watch + UserID: 1, + IsGrafanaAdmin: true, + })) + return &server{ + tracer: opts.Tracer, + log: slog.Default().With("logger", "resource-server"), + backend: opts.Backend, + diagnostics: opts.Diagnostics, + access: opts.WriteAccess, + lifecycle: opts.Lifecycle, + now: opts.Now, + ctx: ctx, + cancel: cancel, + }, nil +} + +var _ ResourceServer = &server{} + +type server struct { + tracer trace.Tracer + log *slog.Logger + backend StorageBackend + diagnostics DiagnosticsServer + access WriteAccessHooks + lifecycle LifecycleHooks + now func() int64 + + // Background watch task -- this has permissions for everything + ctx context.Context + cancel context.CancelFunc + broadcaster Broadcaster[*WrittenEvent] + + // init checking + once sync.Once + initErr error +} + +// Init implements ResourceServer. +func (s *server) Init() error { + s.once.Do(func() { + // Call lifecycle hooks + if s.lifecycle != nil { + err := s.lifecycle.Init() + if err != nil { + s.initErr = fmt.Errorf("initialize Resource Server: %w", err) + } + } + + // Start watching for changes + if s.initErr == nil { + s.initErr = s.initWatcher() + } + + if s.initErr != nil { + s.log.Error("error initializing resource server", "error", s.initErr) + } + }) + return s.initErr +} + +func (s *server) Stop() { + s.initErr = fmt.Errorf("service is stopping") + + if s.lifecycle != nil { + s.lifecycle.Stop() + } + + // Stops the streaming + s.cancel() + + // mark the value as done + s.initErr = fmt.Errorf("service is stopped") +} + +// Old value indicates an update -- otherwise a create +func (s *server) newEventBuilder(ctx context.Context, key *ResourceKey, value, oldValue []byte) (*writeEventBuilder, error) { + event, err := newEventFromBytes(value, oldValue) + if err != nil { + return nil, err + } + event.Key = key + event.Requester, err = identity.GetRequester(ctx) + if err != nil { + return nil, ErrUserNotFoundInContext + } + + obj := event.Meta + if key.Namespace != obj.GetNamespace() { + return nil, apierrors.NewBadRequest("key/namespace do not match") + } + + gvk := obj.GetGroupVersionKind() + if gvk.Kind == "" { + return nil, apierrors.NewBadRequest("expecting resources with a kind in the body") + } + if gvk.Version == "" { + return nil, apierrors.NewBadRequest("expecting resources with an apiVersion") + } + if gvk.Group != "" && gvk.Group != key.Group { + return nil, apierrors.NewBadRequest( + fmt.Sprintf("group in key does not match group in the body (%s != %s)", key.Group, gvk.Group), + ) + } + + // This needs to be a create function + if key.Name == "" { + if obj.GetName() == "" { + return nil, apierrors.NewBadRequest("missing name") + } + key.Name = obj.GetName() + } else if key.Name != obj.GetName() { + return nil, apierrors.NewBadRequest( + fmt.Sprintf("key/name do not match (key: %s, name: %s)", key.Name, obj.GetName())) + } + obj.SetGenerateName("") + err = validateName(obj.GetName()) + if err != nil { + return nil, err + } + + folder := obj.GetFolder() + if folder != "" { + err = s.access.CanWriteFolder(ctx, event.Requester, folder) + if err != nil { + return nil, err + } + } + origin, err := obj.GetOriginInfo() + if err != nil { + return nil, apierrors.NewBadRequest("invalid origin info") + } + if origin != nil { + err = s.access.CanWriteOrigin(ctx, event.Requester, origin.Name) + if err != nil { + return nil, err + } + } + obj.SetOriginInfo(origin) + + // Make sure old values do not mutate things they should not + if event.OldMeta != nil { + old := event.OldMeta + + if obj.GetUID() != event.OldMeta.GetUID() { + return nil, apierrors.NewBadRequest( + fmt.Sprintf("UIDs do not match (old: %s, new: %s)", old.GetUID(), obj.GetUID())) + } + + // Can not change creation timestamps+user + if obj.GetCreatedBy() != event.OldMeta.GetCreatedBy() { + return nil, apierrors.NewBadRequest( + fmt.Sprintf("created by changed (old: %s, new: %s)", old.GetCreatedBy(), obj.GetCreatedBy())) + } + if obj.GetCreationTimestamp() != event.OldMeta.GetCreationTimestamp() { + return nil, apierrors.NewBadRequest( + fmt.Sprintf("creation timestamp changed (old:%v, new:%v)", old.GetCreationTimestamp(), obj.GetCreationTimestamp())) + } + } + return event, nil +} + +func (s *server) Create(ctx context.Context, req *CreateRequest) (*CreateResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.Create") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + rsp := &CreateResponse{} + builder, err := s.newEventBuilder(ctx, req.Key, req.Value, nil) + if err != nil { + rsp.Status, err = errToStatus(err) + return rsp, err + } + + obj := builder.Meta + obj.SetCreatedBy(builder.Requester.GetUID().String()) + obj.SetUpdatedBy("") + obj.SetUpdatedTimestamp(nil) + obj.SetCreationTimestamp(metav1.NewTime(time.UnixMilli(s.now()))) + obj.SetUID(types.UID(uuid.New().String())) + + event, err := builder.toEvent() + if err != nil { + rsp.Status, err = errToStatus(err) + return rsp, err + } + + rsp.ResourceVersion, err = s.backend.WriteEvent(ctx, event) + if err == nil { + rsp.Value = event.Value // with mutated fields + } else { + rsp.Status, err = errToStatus(err) + } + return rsp, err +} + +// Convert golang errors to status result errors that can be returned to a client +func errToStatus(err error) (*StatusResult, error) { + if err != nil { + apistatus, ok := err.(apierrors.APIStatus) + if ok { + s := apistatus.Status() + return &StatusResult{ + Status: s.Status, + Message: s.Message, + Reason: string(s.Reason), + Code: s.Code, + }, nil + } + + // TODO... better conversion!!! + return &StatusResult{ + Status: "Failure", + Message: err.Error(), + Code: 500, + }, nil + } + return nil, err +} + +func (s *server) Update(ctx context.Context, req *UpdateRequest) (*UpdateResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.Update") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + rsp := &UpdateResponse{} + if req.ResourceVersion < 0 { + rsp.Status, _ = errToStatus(apierrors.NewBadRequest("update must include the previous version")) + return rsp, nil + } + + latest, err := s.backend.Read(ctx, &ReadRequest{ + Key: req.Key, + }) + if err != nil { + return nil, err + } + if latest.Value == nil { + return nil, apierrors.NewBadRequest("current value does not exist") + } + + builder, err := s.newEventBuilder(ctx, req.Key, req.Value, latest.Value) + if err != nil { + rsp.Status, err = errToStatus(err) + return rsp, err + } + + obj := builder.Meta + obj.SetUpdatedBy(builder.Requester.GetUID().String()) + obj.SetUpdatedTimestampMillis(time.Now().UnixMilli()) + + event, err := builder.toEvent() + if err != nil { + rsp.Status, err = errToStatus(err) + return rsp, err + } + + event.Type = WatchEvent_MODIFIED + event.PreviousRV = latest.ResourceVersion + + rsp.ResourceVersion, err = s.backend.WriteEvent(ctx, event) + rsp.Status, err = errToStatus(err) + if err == nil { + rsp.Value = event.Value // with mutated fields + } else { + rsp.Status, err = errToStatus(err) + } + return rsp, err +} + +func (s *server) Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.Delete") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + rsp := &DeleteResponse{} + if req.ResourceVersion < 0 { + return nil, apierrors.NewBadRequest("update must include the previous version") + } + + latest, err := s.backend.Read(ctx, &ReadRequest{ + Key: req.Key, + }) + if err != nil { + return nil, err + } + if latest.ResourceVersion != req.ResourceVersion { + return nil, ErrOptimisticLockingFailed + } + + now := metav1.NewTime(time.UnixMilli(s.now())) + event := WriteEvent{ + Key: req.Key, + Type: WatchEvent_DELETED, + PreviousRV: latest.ResourceVersion, + } + requester, err := identity.GetRequester(ctx) + if err != nil { + return nil, apierrors.NewBadRequest("unable to get user") + } + marker := &DeletedMarker{} + err = json.Unmarshal(latest.Value, marker) + if err != nil { + return nil, apierrors.NewBadRequest( + fmt.Sprintf("unable to read previous object, %v", err)) + } + obj, err := utils.MetaAccessor(marker) + if err != nil { + return nil, err + } + obj.SetDeletionTimestamp(&now) + obj.SetUpdatedTimestamp(&now.Time) + obj.SetManagedFields(nil) + obj.SetFinalizers(nil) + obj.SetUpdatedBy(requester.GetUID().String()) + marker.TypeMeta = metav1.TypeMeta{ + Kind: "DeletedMarker", + APIVersion: "common.grafana.app/v0alpha1", // ?? or can we stick this in common? + } + marker.Annotations["RestoreResourceVersion"] = fmt.Sprintf("%d", event.PreviousRV) + event.Value, err = json.Marshal(marker) + if err != nil { + return nil, apierrors.NewBadRequest( + fmt.Sprintf("unable creating deletion marker, %v", err)) + } + + rsp.ResourceVersion, err = s.backend.WriteEvent(ctx, event) + rsp.Status, err = errToStatus(err) + return rsp, err +} + +func (s *server) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, error) { + if err := s.Init(); err != nil { + return nil, err + } + + if req.Key.Group == "" { + status, _ := errToStatus(apierrors.NewBadRequest("missing group")) + return &ReadResponse{Status: status}, nil + } + if req.Key.Resource == "" { + status, _ := errToStatus(apierrors.NewBadRequest("missing resource")) + return &ReadResponse{Status: status}, nil + } + + rsp, err := s.backend.Read(ctx, req) + if err != nil { + if rsp == nil { + rsp = &ReadResponse{} + } + rsp.Status, err = errToStatus(err) + } + return rsp, err +} + +func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, error) { + if err := s.Init(); err != nil { + return nil, err + } + + rsp, err := s.backend.PrepareList(ctx, req) + // Status??? + return rsp, err +} + +func (s *server) initWatcher() error { + var err error + s.broadcaster, err = NewBroadcaster(s.ctx, func(out chan<- *WrittenEvent) error { + events, err := s.backend.WatchWriteEvents(s.ctx) + if err != nil { + return err + } + go func() { + for { + // pipe all events + v := <-events + out <- v + } + }() + return nil + }) + return err +} + +func (s *server) Watch(req *WatchRequest, srv ResourceStore_WatchServer) error { + if err := s.Init(); err != nil { + return err + } + + fmt.Printf("WATCH %v\n", req.Options.Key) + + ctx := srv.Context() + + // Start listening -- this will buffer any changes that happen while we backfill + stream, err := s.broadcaster.Subscribe(ctx) + if err != nil { + return err + } + defer s.broadcaster.Unsubscribe(stream) + + since := req.Since + if req.SendInitialEvents { + fmt.Printf("TODO... query\n") + // All initial events are CREATE + + if req.AllowWatchBookmarks { + fmt.Printf("TODO... send bookmark\n") + } + } + + for { + select { + case <-ctx.Done(): + return nil + + case event, ok := <-stream: + if !ok { + s.log.Debug("watch events closed") + return nil + } + + if event.ResourceVersion > since && matchesQueryKey(req.Options.Key, event.Key) { + // Currently sending *every* event + // if req.Options.Labels != nil { + // // match *either* the old or new object + // } + // TODO: return values that match either the old or the new + + srv.Send(&WatchEvent{ + Timestamp: event.Timestamp, + Type: event.Type, + Resource: &WatchEvent_Resource{ + Value: event.Value, + Version: event.ResourceVersion, + }, + // TODO... previous??? + }) + } + } + } +} + +// IsHealthy implements ResourceServer. +func (s *server) IsHealthy(ctx context.Context, req *HealthCheckRequest) (*HealthCheckResponse, error) { + if err := s.Init(); err != nil { + return nil, err + } + return s.diagnostics.IsHealthy(ctx, req) +} diff --git a/pkg/storage/unified/resource/server_test.go b/pkg/storage/unified/resource/server_test.go new file mode 100644 index 00000000000..9125bca00dc --- /dev/null +++ b/pkg/storage/unified/resource/server_test.go @@ -0,0 +1,145 @@ +package resource + +import ( + "context" + "embed" + "encoding/json" + "fmt" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + "gocloud.dev/blob/fileblob" + "gocloud.dev/blob/memblob" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" +) + +func TestSimpleServer(t *testing.T) { + testUserA := &identity.StaticRequester{ + Namespace: identity.NamespaceUser, + UserID: 123, + UserUID: "u123", + OrgRole: identity.RoleAdmin, + IsGrafanaAdmin: true, // can do anything + } + ctx := identity.WithRequester(context.Background(), testUserA) + + bucket := memblob.OpenBucket(nil) + if false { + tmp, err := os.MkdirTemp("", "xxx-*") + require.NoError(t, err) + + bucket, err = fileblob.OpenBucket(tmp, &fileblob.Options{ + CreateDir: true, + Metadata: fileblob.MetadataDontWrite, // skip + }) + require.NoError(t, err) + + fmt.Printf("ROOT: %s\n\n", tmp) + } + store, err := NewCDKBackend(ctx, CDKBackendOptions{ + Bucket: bucket, + }) + require.NoError(t, err) + + server, err := NewResourceServer(ResourceServerOptions{ + Backend: store, + }) + require.NoError(t, err) + + t.Run("playlist happy CRUD paths", func(t *testing.T) { + raw := testdata(t, "01_create_playlist.json") + key := &ResourceKey{ + Group: "playlist.grafana.app", + Resource: "rrrr", // can be anything :( + Namespace: "default", + Name: "fdgsv37qslr0ga", + } + + // Should be empty when we start + all, err := server.List(ctx, &ListRequest{Options: &ListOptions{ + Key: &ResourceKey{ + Group: key.Group, + Resource: key.Resource, + }, + }}) + require.NoError(t, err) + require.Len(t, all.Items, 0) + + created, err := server.Create(ctx, &CreateRequest{ + Value: raw, + Key: key, + }) + require.NoError(t, err) + require.Nil(t, created.Status) + require.True(t, created.ResourceVersion > 0) + + // The key does not include resource version + found, err := server.Read(ctx, &ReadRequest{Key: key}) + require.NoError(t, err) + require.Nil(t, found.Status) + require.Equal(t, created.ResourceVersion, found.ResourceVersion) + + // Now update the value + tmp := &unstructured.Unstructured{} + err = json.Unmarshal(created.Value, tmp) + require.NoError(t, err) + + now := time.Now().UnixMilli() + obj, err := utils.MetaAccessor(tmp) + require.NoError(t, err) + obj.SetAnnotation("test", "hello") + obj.SetUpdatedTimestampMillis(now) + obj.SetUpdatedBy(testUserA.GetUID().String()) + raw, err = json.Marshal(tmp) + require.NoError(t, err) + + updated, err := server.Update(ctx, &UpdateRequest{ + Key: key, + Value: raw, + ResourceVersion: created.ResourceVersion}) + require.NoError(t, err) + require.Nil(t, updated.Status) + require.True(t, updated.ResourceVersion > created.ResourceVersion) + + // We should still get the latest + found, err = server.Read(ctx, &ReadRequest{Key: key}) + require.NoError(t, err) + require.Nil(t, found.Status) + require.Equal(t, updated.ResourceVersion, found.ResourceVersion) + + all, err = server.List(ctx, &ListRequest{Options: &ListOptions{ + Key: &ResourceKey{ + Group: key.Group, + Resource: key.Resource, + }, + }}) + require.NoError(t, err) + require.Len(t, all.Items, 1) + require.Equal(t, updated.ResourceVersion, all.Items[0].ResourceVersion) + + deleted, err := server.Delete(ctx, &DeleteRequest{Key: key, ResourceVersion: updated.ResourceVersion}) + require.NoError(t, err) + require.True(t, deleted.ResourceVersion > updated.ResourceVersion) + + // We should get not found status when trying to read the latest value + found, err = server.Read(ctx, &ReadRequest{Key: key}) + require.NoError(t, err) + require.NotNil(t, found.Status) + require.Equal(t, int32(404), found.Status.Code) + }) +} + +//go:embed testdata/* +var testdataFS embed.FS + +func testdata(t *testing.T, filename string) []byte { + t.Helper() + b, err := testdataFS.ReadFile(`testdata/` + filename) + require.NoError(t, err) + return b +} diff --git a/pkg/storage/unified/resource/testdata/01_create_playlist.json b/pkg/storage/unified/resource/testdata/01_create_playlist.json new file mode 100644 index 00000000000..151435fe1c6 --- /dev/null +++ b/pkg/storage/unified/resource/testdata/01_create_playlist.json @@ -0,0 +1,23 @@ +{ + "apiVersion": "playlist.grafana.app/v0alpha1", + "kind": "Playlist", + "metadata": { + "name": "fdgsv37qslr0ga", + "namespace": "default", + "annotations": { + "grafana.app/originName": "elsewhere", + "grafana.app/originPath": "path/to/item", + "grafana.app/originTimestamp": "2024-02-02T00:00:00Z" + } + }, + "spec": { + "title": "hello", + "interval": "5m", + "items": [ + { + "type": "dashboard_by_uid", + "value": "vmie2cmWz" + } + ] + } +} \ No newline at end of file diff --git a/pkg/storage/unified/resource/validation.go b/pkg/storage/unified/resource/validation.go new file mode 100644 index 00000000000..746bea81ded --- /dev/null +++ b/pkg/storage/unified/resource/validation.go @@ -0,0 +1,25 @@ +package resource + +import ( + "fmt" + "regexp" +) + +var validNameCharPattern = `a-zA-Z0-9\-\_` +var validNamePattern = regexp.MustCompile(`^[` + validNameCharPattern + `]*$`).MatchString + +func validateName(name string) error { + if len(name) < 2 { + return fmt.Errorf("name is too short") + } + if len(name) > 64 { + return fmt.Errorf("name is too long") + } + if !validNamePattern(name) { + return fmt.Errorf("name includes invalid characters") + } + // In standard k8s, it must not start with a number + // however that would force us to update many many many existing resources + // so we will be slightly more lenient than standard k8s + return nil +} From db68d58d52a532a7c798781fd5de9088030bb83e Mon Sep 17 00:00:00 2001 From: Sarah Zinger Date: Wed, 3 Jul 2024 12:09:40 -0400 Subject: [PATCH 19/39] CloudWatch: add account dropdown to metric insights (#89926) --- .../SQLBuilderEditor/SQLBuilderSelectRow.tsx | 31 +++++++++++++++++-- .../SQLBuilderEditor/SQLFilter.tsx | 22 +++++++++++-- .../QueryEditor/QueryEditor.test.tsx | 21 +++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.tsx index 859031eec9a..d350e18bf66 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.tsx @@ -2,13 +2,15 @@ import { useEffect, useMemo } from 'react'; import { SelectableValue, toOption } from '@grafana/data'; import { EditorField, EditorFieldGroup, EditorSwitch } from '@grafana/experimental'; +import { config } from '@grafana/runtime'; import { Select } from '@grafana/ui'; import { CloudWatchDatasource } from '../../../../datasource'; -import { useDimensionKeys, useMetrics, useNamespaces } from '../../../../hooks'; +import { useAccountOptions, useDimensionKeys, useMetrics, useNamespaces } from '../../../../hooks'; import { STATISTICS } from '../../../../language/cloudwatch-sql/language'; import { CloudWatchMetricsQuery } from '../../../../types'; import { appendTemplateVariables } from '../../../../utils/utils'; +import { Account } from '../../../shared/Account'; import { getMetricNameFromExpression, @@ -48,13 +50,24 @@ const SQLBuilderSelectRow = ({ datasource, query, onQueryChange }: SQLBuilderSel const withSchemaEnabled = isUsingWithSchema(sql.from); const namespaceOptions = useNamespaces(datasource); - const metricOptions = useMetrics(datasource, { region: query.region, namespace }); + const metricOptions = useMetrics(datasource, { + region: query.region, + namespace, + ...(config.featureToggles.cloudWatchCrossAccountQuerying && + config.featureToggles.cloudwatchMetricInsightsCrossAccount + ? { accountId: query.accountId } + : {}), + }); const existingFilters = useMemo(() => stringArrayToDimensions(schemaLabels ?? []), [schemaLabels]); const unusedDimensionKeys = useDimensionKeys(datasource, { region: query.region, namespace, metricName, dimensionFilters: existingFilters, + ...(config.featureToggles.cloudWatchCrossAccountQuerying && + config.featureToggles.cloudwatchMetricInsightsCrossAccount + ? { accountId: query.accountId } + : {}), }); const dimensionKeys = useMemo( () => (schemaLabels?.length ? [...unusedDimensionKeys, ...schemaLabels.map(toOption)] : unusedDimensionKeys), @@ -76,9 +89,23 @@ const SQLBuilderSelectRow = ({ datasource, query, onQueryChange }: SQLBuilderSel return { ...query, sql }; }; + const accountState = useAccountOptions(datasource.resources, query.region); return ( <> + {config.featureToggles.cloudWatchCrossAccountQuerying && + config.featureToggles.cloudwatchMetricInsightsCrossAccount && ( + { + onQueryChange({ + ...query, + accountId, + }); + }} + /> + )} + + + + + + + ); +}; From 4ec4994e890c24abe77418be1e2e3a1a56f16db5 Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Wed, 3 Jul 2024 16:25:57 -0400 Subject: [PATCH 27/39] infra(tracing): Always end started spans (#90016) Signed-off-by: Dave Henderson --- pkg/middleware/request_tracing.go | 36 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/pkg/middleware/request_tracing.go b/pkg/middleware/request_tracing.go index 3a7f8d59213..d97db94c908 100644 --- a/pkg/middleware/request_tracing.go +++ b/pkg/middleware/request_tracing.go @@ -74,32 +74,40 @@ func RouteOperationName(req *http.Request) (string, bool) { func RequestTracing(tracer tracing.Tracer) web.Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if strings.HasPrefix(req.URL.Path, "/public/") || req.URL.Path == "/robots.txt" || req.URL.Path == "/favicon.ico" { + // skip tracing for a few endpoints + if strings.HasPrefix(req.URL.Path, "/public/") || + req.URL.Path == "/robots.txt" || + req.URL.Path == "/favicon.ico" { next.ServeHTTP(w, req) return } - rw := web.Rw(w, req) + // Extract the parent span context from the incoming request. + ctx := otel.GetTextMapPropagator().Extract(req.Context(), propagation.HeaderCarrier(req.Header)) - wireContext := otel.GetTextMapPropagator().Extract(req.Context(), propagation.HeaderCarrier(req.Header)) - ctx, span := tracer.Start(wireContext, fmt.Sprintf("HTTP %s %s", req.Method, req.URL.Path), trace.WithLinks(trace.LinkFromContext(wireContext))) - - req = req.WithContext(ctx) - next.ServeHTTP(w, req) - - // Only call span.Finish when a route operation name have been set, - // meaning that not set the span would not be reported. + // generic span name for requests where there's no route operation name + spanName := fmt.Sprintf("HTTP %s ", req.Method) // TODO: do not depend on web.Context from the future if routeOperation, exists := RouteOperationName(web.FromContext(req.Context()).Req); exists { - defer span.End() - span.SetName(fmt.Sprintf("HTTP %s %s", req.Method, routeOperation)) + spanName = fmt.Sprintf("HTTP %s %s", req.Method, routeOperation) } + ctx, span := tracer.Start(ctx, spanName, trace.WithAttributes( + semconv.HTTPURLKey.String(req.RequestURI), + semconv.HTTPMethodKey.String(req.Method), + ), trace.WithSpanKind(trace.SpanKindServer)) + defer span.End() + + req = req.WithContext(ctx) + + // Ensure the response writer's status can be captured. + rw := web.Rw(w, req) + + next.ServeHTTP(rw, req) + status := rw.Status() span.SetAttributes(semconv.HTTPStatusCode(status)) - span.SetAttributes(semconv.HTTPURL(req.RequestURI)) - span.SetAttributes(semconv.HTTPMethod(req.Method)) if status >= 400 { span.SetStatus(codes.Error, fmt.Sprintf("error with HTTP status code %s", strconv.Itoa(status))) } From ddcbc753d3cf839da9c32d5c6a145f1d5d620305 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 3 Jul 2024 13:38:16 -0700 Subject: [PATCH 28/39] fix tabs to spaces --- .../unified/entitybridge/entitybridge.go | 9 +++++-- pkg/storage/unified/resource/resource.proto | 24 +++++++++---------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/pkg/storage/unified/entitybridge/entitybridge.go b/pkg/storage/unified/entitybridge/entitybridge.go index 7a7288de81f..abc5fc16f9c 100644 --- a/pkg/storage/unified/entitybridge/entitybridge.go +++ b/pkg/storage/unified/entitybridge/entitybridge.go @@ -7,6 +7,7 @@ import ( "path/filepath" "gocloud.dev/blob/fileblob" + "k8s.io/apimachinery/pkg/selection" "k8s.io/klog/v2" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" @@ -244,11 +245,15 @@ func (b *entityBridge) PrepareList(ctx context.Context, req *resource.ListReques WithBody: true, } - // Assumes everything is equals if len(req.Options.Labels) > 0 { query.Labels = make(map[string]string) for _, q := range req.Options.Labels { - query.Labels[q.Key] = q.Values[0] + // The entity structure only supports equals + // the rest will be processed handled by the upstream predicate + op := selection.Operator(q.Operator) + if op == selection.Equals || op == selection.DoubleEquals { + query.Labels[q.Key] = q.Values[0] + } } } diff --git a/pkg/storage/unified/resource/resource.proto b/pkg/storage/unified/resource/resource.proto index b7b741a8bdd..5471d2c8c1c 100644 --- a/pkg/storage/unified/resource/resource.proto +++ b/pkg/storage/unified/resource/resource.proto @@ -154,9 +154,9 @@ message ReadResponse { // The label filtering requirements: // https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/labels/selector.go#L141 message Requirement { - string key = 1; + string key = 1; string operator = 2; // See https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/selection/operator.go#L21 - repeated string values = 3; // typically one value, but depends on the operator + repeated string values = 3; // typically one value, but depends on the operator } message Sort { @@ -164,7 +164,7 @@ message Sort { ASC = 0; DESC = 1; } - string field = 1; + string field = 1; Order order = 2; } @@ -217,16 +217,16 @@ message ListResponse { int64 resource_version = 3; // remainingItemCount is the number of subsequent items in the list which are not included in this - // list response. If the list request contained label or field selectors, then the number of - // remaining items is unknown and the field will be left unset and omitted during serialization. - // If the list is complete (either because it is not chunking or because this is the last chunk), - // then there are no more remaining items and this field will be left unset and omitted during - // serialization. + // list response. If the list request contained label or field selectors, then the number of + // remaining items is unknown and the field will be left unset and omitted during serialization. + // If the list is complete (either because it is not chunking or because this is the last chunk), + // then there are no more remaining items and this field will be left unset and omitted during + // serialization. // - // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients - // should not rely on the remainingItemCount to be set or to be exact. - // +optional - int64 remaining_item_count = 4; // 0 won't be set either (no next page token) + // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients + // should not rely on the remainingItemCount to be set or to be exact. + // +optional + int64 remaining_item_count = 4; // 0 won't be set either (no next page token) } message WatchRequest { From 411bab6d4476f1bb0403408d5625724cb6c89aa7 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Wed, 3 Jul 2024 16:46:28 -0400 Subject: [PATCH 29/39] Alerting: Lower severity of logs about duplicates to debug (#89971) lower severity of logs about duplicates to debug --- pkg/services/ngalert/state/cache.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/services/ngalert/state/cache.go b/pkg/services/ngalert/state/cache.go index 25e8a083b67..d6fca25632c 100644 --- a/pkg/services/ngalert/state/cache.go +++ b/pkg/services/ngalert/state/cache.go @@ -183,7 +183,7 @@ func calculateState(ctx context.Context, log log.Logger, alertRule *ngModels.Ale } } if len(dupes) > 0 { - log.Warn("Rule declares one or many reserved labels. Those rules labels will be ignored", "labels", dupes) + log.Debug("Rule declares one or many reserved labels. Those rules labels will be ignored", "labels", dupes) } dupes = make(data.Labels) for key, val := range resultLabels { @@ -196,7 +196,7 @@ func calculateState(ctx context.Context, log log.Logger, alertRule *ngModels.Ale } } if len(dupes) > 0 { - log.Warn("Evaluation result contains either reserved labels or labels declared in the rules. Those labels from the result will be ignored", "labels", dupes) + log.Debug("Evaluation result contains either reserved labels or labels declared in the rules. Those labels from the result will be ignored", "labels", dupes) } cacheID := lbs.Fingerprint() From d09979b3326633c87dbf00fe3f454500ec70458e Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 3 Jul 2024 13:55:12 -0700 Subject: [PATCH 30/39] check for deleted --- pkg/storage/unified/resource/cdk_backend.go | 35 +++++++++++++-------- pkg/storage/unified/resource/server_test.go | 10 ++++++ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/pkg/storage/unified/resource/cdk_backend.go b/pkg/storage/unified/resource/cdk_backend.go index 3d3415131ff..c37ce5c518c 100644 --- a/pkg/storage/unified/resource/cdk_backend.go +++ b/pkg/storage/unified/resource/cdk_backend.go @@ -167,15 +167,11 @@ func (s *cdkBackend) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, } raw, err := s.bucket.ReadAll(ctx, path) - if err == nil && bytes.Contains(raw, []byte(`"DeletedMarker"`)) { - tmp := &unstructured.Unstructured{} - err = tmp.UnmarshalJSON(raw) - if err == nil && tmp.GetKind() == "DeletedMarker" { - return nil, apierrors.NewNotFound(schema.GroupResource{ - Group: req.Key.Group, - Resource: req.Key.Resource, - }, req.Key.Name) - } + if err == nil && isDeletedMarker(raw) { + return nil, apierrors.NewNotFound(schema.GroupResource{ + Group: req.Key.Group, + Resource: req.Key.Resource, + }, req.Key.Name) } return &ReadResponse{ @@ -184,6 +180,17 @@ func (s *cdkBackend) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, }, err } +func isDeletedMarker(raw []byte) bool { + if bytes.Contains(raw, []byte(`"DeletedMarker"`)) { + tmp := &unstructured.Unstructured{} + err := tmp.UnmarshalJSON(raw) + if err == nil && tmp.GetKind() == "DeletedMarker" { + return true + } + } + return false +} + // List implements AppendingStore. func (s *cdkBackend) PrepareList(ctx context.Context, req *ListRequest) (*ListResponse, error) { resources, err := buildTree(ctx, s, req.Options.Key) @@ -198,10 +205,12 @@ func (s *cdkBackend) PrepareList(ctx context.Context, req *ListRequest) (*ListRe if err != nil { return nil, err } - rsp.Items = append(rsp.Items, &ResourceWrapper{ - ResourceVersion: latest.rv, - Value: raw, - }) + if !isDeletedMarker(raw) { + rsp.Items = append(rsp.Items, &ResourceWrapper{ + ResourceVersion: latest.rv, + Value: raw, + }) + } } return rsp, nil } diff --git a/pkg/storage/unified/resource/server_test.go b/pkg/storage/unified/resource/server_test.go index 6b21c259f27..4589b071dcc 100644 --- a/pkg/storage/unified/resource/server_test.go +++ b/pkg/storage/unified/resource/server_test.go @@ -132,6 +132,16 @@ func TestSimpleServer(t *testing.T) { require.NoError(t, err) require.NotNil(t, found.Status) require.Equal(t, int32(404), found.Status.Code) + + // And the deleted value should not be in the results + all, err = server.List(ctx, &ListRequest{Options: &ListOptions{ + Key: &ResourceKey{ + Group: key.Group, + Resource: key.Resource, + }, + }}) + require.NoError(t, err) + require.Len(t, all.Items, 0) // empty }) } From f70f60efd023ec9e2a3788e124a336ee64cfea11 Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Wed, 3 Jul 2024 17:30:41 -0400 Subject: [PATCH 31/39] localdev: Enable profiling on local dev environment (#89727) Signed-off-by: Dave Henderson --- .bra.toml | 4 ++-- Makefile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bra.toml b/.bra.toml index b57cf283edc..3e9d9992661 100644 --- a/.bra.toml +++ b/.bra.toml @@ -2,7 +2,7 @@ init_cmds = [ ["GO_BUILD_DEV=1", "make", "build-go"], ["make", "gen-jsonnet"], - ["./bin/grafana", "server", "-packaging=dev", "cfg:app_mode=development"] + ["./bin/grafana", "server", "-profile", "-profile-addr=127.0.0.1", "-profile-port=6000", "-packaging=dev", "cfg:app_mode=development"] ] watch_all = true follow_symlinks = true @@ -18,5 +18,5 @@ build_delay = 1500 cmds = [ ["GO_BUILD_DEV=1", "make", "build-go"], ["make", "gen-jsonnet"], - ["./bin/grafana", "server", "-packaging=dev", "cfg:app_mode=development"] + ["./bin/grafana", "server", "-profile", "-profile-addr=127.0.0.1", "-profile-port=6000", "-packaging=dev", "cfg:app_mode=development"] ] diff --git a/Makefile b/Makefile index 8b449e16fbf..50c914ff0db 100644 --- a/Makefile +++ b/Makefile @@ -216,13 +216,13 @@ build-plugin-go: ## Build decoupled plugins build: build-go build-js ## Build backend and frontend. .PHONY: run -run: $(BRA) ## Build and run web server on filesystem changes. +run: $(BRA) ## Build and run web server on filesystem changes. See /.bra.toml for configuration. $(BRA) run .PHONY: run-go run-go: ## Build and run web server immediately. $(GO) run -race $(if $(GO_BUILD_TAGS),-build-tags=$(GO_BUILD_TAGS)) \ - ./pkg/cmd/grafana -- server -packaging=dev cfg:app_mode=development + ./pkg/cmd/grafana -- server -profile -profile-addr=127.0.0.1 -profile-port=6000 -packaging=dev cfg:app_mode=development .PHONY: run-frontend run-frontend: deps-js ## Fetch js dependencies and watch frontend for rebuild From 9fa906ab809ca18f055f6b5d332b730dda5130ae Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 3 Jul 2024 14:45:14 -0700 Subject: [PATCH 32/39] add doc.go --- .../unified/entitybridge/entitybridge.go | 2 +- pkg/storage/unified/resource/cdk_backend.go | 56 ++++++------------- pkg/storage/unified/resource/server_test.go | 37 +++++++----- .../resource/testdata/01_create_playlist.json | 23 -------- 4 files changed, 43 insertions(+), 75 deletions(-) delete mode 100644 pkg/storage/unified/resource/testdata/01_create_playlist.json diff --git a/pkg/storage/unified/entitybridge/entitybridge.go b/pkg/storage/unified/entitybridge/entitybridge.go index abc5fc16f9c..7d47c001b27 100644 --- a/pkg/storage/unified/entitybridge/entitybridge.go +++ b/pkg/storage/unified/entitybridge/entitybridge.go @@ -28,7 +28,7 @@ func ProvideResourceServer(db db.DB, cfg *setting.Cfg, features featuremgmt.Feat Tracer: tracer, } - useEntitySQL := true + useEntitySQL := false if useEntitySQL { eDB, err := dbimpl.ProvideEntityDB(db, cfg, features, tracer) if err != nil { diff --git a/pkg/storage/unified/resource/cdk_backend.go b/pkg/storage/unified/resource/cdk_backend.go index c37ce5c518c..fc201b13413 100644 --- a/pkg/storage/unified/resource/cdk_backend.go +++ b/pkg/storage/unified/resource/cdk_backend.go @@ -69,8 +69,9 @@ type cdkBackend struct { nextRV NextResourceVersion mutex sync.Mutex - // Typically one... the server wrapper - subscribers []chan *WrittenEvent + // Simple watch stream -- NOTE, this only works for single tenant! + broadcaster Broadcaster[*WrittenEvent] + stream chan<- *WrittenEvent } func (s *cdkBackend) getPath(key *ResourceKey, rv int64) string { @@ -123,24 +124,19 @@ func (s *cdkBackend) WriteEvent(ctx context.Context, event WriteEvent) (rv int64 } // Async notify all subscribers - if s.subscribers != nil { + if s.stream != nil { go func() { write := &WrittenEvent{ - WriteEvent: event, - + WriteEvent: event, Timestamp: time.Now().UnixMilli(), ResourceVersion: rv, } - for _, sub := range s.subscribers { - sub <- write - } + s.stream <- write }() } - return rv, err } -// Read implements ResourceStoreServer. func (s *cdkBackend) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, error) { rv := req.ResourceVersion @@ -191,7 +187,6 @@ func isDeletedMarker(raw []byte) bool { return false } -// List implements AppendingStore. func (s *cdkBackend) PrepareList(ctx context.Context, req *ListRequest) (*ListResponse, error) { resources, err := buildTree(ctx, s, req.Options.Key) if err != nil { @@ -215,36 +210,21 @@ func (s *cdkBackend) PrepareList(ctx context.Context, req *ListRequest) (*ListRe return rsp, nil } -// Watch implements AppendingStore. func (s *cdkBackend) WatchWriteEvents(ctx context.Context) (<-chan *WrittenEvent, error) { - stream := make(chan *WrittenEvent, 10) - { - s.mutex.Lock() - defer s.mutex.Unlock() + s.mutex.Lock() + defer s.mutex.Unlock() - // Add the event stream - s.subscribers = append(s.subscribers, stream) - } - - // Wait for context done - go func() { - // Wait till the context is done - <-ctx.Done() - - // Then remove the subscription - s.mutex.Lock() - defer s.mutex.Unlock() - - // Copy all streams without our listener - subs := []chan *WrittenEvent{} - for _, sub := range s.subscribers { - if sub != stream { - subs = append(subs, sub) - } + if s.broadcaster == nil { + var err error + s.broadcaster, err = NewBroadcaster(context.Background(), func(c chan<- *WrittenEvent) error { + s.stream = c + return nil + }) + if err != nil { + return nil, err } - s.subscribers = subs - }() - return stream, nil + } + return s.broadcaster.Subscribe(ctx) } // group > resource > namespace > name > versions diff --git a/pkg/storage/unified/resource/server_test.go b/pkg/storage/unified/resource/server_test.go index 4589b071dcc..e3bfd646e08 100644 --- a/pkg/storage/unified/resource/server_test.go +++ b/pkg/storage/unified/resource/server_test.go @@ -2,7 +2,6 @@ package resource import ( "context" - "embed" "encoding/json" "fmt" "os" @@ -39,7 +38,6 @@ func TestSimpleServer(t *testing.T) { Metadata: fileblob.MetadataDontWrite, // skip }) require.NoError(t, err) - fmt.Printf("ROOT: %s\n\n", tmp) } store, err := NewCDKBackend(ctx, CDKBackendOptions{ @@ -53,7 +51,30 @@ func TestSimpleServer(t *testing.T) { require.NoError(t, err) t.Run("playlist happy CRUD paths", func(t *testing.T) { - raw := testdata(t, "01_create_playlist.json") + raw := []byte(`{ + "apiVersion": "playlist.grafana.app/v0alpha1", + "kind": "Playlist", + "metadata": { + "name": "fdgsv37qslr0ga", + "namespace": "default", + "annotations": { + "grafana.app/originName": "elsewhere", + "grafana.app/originPath": "path/to/item", + "grafana.app/originTimestamp": "2024-02-02T00:00:00Z" + } + }, + "spec": { + "title": "hello", + "interval": "5m", + "items": [ + { + "type": "dashboard_by_uid", + "value": "vmie2cmWz" + } + ] + } + }`) + key := &ResourceKey{ Group: "playlist.grafana.app", Resource: "rrrr", // can be anything :( @@ -144,13 +165,3 @@ func TestSimpleServer(t *testing.T) { require.Len(t, all.Items, 0) // empty }) } - -//go:embed testdata/* -var testdataFS embed.FS - -func testdata(t *testing.T, filename string) []byte { - t.Helper() - b, err := testdataFS.ReadFile(`testdata/` + filename) - require.NoError(t, err) - return b -} diff --git a/pkg/storage/unified/resource/testdata/01_create_playlist.json b/pkg/storage/unified/resource/testdata/01_create_playlist.json deleted file mode 100644 index 151435fe1c6..00000000000 --- a/pkg/storage/unified/resource/testdata/01_create_playlist.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "apiVersion": "playlist.grafana.app/v0alpha1", - "kind": "Playlist", - "metadata": { - "name": "fdgsv37qslr0ga", - "namespace": "default", - "annotations": { - "grafana.app/originName": "elsewhere", - "grafana.app/originPath": "path/to/item", - "grafana.app/originTimestamp": "2024-02-02T00:00:00Z" - } - }, - "spec": { - "title": "hello", - "interval": "5m", - "items": [ - { - "type": "dashboard_by_uid", - "value": "vmie2cmWz" - } - ] - } -} \ No newline at end of file From 53f16521a7af529d0ae590d03b2d9d39342b97ef Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 3 Jul 2024 14:45:25 -0700 Subject: [PATCH 33/39] add doc.go --- pkg/storage/unified/apistore/doc.go | 5 +++++ pkg/storage/unified/entitybridge/doc.go | 5 +++++ pkg/storage/unified/resource/doc.go | 2 ++ 3 files changed, 12 insertions(+) create mode 100644 pkg/storage/unified/apistore/doc.go create mode 100644 pkg/storage/unified/entitybridge/doc.go create mode 100644 pkg/storage/unified/resource/doc.go diff --git a/pkg/storage/unified/apistore/doc.go b/pkg/storage/unified/apistore/doc.go new file mode 100644 index 00000000000..5fb2f8ac31f --- /dev/null +++ b/pkg/storage/unified/apistore/doc.go @@ -0,0 +1,5 @@ +// Package apistore provides a kubernetes store.Interface for a ResourceServer +// +// This package is responsible for running all the apiserver specific logic +// before and after sending requests to the StorageServer +package apistore diff --git a/pkg/storage/unified/entitybridge/doc.go b/pkg/storage/unified/entitybridge/doc.go new file mode 100644 index 00000000000..aed47e41441 --- /dev/null +++ b/pkg/storage/unified/entitybridge/doc.go @@ -0,0 +1,5 @@ +// Package entitybridge implements an ResourceServer using existing EntityAPI contracts +// +// This package will be removed and replaced with a more streamlined SQL implementation +// that leverages what we have learned from the entity deployments so far +package entitybridge diff --git a/pkg/storage/unified/resource/doc.go b/pkg/storage/unified/resource/doc.go new file mode 100644 index 00000000000..9c186ed6c46 --- /dev/null +++ b/pkg/storage/unified/resource/doc.go @@ -0,0 +1,2 @@ +// Package resource creates a ResourceServer that handles generic storage operations +package resource From 274bd08afc4a037b36d5701a2c65a86e180ec183 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:11:45 -0400 Subject: [PATCH 34/39] K8s: Improve key generation and parsing (#90014) --- pkg/apiserver/go.mod | 6 +- pkg/apiserver/registry/generic/key.go | 92 ++++++++------ pkg/apiserver/registry/generic/key_test.go | 120 ++++++++++++++++++ .../apiserver/storage/entity/utils_test.go | 4 +- .../sqlstash/testdata/grpc-req-create.json | 2 +- .../sqlstash/testdata/grpc-req-delete.json | 2 +- .../sqlstash/testdata/grpc-req-update.json | 2 +- .../sqlstash/testdata/grpc-res-entity.json | 2 +- 8 files changed, 181 insertions(+), 49 deletions(-) create mode 100644 pkg/apiserver/registry/generic/key_test.go diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 828015a3f06..74055d945c6 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -4,6 +4,7 @@ go 1.21.10 require ( github.com/bwmarrin/snowflake v0.3.0 + github.com/google/go-cmp v0.6.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 github.com/prometheus/client_golang v1.19.0 github.com/stretchr/testify v1.9.0 @@ -11,7 +12,9 @@ require ( k8s.io/apimachinery v0.29.3 k8s.io/apiserver v0.29.2 k8s.io/client-go v0.29.3 + k8s.io/component-base v0.29.2 k8s.io/klog/v2 v2.120.1 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b ) require ( @@ -34,7 +37,6 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240416155748-26353dc0451f // indirect github.com/google/uuid v1.6.0 // indirect @@ -84,9 +86,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.29.3 // indirect - k8s.io/component-base v0.29.2 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/pkg/apiserver/registry/generic/key.go b/pkg/apiserver/registry/generic/key.go index 35ce83a6b35..f257cc03894 100644 --- a/pkg/apiserver/registry/generic/key.go +++ b/pkg/apiserver/registry/generic/key.go @@ -12,63 +12,75 @@ import ( ) type Key struct { - Group string - Resource string - Namespace string - Name string + Group string `json:"group,omitempty"` + Resource string `json:"resource"` + Namespace string `json:"namespace,omitempty"` + Name string `json:"name,omitempty"` } -func ParseKey(key string) (*Key, error) { - // //[/namespaces/][/] - parts := strings.Split(key, "/") - if len(parts) < 3 { - return nil, fmt.Errorf("invalid key (expecting at least 2 parts): %s", key) +// ParseKey parses a key string into a Key. +// Format: [/group/]/resource/[/namespace/][/name/] +func ParseKey(raw string) (*Key, error) { + parts := strings.Split(raw, "/") + key := &Key{} + + // Skip the first empty string + if parts[0] == "" { + parts = parts[1:] } - if parts[0] != "" { - return nil, fmt.Errorf("invalid key (expecting leading slash): %s", key) + for i := 0; i < len(parts); i += 2 { + k := parts[i] + if i+1 >= len(parts) { + return nil, fmt.Errorf("invalid key: %s", raw) + } + v := parts[i+1] + switch k { + case "group": + key.Group = v + case "resource": + key.Resource = v + case "namespace": + key.Namespace = v + case "name": + key.Name = v + default: + return nil, fmt.Errorf("invalid key name: %s", key) + } } - k := &Key{ - Group: parts[1], - Resource: parts[2], + if len(key.Resource) == 0 { + return nil, fmt.Errorf("missing resource: %s", raw) } - if len(parts) == 3 { - return k, nil - } - - if parts[3] != "namespaces" { - k.Name = parts[3] - return k, nil - } - - if len(parts) < 5 { - return nil, fmt.Errorf("invalid key (expecting namespace after 'namespaces'): %s", key) - } - - k.Namespace = parts[4] - - if len(parts) == 5 { - return k, nil - } - - k.Name = parts[5] - - return k, nil + return key, nil } +// String returns the string representation of the Key. func (k *Key) String() string { - s := "/" + k.Group + "/" + k.Resource + var builder strings.Builder + + if len(k.Group) > 0 { + builder.WriteString("/group/") + builder.WriteString(k.Group) + } + if len(k.Resource) > 0 { + builder.WriteString("/resource/") + builder.WriteString(k.Resource) + } if len(k.Namespace) > 0 { - s += "/namespaces/" + k.Namespace + builder.WriteString("/namespace/") + builder.WriteString(k.Namespace) } if len(k.Name) > 0 { - s += "/" + k.Name + builder.WriteString("/name/") + builder.WriteString(k.Name) } - return s + + return builder.String() } +// IsEqual returns true if the keys are equal. func (k *Key) IsEqual(other *Key) bool { return k.Group == other.Group && k.Resource == other.Resource && diff --git a/pkg/apiserver/registry/generic/key_test.go b/pkg/apiserver/registry/generic/key_test.go new file mode 100644 index 00000000000..c000bd58784 --- /dev/null +++ b/pkg/apiserver/registry/generic/key_test.go @@ -0,0 +1,120 @@ +package generic + +import ( + "reflect" + "testing" +) + +func TestParseKey(t *testing.T) { + tests := []struct { + name string + raw string + expected *Key + wantErr bool + }{ + { + name: "All keys", + raw: "/group/test-group/resource/test-resource/namespace/test-namespace/name/test-name", + expected: &Key{Group: "test-group", Resource: "test-resource", Namespace: "test-namespace", Name: "test-name"}, + wantErr: false, + }, + { + name: "Missing group", + raw: "/resource/test-resource/namespace/test-namespace/name/test-name", + expected: &Key{Group: "", Resource: "test-resource", Namespace: "test-namespace", Name: "test-name"}, + wantErr: false, + }, + { + name: "Missing namespace", + raw: "/group/test-group/resource/test-resource/name/test-name", + expected: &Key{Group: "test-group", Resource: "test-resource", Namespace: "", Name: "test-name"}, + wantErr: false, + }, + { + name: "Missing name", + raw: "/group/test-group/resource/test-resource/namespace/test-namespace", + expected: &Key{Group: "test-group", Resource: "test-resource", Namespace: "test-namespace", Name: ""}, + wantErr: false, + }, + { + name: "Missing resource", + raw: "/group/test-group/namespace/test-namespace/name/test-name", + expected: nil, + wantErr: true, + }, + { + name: "Empty string", + raw: "", + expected: nil, + wantErr: true, + }, + { + name: "Invalid key", + raw: "/", + expected: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseKey(tt.raw) + if (err != nil) != tt.wantErr { + t.Errorf("ParseKey() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("ParseKey() = %v, expected %v", got, tt.expected) + } + }) + } +} + +func BenchmarkKey_String(b *testing.B) { + key := &Key{Group: "test-group", Resource: "test-resource", Namespace: "test-namespace", Name: "test-name"} + for i := 0; i < b.N; i++ { + _ = key.String() + } +} +func TestKey_String(t *testing.T) { + tests := []struct { + name string + key *Key + expected string + }{ + { + name: "All fields", + key: &Key{Group: "test-group", Resource: "test-resource", Namespace: "test-namespace", Name: "test-name"}, + expected: "/group/test-group/resource/test-resource/namespace/test-namespace/name/test-name", + }, + { + name: "Missing group", + key: &Key{Resource: "test-resource", Namespace: "test-namespace", Name: "test-name"}, + expected: "/resource/test-resource/namespace/test-namespace/name/test-name", + }, + { + name: "Missing namespace", + key: &Key{Group: "test-group", Resource: "test-resource", Name: "test-name"}, + expected: "/group/test-group/resource/test-resource/name/test-name", + }, + { + name: "Missing name", + key: &Key{Group: "test-group", Resource: "test-resource", Namespace: "test-namespace"}, + expected: "/group/test-group/resource/test-resource/namespace/test-namespace", + }, + { + name: "Missing resource", + key: &Key{Group: "test-group", Namespace: "test-namespace", Name: "test-name"}, + expected: "/group/test-group/namespace/test-namespace/name/test-name", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.key.String() + if got != tt.expected { + t.Errorf("Key.String() = %s, expected %s", got, tt.expected) + } + }) + } +} diff --git a/pkg/services/apiserver/storage/entity/utils_test.go b/pkg/services/apiserver/storage/entity/utils_test.go index 950beec6012..5136b6a44d6 100644 --- a/pkg/services/apiserver/storage/entity/utils_test.go +++ b/pkg/services/apiserver/storage/entity/utils_test.go @@ -86,7 +86,7 @@ func TestResourceToEntity(t *testing.T) { }, }, }, - expectedKey: "/playlist.grafana.app/playlists/namespaces/default/test-name", + expectedKey: "/group/playlist.grafana.app/resource/playlists/namespace/default/name/test-name", expectedGroupVersion: "v0alpha1", expectedName: "test-name", expectedNamespace: "default", @@ -157,7 +157,7 @@ func TestEntityToResource(t *testing.T) { }{ { entity: &entityStore.Entity{ - Key: "/playlist.grafana.app/playlists/namespaces/default/test-uid", + Key: "/group/playlist.grafana.app/resource/playlists/namespaces/default/name/test-uid", GroupVersion: "v0alpha1", Name: "test-uid", Title: "A playlist", diff --git a/pkg/services/store/entity/sqlstash/testdata/grpc-req-create.json b/pkg/services/store/entity/sqlstash/testdata/grpc-req-create.json index 27a8fd2ca07..ae7c2a0125b 100644 --- a/pkg/services/store/entity/sqlstash/testdata/grpc-req-create.json +++ b/pkg/services/store/entity/sqlstash/testdata/grpc-req-create.json @@ -6,7 +6,7 @@ "namespace": "default", "name": "adnj1llchbbi8a", "group_version": "v0alpha1", - "key": "/playlist.grafana.app/playlists/namespaces/default/adnj1llchbbi8a", + "key": "/group/playlist.grafana.app/resource/playlists/namespaces/default/name/adnj1llchbbi8a", "meta": "eyJtZXRhZGF0YSI6eyJuYW1lIjoiYWRuajFsbGNoYmJpOGEiLCJuYW1lc3BhY2UiOiJkZWZhdWx0IiwidWlkIjoiYjAxOTljNjAtNWYzYS00MWJlLTliYTYtN2E1MmYxZGU4M2ZmIiwiY3JlYXRpb25UaW1lc3RhbXAiOiIyMDI0LTA2LTAyVDAzOjI4OjE3WiIsImFubm90YXRpb25zIjp7ImdyYWZhbmEuYXBwL29yaWdpbktleSI6IjIiLCJncmFmYW5hLmFwcC9vcmlnaW5OYW1lIjoiU1FMIiwiZ3JhZmFuYS5hcHAvb3JpZ2luVGltZXN0YW1wIjoiMjAyNC0wNi0wMlQwMzoyODoxN1oiLCJncmFmYW5hLmFwcC91cGRhdGVkVGltZXN0YW1wIjoiMjAyNC0wNi0wMlQwMzoyODoxN1oifX19", "body": "eyJraW5kIjoiUGxheWxpc3QiLCJhcGlWZXJzaW9uIjoicGxheWxpc3QuZ3JhZmFuYS5hcHAvdjBhbHBoYTEiLCJtZXRhZGF0YSI6eyJuYW1lIjoiYWRuajFsbGNoYmJpOGEiLCJuYW1lc3BhY2UiOiJkZWZhdWx0IiwidWlkIjoiYjAxOTljNjAtNWYzYS00MWJlLTliYTYtN2E1MmYxZGU4M2ZmIiwiY3JlYXRpb25UaW1lc3RhbXAiOiIyMDI0LTA2LTAyVDAzOjI4OjE3WiIsImFubm90YXRpb25zIjp7ImdyYWZhbmEuYXBwL29yaWdpbktleSI6IjIiLCJncmFmYW5hLmFwcC9vcmlnaW5OYW1lIjoiU1FMIiwiZ3JhZmFuYS5hcHAvb3JpZ2luVGltZXN0YW1wIjoiMjAyNC0wNi0wMlQwMzoyODoxN1oiLCJncmFmYW5hLmFwcC91cGRhdGVkVGltZXN0YW1wIjoiMjAyNC0wNi0wMlQwMzoyODoxN1oifX0sInNwZWMiOnsidGl0bGUiOiJ0ZXN0IHBsYXlsaXN0IiwiaW50ZXJ2YWwiOiI1bSIsIml0ZW1zIjpbeyJ0eXBlIjoiZGFzaGJvYXJkX2J5X3VpZCIsInZhbHVlIjoiY2RuaXY1M2dtZDR3MGUifV19fQo=", "title": "test playlist", diff --git a/pkg/services/store/entity/sqlstash/testdata/grpc-req-delete.json b/pkg/services/store/entity/sqlstash/testdata/grpc-req-delete.json index 4802693b534..a3a593b17d3 100644 --- a/pkg/services/store/entity/sqlstash/testdata/grpc-req-delete.json +++ b/pkg/services/store/entity/sqlstash/testdata/grpc-req-delete.json @@ -1,3 +1,3 @@ { - "key": "/playlist.grafana.app/playlists/namespaces/default/sdfsdfsdf" + "key": "/group/playlist.grafana.app/resource/playlists/namespaces/default/name/sdfsdfsdf" } diff --git a/pkg/services/store/entity/sqlstash/testdata/grpc-req-update.json b/pkg/services/store/entity/sqlstash/testdata/grpc-req-update.json index a94dabcb233..5b93ee07546 100644 --- a/pkg/services/store/entity/sqlstash/testdata/grpc-req-update.json +++ b/pkg/services/store/entity/sqlstash/testdata/grpc-req-update.json @@ -7,7 +7,7 @@ "namespace": "default", "name": "sdfsdfsdf", "group_version": "v0alpha1", - "key": "/playlist.grafana.app/playlists/namespaces/default/sdfsdfsdf", + "key": "/group/playlist.grafana.app/resource/playlists/namespaces/default/name/sdfsdfsdf", "meta": "eyJtZXRhZGF0YSI6eyJuYW1lIjoic2Rmc2Rmc2RmIiwibmFtZXNwYWNlIjoiZGVmYXVsdCIsInVpZCI6IjNjNzY5YjJlLWFhYTctNDZmNi1hYjgzLWUwMzgwNTBhNmE3NSIsInJlc291cmNlVmVyc2lvbiI6IjEiLCJjcmVhdGlvblRpbWVzdGFtcCI6IjIwMjQtMDYtMDJUMDM6NDk6MjlaIiwibWFuYWdlZEZpZWxkcyI6W3sibWFuYWdlciI6Ik1vemlsbGEiLCJvcGVyYXRpb24iOiJVcGRhdGUiLCJhcGlWZXJzaW9uIjoicGxheWxpc3QuZ3JhZmFuYS5hcHAvdjBhbHBoYTEiLCJ0aW1lIjoiMjAyNC0wNi0wMlQwMzo1Mzo1NVoiLCJmaWVsZHNUeXBlIjoiRmllbGRzVjEiLCJmaWVsZHNWMSI6eyJmOnNwZWMiOnsiZjppbnRlcnZhbCI6e30sImY6aXRlbXMiOnt9LCJmOnRpdGxlIjp7fX19fV19fQ==", "body": "eyJraW5kIjoiUGxheWxpc3QiLCJhcGlWZXJzaW9uIjoicGxheWxpc3QuZ3JhZmFuYS5hcHAvdjBhbHBoYTEiLCJtZXRhZGF0YSI6eyJuYW1lIjoic2Rmc2Rmc2RmIiwibmFtZXNwYWNlIjoiZGVmYXVsdCIsInVpZCI6IjNjNzY5YjJlLWFhYTctNDZmNi1hYjgzLWUwMzgwNTBhNmE3NSIsInJlc291cmNlVmVyc2lvbiI6IjEiLCJjcmVhdGlvblRpbWVzdGFtcCI6IjIwMjQtMDYtMDJUMDM6NDk6MjlaIiwibWFuYWdlZEZpZWxkcyI6W3sibWFuYWdlciI6Ik1vemlsbGEiLCJvcGVyYXRpb24iOiJVcGRhdGUiLCJhcGlWZXJzaW9uIjoicGxheWxpc3QuZ3JhZmFuYS5hcHAvdjBhbHBoYTEiLCJ0aW1lIjoiMjAyNC0wNi0wMlQwMzo1Mzo1NVoiLCJmaWVsZHNUeXBlIjoiRmllbGRzVjEiLCJmaWVsZHNWMSI6eyJmOnNwZWMiOnsiZjppbnRlcnZhbCI6e30sImY6aXRlbXMiOnt9LCJmOnRpdGxlIjp7fX19fV19LCJzcGVjIjp7InRpdGxlIjoieHpjdnp4Y3Zxd2Vxd2UiLCJpbnRlcnZhbCI6IjVtIiwiaXRlbXMiOlt7InR5cGUiOiJkYXNoYm9hcmRfYnlfdWlkIiwidmFsdWUiOiJjZG5pdjUzZ21kNHcwZSJ9XX19Cg==", "title": "xzcvzxcvqweqwe", diff --git a/pkg/services/store/entity/sqlstash/testdata/grpc-res-entity.json b/pkg/services/store/entity/sqlstash/testdata/grpc-res-entity.json index b0bf49ededf..438e1292259 100644 --- a/pkg/services/store/entity/sqlstash/testdata/grpc-res-entity.json +++ b/pkg/services/store/entity/sqlstash/testdata/grpc-res-entity.json @@ -6,7 +6,7 @@ "namespace": "default", "name": "sdfsdfsdf", "group_version": "v0alpha1", - "key": "/playlist.grafana.app/playlists/namespaces/default/sdfsdfsdf", + "key": "/group/playlist.grafana.app/resource/playlists/namespace/default/name/sdfsdfsdf", "meta": "eyJtZXRhZGF0YSI6eyJuYW1lIjoic2Rmc2Rmc2RmIiwibmFtZXNwYWNlIjoiZGVmYXVsdCIsInVpZCI6IjAyZmVhOGVlLTk2ZDYtNGIzMy04ZGI5LTU5MmI0NzU4NTM4NSIsImNyZWF0aW9uVGltZXN0YW1wIjoiMjAyNC0wNi0wNFQxNToxODozNFoiLCJtYW5hZ2VkRmllbGRzIjpbeyJtYW5hZ2VyIjoiTW96aWxsYSIsIm9wZXJhdGlvbiI6IlVwZGF0ZSIsImFwaVZlcnNpb24iOiJwbGF5bGlzdC5ncmFmYW5hLmFwcC92MGFscGhhMSIsInRpbWUiOiIyMDI0LTA2LTA0VDE1OjE4OjM0WiIsImZpZWxkc1R5cGUiOiJGaWVsZHNWMSIsImZpZWxkc1YxIjp7ImY6c3BlYyI6eyJmOmludGVydmFsIjp7fSwiZjppdGVtcyI6e30sImY6dGl0bGUiOnt9fX19XX19", "body": "eyJraW5kIjoiUGxheWxpc3QiLCJhcGlWZXJzaW9uIjoicGxheWxpc3QuZ3JhZmFuYS5hcHAvdjBhbHBoYTEiLCJtZXRhZGF0YSI6eyJuYW1lIjoic2Rmc2Rmc2RmIiwibmFtZXNwYWNlIjoiZGVmYXVsdCIsInVpZCI6IjAyZmVhOGVlLTk2ZDYtNGIzMy04ZGI5LTU5MmI0NzU4NTM4NSIsImNyZWF0aW9uVGltZXN0YW1wIjoiMjAyNC0wNi0wNFQxNToxODozNFoiLCJtYW5hZ2VkRmllbGRzIjpbeyJtYW5hZ2VyIjoiTW96aWxsYSIsIm9wZXJhdGlvbiI6IlVwZGF0ZSIsImFwaVZlcnNpb24iOiJwbGF5bGlzdC5ncmFmYW5hLmFwcC92MGFscGhhMSIsInRpbWUiOiIyMDI0LTA2LTA0VDE1OjE4OjM0WiIsImZpZWxkc1R5cGUiOiJGaWVsZHNWMSIsImZpZWxkc1YxIjp7ImY6c3BlYyI6eyJmOmludGVydmFsIjp7fSwiZjppdGVtcyI6e30sImY6dGl0bGUiOnt9fX19XX0sInNwZWMiOnsidGl0bGUiOiJ4emN2enhjdiIsImludGVydmFsIjoiNW0iLCJpdGVtcyI6W3sidHlwZSI6ImRhc2hib2FyZF9ieV91aWQiLCJ2YWx1ZSI6ImNkbml2NTNnbWQ0dzBlIn1dfX0K", "title": "xzcvzxcv", From de06762852d0b44e4d8637d519ab3f4c56b37d4a Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 3 Jul 2024 15:26:33 -0700 Subject: [PATCH 35/39] K8s: Add basic peakq test w/ resource (#90026) --- pkg/tests/apis/datasource/testdata_test.go | 2 +- pkg/tests/apis/peakq/peakq_test.go | 71 +++++++++++++++++++ .../apis/peakq/testdata/query-generate.yaml | 7 ++ pkg/tests/apis/playlist/playlist_test.go | 2 +- 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 pkg/tests/apis/peakq/peakq_test.go create mode 100644 pkg/tests/apis/peakq/testdata/query-generate.yaml diff --git a/pkg/tests/apis/datasource/testdata_test.go b/pkg/tests/apis/datasource/testdata_test.go index 328cf81e352..da3283ec8e3 100644 --- a/pkg/tests/apis/datasource/testdata_test.go +++ b/pkg/tests/apis/datasource/testdata_test.go @@ -42,7 +42,7 @@ func TestIntegrationTestDatasource(t *testing.T) { t.Run("Check discovery client", func(t *testing.T) { disco := helper.GetGroupVersionInfoJSON("testdata.datasource.grafana.app") - // fmt.Printf("%s", string(disco)) + // fmt.Printf("%s", disco) require.JSONEq(t, `[ { diff --git a/pkg/tests/apis/peakq/peakq_test.go b/pkg/tests/apis/peakq/peakq_test.go new file mode 100644 index 00000000000..cda9024231a --- /dev/null +++ b/pkg/tests/apis/peakq/peakq_test.go @@ -0,0 +1,71 @@ +package playlist + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/tests/apis" + "github.com/grafana/grafana/pkg/tests/testinfra" + "github.com/grafana/grafana/pkg/tests/testsuite" +) + +func TestMain(m *testing.M) { + testsuite.Run(m) +} + +func TestIntegrationFoldersApp(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: false, // required for experimental APIs + EnableFeatureToggles: []string{ + featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the example service + }, + }) + + t.Run("Check discovery client", func(t *testing.T) { + disco := helper.NewDiscoveryClient() + resources, err := disco.ServerResourcesForGroupVersion("peakq.grafana.app/v0alpha1") + require.NoError(t, err) + + v1Disco, err := json.MarshalIndent(resources, "", " ") + require.NoError(t, err) + //fmt.Printf("%s", string(v1Disco)) + require.JSONEq(t, `{ + "kind": "APIResourceList", + "apiVersion": "v1", + "groupVersion": "peakq.grafana.app/v0alpha1", + "resources": [ + { + "name": "querytemplates", + "singularName": "querytemplate", + "namespaced": true, + "kind": "QueryTemplate", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, + { + "name": "querytemplates/render", + "singularName": "", + "namespaced": true, + "kind": "RenderedQuery", + "verbs": [ + "get" + ] + } + ] + }`, string(v1Disco)) + }) +} diff --git a/pkg/tests/apis/peakq/testdata/query-generate.yaml b/pkg/tests/apis/peakq/testdata/query-generate.yaml new file mode 100644 index 00000000000..efbcaccbe1f --- /dev/null +++ b/pkg/tests/apis/peakq/testdata/query-generate.yaml @@ -0,0 +1,7 @@ +apiVersion: peakq.grafana.app/v0alpha1 +kind: QueryTemplate +metadata: + generateName: x # anything is ok here... except yes or true -- they become boolean! +spec: + title: Generated query template + description: A description from here diff --git a/pkg/tests/apis/playlist/playlist_test.go b/pkg/tests/apis/playlist/playlist_test.go index fd4a0939732..1083dde313b 100644 --- a/pkg/tests/apis/playlist/playlist_test.go +++ b/pkg/tests/apis/playlist/playlist_test.go @@ -47,7 +47,7 @@ func TestIntegrationPlaylist(t *testing.T) { // The accepted verbs will change when dual write is enabled disco := h.GetGroupVersionInfoJSON("playlist.grafana.app") - // fmt.Printf("%s", string(disco)) + // fmt.Printf("%s", disco) require.JSONEq(t, `[ { "version": "v0alpha1", From 9e5b88c6dd39c86444fff8ecca86e5a611d95eb6 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 3 Jul 2024 15:56:28 -0700 Subject: [PATCH 36/39] add field selectors --- pkg/services/store/entity/entity.pb.go | 4 +- pkg/services/store/entity/entity_grpc.pb.go | 38 ++++++---- pkg/storage/unified/apistore/storage.go | 11 +++ pkg/storage/unified/resource/resource.pb.go | 83 ++++++++++++--------- pkg/storage/unified/resource/resource.proto | 11 ++- 5 files changed, 92 insertions(+), 55 deletions(-) diff --git a/pkg/services/store/entity/entity.pb.go b/pkg/services/store/entity/entity.pb.go index 97cb0a6d8da..fa46488db0e 100644 --- a/pkg/services/store/entity/entity.pb.go +++ b/pkg/services/store/entity/entity.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.33.0 +// protoc-gen-go v1.34.1 // protoc (unknown) // source: entity.proto @@ -1401,7 +1401,7 @@ type EntityListRequest struct { WithStatus bool `protobuf:"varint,10,opt,name=with_status,json=withStatus,proto3" json:"with_status,omitempty"` // list deleted entities instead of active ones Deleted bool `protobuf:"varint,12,opt,name=deleted,proto3" json:"deleted,omitempty"` - // Limit to a set of origin keys (empty is all) + // Deprecated: Limit to a set of origin keys (empty is all) OriginKeys []string `protobuf:"bytes,13,rep,name=origin_keys,json=originKeys,proto3" json:"origin_keys,omitempty"` } diff --git a/pkg/services/store/entity/entity_grpc.pb.go b/pkg/services/store/entity/entity_grpc.pb.go index fd670d1106a..9d8bed0ed0b 100644 --- a/pkg/services/store/entity/entity_grpc.pb.go +++ b/pkg/services/store/entity/entity_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.3.0 +// - protoc-gen-go-grpc v1.4.0 // - protoc (unknown) // source: entity.proto @@ -15,8 +15,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 const ( EntityStore_Read_FullMethodName = "/entity.EntityStore/Read" @@ -32,6 +32,8 @@ const ( // EntityStoreClient is the client API for EntityStore service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// The entity store provides a basic CRUD (+watch eventually) interface for generic entities type EntityStoreClient interface { Read(ctx context.Context, in *ReadEntityRequest, opts ...grpc.CallOption) (*Entity, error) Create(ctx context.Context, in *CreateEntityRequest, opts ...grpc.CallOption) (*CreateEntityResponse, error) @@ -52,8 +54,9 @@ func NewEntityStoreClient(cc grpc.ClientConnInterface) EntityStoreClient { } func (c *entityStoreClient) Read(ctx context.Context, in *ReadEntityRequest, opts ...grpc.CallOption) (*Entity, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Entity) - err := c.cc.Invoke(ctx, EntityStore_Read_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, EntityStore_Read_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -61,8 +64,9 @@ func (c *entityStoreClient) Read(ctx context.Context, in *ReadEntityRequest, opt } func (c *entityStoreClient) Create(ctx context.Context, in *CreateEntityRequest, opts ...grpc.CallOption) (*CreateEntityResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CreateEntityResponse) - err := c.cc.Invoke(ctx, EntityStore_Create_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, EntityStore_Create_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -70,8 +74,9 @@ func (c *entityStoreClient) Create(ctx context.Context, in *CreateEntityRequest, } func (c *entityStoreClient) Update(ctx context.Context, in *UpdateEntityRequest, opts ...grpc.CallOption) (*UpdateEntityResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UpdateEntityResponse) - err := c.cc.Invoke(ctx, EntityStore_Update_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, EntityStore_Update_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -79,8 +84,9 @@ func (c *entityStoreClient) Update(ctx context.Context, in *UpdateEntityRequest, } func (c *entityStoreClient) Delete(ctx context.Context, in *DeleteEntityRequest, opts ...grpc.CallOption) (*DeleteEntityResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeleteEntityResponse) - err := c.cc.Invoke(ctx, EntityStore_Delete_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, EntityStore_Delete_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -88,8 +94,9 @@ func (c *entityStoreClient) Delete(ctx context.Context, in *DeleteEntityRequest, } func (c *entityStoreClient) History(ctx context.Context, in *EntityHistoryRequest, opts ...grpc.CallOption) (*EntityHistoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(EntityHistoryResponse) - err := c.cc.Invoke(ctx, EntityStore_History_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, EntityStore_History_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -97,8 +104,9 @@ func (c *entityStoreClient) History(ctx context.Context, in *EntityHistoryReques } func (c *entityStoreClient) List(ctx context.Context, in *EntityListRequest, opts ...grpc.CallOption) (*EntityListResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(EntityListResponse) - err := c.cc.Invoke(ctx, EntityStore_List_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, EntityStore_List_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -106,11 +114,12 @@ func (c *entityStoreClient) List(ctx context.Context, in *EntityListRequest, opt } func (c *entityStoreClient) Watch(ctx context.Context, opts ...grpc.CallOption) (EntityStore_WatchClient, error) { - stream, err := c.cc.NewStream(ctx, &EntityStore_ServiceDesc.Streams[0], EntityStore_Watch_FullMethodName, opts...) + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &EntityStore_ServiceDesc.Streams[0], EntityStore_Watch_FullMethodName, cOpts...) if err != nil { return nil, err } - x := &entityStoreWatchClient{stream} + x := &entityStoreWatchClient{ClientStream: stream} return x, nil } @@ -137,8 +146,9 @@ func (x *entityStoreWatchClient) Recv() (*EntityWatchResponse, error) { } func (c *entityStoreClient) IsHealthy(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(HealthCheckResponse) - err := c.cc.Invoke(ctx, EntityStore_IsHealthy_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, EntityStore_IsHealthy_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -148,6 +158,8 @@ func (c *entityStoreClient) IsHealthy(ctx context.Context, in *HealthCheckReques // EntityStoreServer is the server API for EntityStore service. // All implementations should embed UnimplementedEntityStoreServer // for forward compatibility +// +// The entity store provides a basic CRUD (+watch eventually) interface for generic entities type EntityStoreServer interface { Read(context.Context, *ReadEntityRequest) (*Entity, error) Create(context.Context, *CreateEntityRequest) (*CreateEntityResponse, error) @@ -308,7 +320,7 @@ func _EntityStore_List_Handler(srv interface{}, ctx context.Context, dec func(in } func _EntityStore_Watch_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(EntityStoreServer).Watch(&entityStoreWatchServer{stream}) + return srv.(EntityStoreServer).Watch(&entityStoreWatchServer{ServerStream: stream}) } type EntityStore_WatchServer interface { diff --git a/pkg/storage/unified/apistore/storage.go b/pkg/storage/unified/apistore/storage.go index 9ccab9d2ac7..1f158bcd655 100644 --- a/pkg/storage/unified/apistore/storage.go +++ b/pkg/storage/unified/apistore/storage.go @@ -310,6 +310,17 @@ func toListRequest(key string, opts storage.ListOptions) (*resource.ListRequest, } } + if opts.Predicate.Field != nil && !opts.Predicate.Field.Empty() { + requirements := opts.Predicate.Field.Requirements() + for _, r := range requirements { + requirement := &resource.Requirement{Key: r.Field, Operator: string(r.Operator)} + if r.Value != "" { + requirement.Values = append(requirement.Values, r.Value) + } + req.Options.Labels = append(req.Options.Labels, requirement) + } + } + if opts.ResourceVersion != "" { rv, err := strconv.ParseInt(opts.ResourceVersion, 10, 64) if err != nil { diff --git a/pkg/storage/unified/resource/resource.pb.go b/pkg/storage/unified/resource/resource.pb.go index 4f46525bdff..dadb67c2bae 100644 --- a/pkg/storage/unified/resource/resource.pb.go +++ b/pkg/storage/unified/resource/resource.pb.go @@ -1153,12 +1153,16 @@ type ListOptions struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Namespace+Group+Resource+etc + // Group+Namespace+Resource (not name) Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // (best effort) Match label // Allowed to send more results than actually match because the filter will be appled // to the resutls agin in the client. That time with the full field selector Labels []*Requirement `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty"` + // (best effort) fields matcher + // Allowed to send more results than actually match because the filter will be appled + // to the resutls agin in the client. That time with the full field selector + Fields []*Requirement `protobuf:"bytes,3,rep,name=fields,proto3" json:"fields,omitempty"` } func (x *ListOptions) Reset() { @@ -1207,6 +1211,13 @@ func (x *ListOptions) GetLabels() []*Requirement { return nil } +func (x *ListOptions) GetFields() []*Requirement { + if x != nil { + return x.Fields + } + return nil +} + type ListRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1778,13 +1789,16 @@ var file_resource_proto_rawDesc = []byte{ 0x63, 0x65, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x1a, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x45, 0x53, 0x43, 0x10, 0x01, - 0x22, 0x65, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, - 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x22, 0x94, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x06, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x2d, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x22, 0xec, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, + 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0xec, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, @@ -1952,33 +1966,34 @@ var file_resource_proto_depIdxs = []int32{ 1, // 8: resource.Sort.order:type_name -> resource.Sort.Order 4, // 9: resource.ListOptions.key:type_name -> resource.ResourceKey 16, // 10: resource.ListOptions.labels:type_name -> resource.Requirement - 0, // 11: resource.ListRequest.version_match:type_name -> resource.ResourceVersionMatch - 18, // 12: resource.ListRequest.options:type_name -> resource.ListOptions - 5, // 13: resource.ListResponse.items:type_name -> resource.ResourceWrapper - 18, // 14: resource.WatchRequest.options:type_name -> resource.ListOptions - 2, // 15: resource.WatchEvent.type:type_name -> resource.WatchEvent.Type - 25, // 16: resource.WatchEvent.resource:type_name -> resource.WatchEvent.Resource - 25, // 17: resource.WatchEvent.previous:type_name -> resource.WatchEvent.Resource - 3, // 18: resource.HealthCheckResponse.status:type_name -> resource.HealthCheckResponse.ServingStatus - 14, // 19: resource.ResourceStore.Read:input_type -> resource.ReadRequest - 8, // 20: resource.ResourceStore.Create:input_type -> resource.CreateRequest - 10, // 21: resource.ResourceStore.Update:input_type -> resource.UpdateRequest - 12, // 22: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest - 19, // 23: resource.ResourceStore.List:input_type -> resource.ListRequest - 21, // 24: resource.ResourceStore.Watch:input_type -> resource.WatchRequest - 23, // 25: resource.Diagnostics.IsHealthy:input_type -> resource.HealthCheckRequest - 15, // 26: resource.ResourceStore.Read:output_type -> resource.ReadResponse - 9, // 27: resource.ResourceStore.Create:output_type -> resource.CreateResponse - 11, // 28: resource.ResourceStore.Update:output_type -> resource.UpdateResponse - 13, // 29: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse - 20, // 30: resource.ResourceStore.List:output_type -> resource.ListResponse - 22, // 31: resource.ResourceStore.Watch:output_type -> resource.WatchEvent - 24, // 32: resource.Diagnostics.IsHealthy:output_type -> resource.HealthCheckResponse - 26, // [26:33] is the sub-list for method output_type - 19, // [19:26] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 16, // 11: resource.ListOptions.fields:type_name -> resource.Requirement + 0, // 12: resource.ListRequest.version_match:type_name -> resource.ResourceVersionMatch + 18, // 13: resource.ListRequest.options:type_name -> resource.ListOptions + 5, // 14: resource.ListResponse.items:type_name -> resource.ResourceWrapper + 18, // 15: resource.WatchRequest.options:type_name -> resource.ListOptions + 2, // 16: resource.WatchEvent.type:type_name -> resource.WatchEvent.Type + 25, // 17: resource.WatchEvent.resource:type_name -> resource.WatchEvent.Resource + 25, // 18: resource.WatchEvent.previous:type_name -> resource.WatchEvent.Resource + 3, // 19: resource.HealthCheckResponse.status:type_name -> resource.HealthCheckResponse.ServingStatus + 14, // 20: resource.ResourceStore.Read:input_type -> resource.ReadRequest + 8, // 21: resource.ResourceStore.Create:input_type -> resource.CreateRequest + 10, // 22: resource.ResourceStore.Update:input_type -> resource.UpdateRequest + 12, // 23: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest + 19, // 24: resource.ResourceStore.List:input_type -> resource.ListRequest + 21, // 25: resource.ResourceStore.Watch:input_type -> resource.WatchRequest + 23, // 26: resource.Diagnostics.IsHealthy:input_type -> resource.HealthCheckRequest + 15, // 27: resource.ResourceStore.Read:output_type -> resource.ReadResponse + 9, // 28: resource.ResourceStore.Create:output_type -> resource.CreateResponse + 11, // 29: resource.ResourceStore.Update:output_type -> resource.UpdateResponse + 13, // 30: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse + 20, // 31: resource.ResourceStore.List:output_type -> resource.ListResponse + 22, // 32: resource.ResourceStore.Watch:output_type -> resource.WatchEvent + 24, // 33: resource.Diagnostics.IsHealthy:output_type -> resource.HealthCheckResponse + 27, // [27:34] is the sub-list for method output_type + 20, // [20:27] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name } func init() { file_resource_proto_init() } diff --git a/pkg/storage/unified/resource/resource.proto b/pkg/storage/unified/resource/resource.proto index 5471d2c8c1c..47fe30df579 100644 --- a/pkg/storage/unified/resource/resource.proto +++ b/pkg/storage/unified/resource/resource.proto @@ -169,7 +169,7 @@ message Sort { } message ListOptions { - // Namespace+Group+Resource+etc + // Group+Namespace+Resource (not name) ResourceKey key = 1; // (best effort) Match label @@ -177,11 +177,10 @@ message ListOptions { // to the resutls agin in the client. That time with the full field selector repeated Requirement labels = 2; - // TODO (later!) once we have a blob > search doc - // Match fields (not yet supported) - // metadata.name - // metadata.namespace - // repeated Requirement fields = 3; + // (best effort) fields matcher + // Allowed to send more results than actually match because the filter will be appled + // to the resutls agin in the client. That time with the full field selector + repeated Requirement fields = 3; } enum ResourceVersionMatch { From edc1f50c4e00422eaa06438a6709ff6fbfe353f8 Mon Sep 17 00:00:00 2001 From: Li Zeghong Date: Thu, 4 Jul 2024 07:33:03 +0800 Subject: [PATCH 37/39] Chore: Remove unnecessary typecheck linter config (#89198) --- .golangci.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/.golangci.toml b/.golangci.toml index 003243871c1..d39c3192c31 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -150,7 +150,6 @@ enable = [ "revive", "staticcheck", "stylecheck", - "typecheck", "unconvert", "unused", "whitespace", From fe201b6bb21174bcf4b031e175a9d70232fa04ae Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 3 Jul 2024 18:53:39 -0700 Subject: [PATCH 38/39] FeatureToggles: Update metadata from gitlog (#89766) --- pkg/services/featuremgmt/toggles-gitlog.csv | 74 ++++++++++++--------- pkg/services/featuremgmt/toggles_gen.json | 36 +++++----- 2 files changed, 62 insertions(+), 48 deletions(-) diff --git a/pkg/services/featuremgmt/toggles-gitlog.csv b/pkg/services/featuremgmt/toggles-gitlog.csv index d2ffa07d38f..a0c15aada46 100644 --- a/pkg/services/featuremgmt/toggles-gitlog.csv +++ b/pkg/services/featuremgmt/toggles-gitlog.csv @@ -1,24 +1,24 @@ #name,created,deleted,hash,author -newNavigation,2022-01-26T17:44:20Z,2022-06-16T09:48:38Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -trimDefaults,2022-01-26T17:44:20Z,2023-11-02T15:35:14Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -dashboardPreviews,2022-01-26T17:44:20Z,2023-04-13T17:42:24Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -live-config,2022-01-26T17:44:20Z,2023-02-03T21:21:48Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley accesscontrol,2022-01-26T17:44:20Z,2022-05-16T10:45:41Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -tempoServiceGraph,2022-01-26T17:44:20Z,2022-07-19T07:00:58Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -httpclientprovider_azure_auth,2022-01-26T17:44:20Z,2022-05-30T15:43:32Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -tempoBackendSearch,2022-01-26T17:44:20Z,2022-06-01T17:32:10Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -showFeatureFlagsInUI,2022-01-26T17:44:20Z,2023-02-09T00:01:34Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -queryOverLive,2022-01-26T17:44:20Z,,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -validatedQueries,2022-01-26T17:44:20Z,2022-05-16T21:17:05Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -fullRangeLogsVolume,2022-01-26T17:44:20Z,2022-02-15T08:05:03Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -prometheus_azure_auth,2022-01-26T17:44:20Z,2022-08-11T14:12:57Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -live-service-web-worker,2022-01-26T17:44:20Z,,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -envelopeEncryption,2022-01-26T17:44:20Z,2022-05-24T08:34:47Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +dashboardPreviews,2022-01-26T17:44:20Z,2023-04-13T17:42:24Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley database_metrics,2022-01-26T17:44:20Z,2023-04-28T13:19:06Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -live-pipeline,2022-01-26T17:44:20Z,2023-03-22T18:09:44Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -service-accounts,2022-01-26T17:44:20Z,2022-04-21T09:41:37Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley -tempoSearch,2022-01-26T17:44:20Z,2022-06-01T17:32:10Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley disable_http_request_histogram,2022-01-26T17:44:20Z,2022-06-01T12:33:59Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +envelopeEncryption,2022-01-26T17:44:20Z,2022-05-24T08:34:47Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +fullRangeLogsVolume,2022-01-26T17:44:20Z,2022-02-15T08:05:03Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +httpclientprovider_azure_auth,2022-01-26T17:44:20Z,2022-05-30T15:43:32Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +live-config,2022-01-26T17:44:20Z,2023-02-03T21:21:48Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +live-pipeline,2022-01-26T17:44:20Z,2023-03-22T18:09:44Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +live-service-web-worker,2022-01-26T17:44:20Z,,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +newNavigation,2022-01-26T17:44:20Z,2022-06-16T09:48:38Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +prometheus_azure_auth,2022-01-26T17:44:20Z,2022-08-11T14:12:57Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +queryOverLive,2022-01-26T17:44:20Z,,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +service-accounts,2022-01-26T17:44:20Z,2022-04-21T09:41:37Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +showFeatureFlagsInUI,2022-01-26T17:44:20Z,2023-02-09T00:01:34Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +tempoBackendSearch,2022-01-26T17:44:20Z,2022-06-01T17:32:10Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +tempoSearch,2022-01-26T17:44:20Z,2022-06-01T17:32:10Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +tempoServiceGraph,2022-01-26T17:44:20Z,2022-07-19T07:00:58Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +trimDefaults,2022-01-26T17:44:20Z,2023-11-02T15:35:14Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley +validatedQueries,2022-01-26T17:44:20Z,2022-05-16T21:17:05Z,5d66194ec5d8a7c174a075b6023dcbf58d17b861,Ryan McKinley featureHighlights,2022-02-03T11:53:23Z,,a79c048344bddff7a868b040d9a08953917480f9,Alex Khomenko lokiBackendMode,2022-02-07T07:43:48Z,2022-06-08T06:14:34Z,560c77390550e12e8c0be507c00f27cde0aa31e5,Gábor Farkas swaggerUi,2022-02-08T12:38:43Z,2023-03-01T14:36:37Z,35fe58de374003bb4b077a878cc47ffc0a9d27b5,Sofia Papagiannaki @@ -32,8 +32,8 @@ dashboardComments,2022-02-22T07:47:42Z,2023-03-11T12:28:12Z,28c30a34adbe94d0f08a lokiLive,2022-03-01T22:46:52Z,2023-06-19T10:03:51Z,796bc27f75d52148d5b15cc8c4901276c344df2d,Ryan McKinley fileStoreApi,2022-03-03T06:53:26Z,2022-03-11T18:08:19Z,a8b90d9a2524765c49923c48a7fcf0025c85b733,Artur Wierzbicki azureMonitorResourcePickerForMetrics,2022-03-14T19:07:45Z,2023-01-30T16:19:03Z,275f33cf37bb4221ef120310a87c17d2e0ff5f35,Sarah Zinger -storageLocalUpload,2022-03-17T17:19:23Z,2022-07-18T17:44:42Z,1cfb9a4a1916d3eac473e5ccd0cb77ce281f044f,Ryan McKinley storage,2022-03-17T17:19:23Z,,1cfb9a4a1916d3eac473e5ccd0cb77ce281f044f,Ryan McKinley +storageLocalUpload,2022-03-17T17:19:23Z,2022-07-18T17:44:42Z,1cfb9a4a1916d3eac473e5ccd0cb77ce281f044f,Ryan McKinley saveDashboardDrawer,2022-03-30T17:07:41Z,2022-05-02T16:29:22Z,edf384c730a448a5e90b21bbbce49bc0e70e8197,Ryan McKinley accesscontrol-builtins,2022-03-31T09:40:57Z,2022-05-19T07:29:36Z,0d87de153a2b8406d03efe0cf43b5a926ce7acab,Gabriel MABILLE alertProvisioning,2022-04-01T06:32:00Z,2022-06-05T05:45:36Z,b8e277ee4c070b64ba4cf024b18c460626d1a2d3,Alexander Weaver @@ -65,7 +65,7 @@ lokiDataframeApi,2022-06-13T06:33:46Z,2023-04-13T13:22:09Z,8fd9cb48548313cf43ebc topnav,2022-06-20T14:25:43Z,,3c3293df78344a782fb65e5f977fd148daa597ce,Torkel Ödegaard customBranding,2022-06-22T15:05:52Z,2022-10-05T12:07:35Z,405df77e3e6abcf5264ba192abe33630bd64da20,Tania useLegacyHeatmapPanel,2022-06-23T18:48:28Z,2022-11-23T18:46:21Z,dd5a3b77472884035de13ddd441f41d0dd007e4b,Ryan McKinley -scenes,2022-07-07T06:53:02Z,,935334cbdabef8b0516bfe6c8ed45dd063cce7e9,Torkel Ödegaard +scenes,2022-07-07T06:53:02Z,2024-06-27T07:03:46Z,935334cbdabef8b0516bfe6c8ed45dd063cce7e9,Torkel Ödegaard disableSecretsCompatibility,2022-07-12T20:27:37Z,,2d8a91a8461098f83aa512c867aff5af17b7893e,Guilherme Caulada dashboardsFromStorage,2022-07-14T22:36:17Z,2023-03-20T16:36:49Z,da1701ce576ab26506d6256567b73a597043623a,Ryan McKinley exploreMixedDatasource,2022-07-27T14:40:59Z,2022-07-27T15:17:31Z,e2258120e742b31ecde50e8de93544220a0762a3,Kristina @@ -169,7 +169,7 @@ elasticToggleableFilters,2023-06-27T08:38:20Z,2023-07-28T12:49:02Z,c1ce24c90f75d vizAndWidgetSplit,2023-06-27T10:22:13Z,,2785ed80d999b9ee1770e1209284d4fccd985401,Alexa V nestedFolderPicker,2023-06-28T09:40:29Z,2024-06-04T09:16:12Z,f18a7f7d9696a6e80606dc49d5ac0064384f111d,Josh Hunt frontendSandboxMonitorOnly,2023-07-05T11:48:25Z,,72f6793344fb3a63f5a8a86570b786b518264366,Esteban Beltran -prometheusIncrementalQueryInstrumentation,2023-07-05T19:39:49Z,,daf9f9cd199e0bbc110222a3f005dc05dab1681a,Galen Kistler +prometheusIncrementalQueryInstrumentation,2023-07-05T19:39:49Z,2024-06-20T13:04:22Z,daf9f9cd199e0bbc110222a3f005dc05dab1681a,Galen Kistler dashboardEmbed,2023-07-06T14:43:20Z,2024-04-19T10:48:08Z,420b19e0e4bbdb97ae707cc1360bef7f79839d02,Alex Khomenko awsDatasourcesTempCredentials,2023-07-06T15:06:11Z,,d33508453f6f1f7aab262b54c36ef471ed3eab87,Ida Štambuk logsExploreTableVisualisation,2023-07-12T13:52:42Z,,7e4e743a42052183f549f7fa88cd0cc362844ad1,Sven Grossmann @@ -222,7 +222,7 @@ libraryPanelRBAC,2023-10-11T23:30:50Z,,a12cb8cbf3a9b33841b2f2cb1522be11de78c86a, awsDatasourcesNewFormStyling,2023-10-12T08:59:10Z,,2771fb940342aa152377b26b9554eb15082f90ac,Ida Štambuk cachingOptimizeSerializationMemoryUsage,2023-10-12T16:56:49Z,,94ce87571ddfcede0fb7a229a65502b385d5bca3,Michael Mandrus panelTitleSearchInV1,2023-10-13T12:04:24Z,,bf2f2540da7a4e4b8d80e1fa4ae3d05868cf7b69,Arati R -exploreContentOutline,2023-10-13T16:57:13Z,,4ec54bc2c39ba43843c693fdb2a4529b6a4703f2,Haris Rozajac +exploreContentOutline,2023-10-13T16:57:13Z,2024-06-24T15:45:42Z,4ec54bc2c39ba43843c693fdb2a4529b6a4703f2,Haris Rozajac formatString,2023-10-13T18:17:12Z,,889576ac1d9278b1c6e3e278e8195968646a2db0,Sol pluginsInstrumentationStatusSource,2023-10-17T08:27:45Z,2024-02-21T11:57:40Z,f5076d1868caa14ce44a70e812315541b4199d9f,Giuseppe Guerra teamHttpHeaders,2023-10-17T10:23:54Z,,be5ba6813209b5b24e955e0f761032cb5826b578,Eric Leijonmarck @@ -232,8 +232,8 @@ prometheusPromQAIL,2023-10-19T15:45:32Z,,5580d061019bee46ea2e69c94041f3da14585ce cloudWatchBatchQueries,2023-10-20T19:09:41Z,,ecbc52f51529e1f35e26895db1a10f8a1c2f4244,Isabella Siu alertingContactPointsV2,2023-10-25T13:57:53Z,2023-11-30T12:37:14Z,e12e40fc2493160338237b0b94e72fa530a78ef4,Gilles De Mey alertmanagerRemoteOnly,2023-10-30T16:27:08Z,,363830883cb1f5de30f7015df5cba419df47468e,Santiago -alertmanagerRemoteSecondary,2023-10-30T16:27:08Z,,363830883cb1f5de30f7015df5cba419df47468e,Santiago alertmanagerRemotePrimary,2023-10-30T16:27:08Z,,363830883cb1f5de30f7015df5cba419df47468e,Santiago +alertmanagerRemoteSecondary,2023-10-30T16:27:08Z,,363830883cb1f5de30f7015df5cba419df47468e,Santiago annotationPermissionUpdate,2023-10-31T13:30:13Z,,c51c51458e4dd103aa0c099aa48b0d41f9375f86,Ieva kubernetesPlaylistsAPI,2023-10-31T17:26:39Z,2023-11-08T19:14:05Z,dd773e74f120ba908cafd596a6875c2d7fa199bf,Ryan McKinley traceToProfiles,2023-11-01T10:14:24Z,2024-01-22T14:21:14Z,c39e9a8f527b79881b95f64fd1c413b4bff42983,Joey @@ -271,8 +271,8 @@ alertingQueryOptimization,2024-01-10T20:52:58Z,,afa33f12b2cf50d2c7e438f498d27b06 newFolderPicker,2024-01-15T11:43:19Z,,ec53487c995777b314f566f5a1054e3f8e29ec05,Ashley Harrison kubernetesFeatureToggles,2024-01-18T05:32:44Z,,41e523bde7db5706f339d418c68d019039a8062e,Ryan McKinley returnToPrevious,2024-01-18T17:12:14Z,2024-05-27T15:47:57Z,5800e40fba2accf96d81328f000e35bbb7c7acf1,Laura Fernández -jitterAlertRulesWithinGroups,2024-01-18T18:48:11Z,,00a260effab802edc8f72df50bfb6447aac343f0,Alexander Weaver jitterAlertRules,2024-01-18T18:48:11Z,2024-02-09T21:53:58Z,00a260effab802edc8f72df50bfb6447aac343f0,Alexander Weaver +jitterAlertRulesWithinGroups,2024-01-18T18:48:11Z,,00a260effab802edc8f72df50bfb6447aac343f0,Alexander Weaver onPremToCloudMigrations,2024-01-22T16:09:08Z,,cf13cb9f70c2230f17450667ce59440304fb023c,Michael Mandrus alertingSaveStatePeriodic,2024-01-23T16:03:30Z,,aa25776f813926cb4f1947d4ae5a014f4e7728ff,Jean-Philippe Quéméner promQLScope,2024-01-29T20:22:17Z,,43d0664340f3e3af219d7b5c747f486a073f5ce3,Kyle Brandt @@ -285,17 +285,17 @@ newPDFRendering,2024-02-08T12:09:34Z,,28e66b4ad82ecebb551374325f0be4332412341c,A autoMigrateGraphPanel,2024-02-08T22:00:48Z,,829672759c12b27f849c92c3a2aee4a6b0037920,Nathan Marrs dashboardSceneSolo,2024-02-11T08:08:47Z,,fe6d1460b09b403fc74fd20e95f61513eede2555,Torkel Ödegaard kubernetesAggregator,2024-02-12T20:59:35Z,,d6e6298103d5d6a4efd1c21a3d74f429b506f3a7,Todd Treece -autoMigrateStatPanel,2024-02-14T16:06:25Z,,ce750e06187599da6b9c0a91ef95c7a62fe0d069,Nathan Marrs -autoMigrateWorldmapPanel,2024-02-14T16:06:25Z,,ce750e06187599da6b9c0a91ef95c7a62fe0d069,Nathan Marrs autoMigratePiechartPanel,2024-02-14T16:06:25Z,,ce750e06187599da6b9c0a91ef95c7a62fe0d069,Nathan Marrs +autoMigrateStatPanel,2024-02-14T16:06:25Z,,ce750e06187599da6b9c0a91ef95c7a62fe0d069,Nathan Marrs autoMigrateTablePanel,2024-02-14T16:06:25Z,,ce750e06187599da6b9c0a91ef95c7a62fe0d069,Nathan Marrs +autoMigrateWorldmapPanel,2024-02-14T16:06:25Z,,ce750e06187599da6b9c0a91ef95c7a62fe0d069,Nathan Marrs groupByVariable,2024-02-14T17:18:04Z,,f016f95298fe490a865612864520f3622f8e804a,Dominik Prokop alertingUpgradeDryrunOnStart,2024-02-16T16:29:54Z,2024-03-14T14:36:35Z,dfaf6d1e2e13b2bd11dc8f0cd4432bfcad819aa9,Matthew Jacobson expressionParser,2024-02-17T00:59:11Z,,f23f50f58d7ab5cb1fd88b42b6c58ec09c1a159d,Ryan McKinley sqlExpressions,2024-02-27T21:16:00Z,,70009201d44c2d0ab39cc77081808a69a6c4fd63,Scott Lepper aiGeneratedDashboardChanges,2024-03-05T12:01:31Z,,a7c06d26f14b2a9fa8faa929a6c9a0c355018429,Ivan Ortega Alba scopeFilters,2024-03-05T15:41:19Z,,b3efb4217e48656f24aacdffd5737595d7361afe,Carl Bergquist -betterPageScrolling,2024-03-06T15:06:47Z,,6a4e0c692ab26f4d4cb99ae615013e0b6e23f90b,Josh Hunt +betterPageScrolling,2024-03-06T15:06:47Z,2024-06-18T13:33:08Z,6a4e0c692ab26f4d4cb99ae615013e0b6e23f90b,Josh Hunt emailVerificationEnforcement,2024-03-11T14:09:44Z,2024-03-22T13:30:58Z,0b55d72fb5698e1ea2cf73eaceae166cd5619daa,Karl Persson ssoSettingsSAML,2024-03-14T11:04:45Z,,831ee9ee1696c0aa7a6e4ca022ddfc0ab28b86dc,linoman publicDashboardsScene,2024-03-22T14:48:21Z,,8d4ca72f2a0e66c446d58d8bf13fadbc988fce11,Juan Cabanas @@ -309,17 +309,17 @@ cloudWatchNewLabelParsing,2024-04-05T15:57:56Z,,58f32150c262605d188874b88f44a7de exploreMetrics,2024-04-09T18:15:18Z,,66c0fd4dcc3202e11f41b302d27894dc162fb288,Darren Janeczek accessActionSets,2024-04-12T16:19:25Z,,56f4664875047d6861ea3facbc94cd921e263950,Ieva disableNumericMetricsSortingInExpressions,2024-04-16T14:52:47Z,,d3fee607e2818747ad02daf6b8c58cc801075076,Nick Richmond -queryServiceRewrite,2024-04-19T09:26:21Z,,5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904,Ryan McKinley -queryServiceFromUI,2024-04-19T09:26:21Z,,5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904,Ryan McKinley queryService,2024-04-19T09:26:21Z,,5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904,Ryan McKinley +queryServiceFromUI,2024-04-19T09:26:21Z,,5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904,Ryan McKinley +queryServiceRewrite,2024-04-19T09:26:21Z,,5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904,Ryan McKinley grafanaManagedRecordingRules,2024-04-22T17:53:16Z,,c32953e52cf9fd46a462711bc23fde15a5c3b6bf,Alexander Weaver logsExploreTableDefaultVisualization,2024-05-02T15:28:15Z,,840aeddbd1957117d9b62074c155e87bb4afd4b4,Galen Kistler -autofixDSUID,2024-05-03T11:32:07Z,,b6f899d953a0924760dc5fd5a3d40669c86d475d,Andres Martinez Gotor +autofixDSUID,2024-05-03T11:32:07Z,2024-06-20T10:56:39Z,b6f899d953a0924760dc5fd5a3d40669c86d475d,Andres Martinez Gotor newDashboardSharingComponent,2024-05-03T15:02:18Z,,d1434fad3a68bc1d2b49725be31e45526e6ad349,Juan Cabanas tlsMemcached,2024-05-09T19:12:08Z,,b009536329d110afd807ef2f27f2b7dcc7d310ba,lean.dev notificationBanner,2024-05-13T09:32:34Z,,f3953b4955c218cc4678e842faa3d3380fd0f8f7,Alex Khomenko -dualWritePlaylistsMode3,2024-05-14T12:11:56Z,2024-05-31T18:18:09Z,6836bfe1ea1bf62f4eb66dc1328a456183874f81,Arati R dualWritePlaylistsMode2,2024-05-14T12:11:56Z,2024-05-31T18:18:09Z,6836bfe1ea1bf62f4eb66dc1328a456183874f81,Arati R +dualWritePlaylistsMode3,2024-05-14T12:11:56Z,2024-05-31T18:18:09Z,6836bfe1ea1bf62f4eb66dc1328a456183874f81,Arati R dashboardRestore,2024-05-16T17:36:26Z,,42d75ac737d7ac001a6d53376e25512408a01db5,Ezequiel Victorero datasourceProxyDisableRBAC,2024-05-21T13:05:16Z,,0072e4a92d896df343d9586522d6f7533773da78,Aaron Godin alertingDisableSendAlertsExternal,2024-05-23T12:29:19Z,,8421919cb552b9e8dbd4ebba20bcc67bbc5e6b4f,Steve Simpson @@ -330,3 +330,17 @@ alertingCentralAlertHistory,2024-05-29T15:01:38Z,,289ce6185574df99acea37b86c2e08 pluginProxyPreserveTrailingSlash,2024-06-05T11:36:14Z,,fe3e5917f1bc83ab29b6a57578316e054718aa4a,Marcus Efraimsson kubernetesDashboards,2024-06-05T14:34:23Z,,41e0430f83bf7db50c4caaa1472afa3bf3d5c2dc,Ryan McKinley azureMonitorPrometheusExemplars,2024-06-06T16:53:17Z,,c9778c3332aa93e5dd8bc3b894264e1955f0d593,Andreas Christou +pinNavItems,2024-06-10T11:40:03Z,,84b638fb26cecf856374bb3d09b123061b4b8a6b,Laura Fernández +authZGRPCServer,2024-06-13T09:41:35Z,,afcb5a855c26e985e43861bff6fab36b1b008109,Gabriel MABILLE +openSearchBackendFlowEnabled,2024-06-17T09:41:50Z,,ab2af9b8f75cd13595f4d487c1168e849768a518,Ida Štambuk +ssoSettingsLDAP,2024-06-18T11:31:27Z,,d074cc7892b96a1333bd07011baff146ea71e21d,Mihai Doarna +databaseReadReplica,2024-06-18T15:07:15Z,,50244ed4a1435cbf3e3c87d4af34fd7937f7c259,Kristin Laemmert +disableClassicHTTPHistogram,2024-06-18T19:37:44Z,,3bbc821131f1b10ace139dbb4a6880fb77686646,Dave Henderson +zanzana,2024-06-19T13:59:47Z,,3fe29809bec39239c45d672d686392725773f2e1,Karl Persson +failWrongDSUID,2024-06-20T10:56:39Z,,44fd13c742e606b8409e23eb62cab8bab24310f1,Andres Martinez Gotor +passScopeToDashboardApi,2024-06-20T15:49:19Z,,543e71eb2862187d12e8ee7742badb06e4c913e8,Bogdan Matei +alertingApiServer,2024-06-20T20:52:03Z,,b07592620279f16b0353444e7aba3c457c50d7ec,Yuri Tseretyan +dashboardRestoreUI,2024-06-25T14:43:13Z,,a3879e02bb3b7e8e917ba1bb4163bb230f917f2c,Laura Fernández +cloudWatchRoundUpEndTime,2024-06-27T15:10:28Z,,ba5b33227c343cb2c7dad15ff85a745869c68da9,Ida Štambuk +bodyScrolling,2024-07-01T10:28:39Z,,c0058f9c7e390d8a196f5b375382334287633ea9,Ashley Harrison +cloudwatchMetricInsightsCrossAccount,2024-07-02T10:34:12Z,,36ff0fe63a7710eb496f2f048feb71e6eb6e3c56,Ida Štambuk diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index a9c967e2227..a68a06f0350 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -94,7 +94,7 @@ "metadata": { "name": "alertingApiServer", "resourceVersion": "1718908755156", - "creationTimestamp": "2024-06-20T18:39:15Z" + "creationTimestamp": "2024-06-20T20:52:03Z" }, "spec": { "description": "Register Alerting APIs with the K8s API server", @@ -334,7 +334,7 @@ "metadata": { "name": "authZGRPCServer", "resourceVersion": "1718727528075", - "creationTimestamp": "2024-06-18T16:18:48Z" + "creationTimestamp": "2024-06-13T09:41:35Z" }, "spec": { "description": "Enables the gRPC server for authorization", @@ -440,7 +440,7 @@ "name": "autofixDSUID", "resourceVersion": "1717578796182", "creationTimestamp": "2024-05-03T11:32:07Z", - "deletionTimestamp": "2024-06-18T14:28:32Z" + "deletionTimestamp": "2024-06-20T10:56:39Z" }, "spec": { "description": "Automatically migrates invalid datasource UIDs", @@ -509,7 +509,7 @@ "metadata": { "name": "bodyScrolling", "resourceVersion": "1720021873452", - "creationTimestamp": "2024-07-01T09:10:52Z", + "creationTimestamp": "2024-07-01T10:28:39Z", "annotations": { "grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC" } @@ -626,7 +626,7 @@ "metadata": { "name": "cloudWatchRoundUpEndTime", "resourceVersion": "1720021873452", - "creationTimestamp": "2024-06-25T14:02:23Z", + "creationTimestamp": "2024-06-27T15:10:28Z", "annotations": { "grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC" } @@ -642,7 +642,7 @@ "metadata": { "name": "cloudwatchMetricInsightsCrossAccount", "resourceVersion": "1719497905377", - "creationTimestamp": "2024-06-27T14:18:25Z" + "creationTimestamp": "2024-07-02T10:34:12Z" }, "spec": { "description": "Enables cross account observability for Cloudwatch Metric Insights", @@ -702,7 +702,7 @@ "metadata": { "name": "dashboardRestoreUI", "resourceVersion": "1720021873452", - "creationTimestamp": "2024-06-25T13:15:38Z", + "creationTimestamp": "2024-06-25T14:43:13Z", "annotations": { "grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC" } @@ -774,7 +774,7 @@ "metadata": { "name": "databaseReadReplica", "resourceVersion": "1720021873452", - "creationTimestamp": "2024-06-18T16:18:48Z", + "creationTimestamp": "2024-06-18T15:07:15Z", "annotations": { "grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC" } @@ -864,7 +864,7 @@ "metadata": { "name": "disableClassicHTTPHistogram", "resourceVersion": "1718727528075", - "creationTimestamp": "2024-06-18T16:18:48Z" + "creationTimestamp": "2024-06-18T19:37:44Z" }, "spec": { "description": "Disables classic HTTP Histogram (use with enableNativeHTTPHistogram)", @@ -974,7 +974,7 @@ "name": "exploreContentOutline", "resourceVersion": "1717578796182", "creationTimestamp": "2023-10-13T16:57:13Z", - "deletionTimestamp": "2024-06-17T09:45:00Z" + "deletionTimestamp": "2024-06-24T15:45:42Z" }, "spec": { "description": "Content outline sidebar", @@ -1069,7 +1069,7 @@ "metadata": { "name": "failWrongDSUID", "resourceVersion": "1718721033692", - "creationTimestamp": "2024-06-18T14:30:33Z" + "creationTimestamp": "2024-06-20T10:56:39Z" }, "spec": { "description": "Throws an error if a datasource has an invalid UIDs", @@ -1818,7 +1818,7 @@ "metadata": { "name": "openSearchBackendFlowEnabled", "resourceVersion": "1718727528075", - "creationTimestamp": "2024-06-18T16:18:48Z" + "creationTimestamp": "2024-06-17T09:41:50Z" }, "spec": { "description": "Enables the backend query flow for Open Search datasource plugin", @@ -1887,7 +1887,7 @@ "metadata": { "name": "passScopeToDashboardApi", "resourceVersion": "1718290335877", - "creationTimestamp": "2024-06-13T14:52:15Z" + "creationTimestamp": "2024-06-20T15:49:19Z" }, "spec": { "description": "Enables the passing of scopes to dashboards fetching in Grafana", @@ -1925,7 +1925,7 @@ "metadata": { "name": "pinNavItems", "resourceVersion": "1718727528075", - "creationTimestamp": "2024-06-18T16:18:48Z" + "creationTimestamp": "2024-06-10T11:40:03Z" }, "spec": { "description": "Enables pinning of nav items", @@ -2064,7 +2064,7 @@ "name": "prometheusIncrementalQueryInstrumentation", "resourceVersion": "1718727528075", "creationTimestamp": "2023-07-05T19:39:49Z", - "deletionTimestamp": "2024-06-20T11:30:37Z" + "deletionTimestamp": "2024-06-20T13:04:22Z" }, "spec": { "description": "Adds RudderStack events to incremental queries", @@ -2303,7 +2303,7 @@ "name": "scenes", "resourceVersion": "1718727528075", "creationTimestamp": "2022-07-07T06:53:02Z", - "deletionTimestamp": "2024-06-26T11:58:18Z" + "deletionTimestamp": "2024-06-27T07:03:46Z" }, "spec": { "description": "Experimental framework to build interactive dashboards", @@ -2397,7 +2397,7 @@ "metadata": { "name": "ssoSettingsLDAP", "resourceVersion": "1718727528075", - "creationTimestamp": "2024-06-18T16:18:48Z" + "creationTimestamp": "2024-06-18T11:31:27Z" }, "spec": { "description": "Use the new SSO Settings API to configure LDAP", @@ -2592,7 +2592,7 @@ "metadata": { "name": "zanzana", "resourceVersion": "1718787304727", - "creationTimestamp": "2024-06-19T08:55:04Z" + "creationTimestamp": "2024-06-19T13:59:47Z" }, "spec": { "description": "Use openFGA as authorization engine.", From 8f4b3062d62863a52186873c7547f8ec1e7d9b69 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 3 Jul 2024 19:23:40 -0700 Subject: [PATCH 39/39] fix lint --- pkg/services/store/entity/sqlstash/sql_storage_server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/services/store/entity/sqlstash/sql_storage_server.go b/pkg/services/store/entity/sqlstash/sql_storage_server.go index b5b89530d72..723bf34c52d 100644 --- a/pkg/services/store/entity/sqlstash/sql_storage_server.go +++ b/pkg/services/store/entity/sqlstash/sql_storage_server.go @@ -655,6 +655,7 @@ func (s *sqlEntityServer) List(ctx context.Context, r *entity.EntityListRequest) rvSubQuery.AddWhere("("+strings.Join(where, " OR ")+")", args...) } + // nolint:staticcheck if len(r.OriginKeys) > 0 { entityQuery.AddWhereIn("origin_key", ToAnyList(r.OriginKeys)) rvMaxQuery.AddWhereIn("origin_key", ToAnyList(r.OriginKeys))