merge main
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
@@ -99,6 +100,8 @@ func SetupConfig(
|
||||
handler = filters.WithAcceptHeader(handler)
|
||||
handler = filters.WithPathRewriters(handler, pathRewriters)
|
||||
handler = k8stracing.WithTracing(handler, serverConfig.TracerProvider, "KubernetesAPI")
|
||||
// Configure filters.WithPanicRecovery to not crash on panic
|
||||
utilruntime.ReallyCrash = false
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
@@ -21,25 +21,23 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type ZanzanaClient interface{}
|
||||
|
||||
// ProvideZanzana used to register ZanzanaClient.
|
||||
// It will also start an embedded ZanzanaSever if mode is set to "embedded".
|
||||
func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureToggles) (ZanzanaClient, error) {
|
||||
func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureToggles) (zanzana.Client, error) {
|
||||
if !features.IsEnabledGlobally(featuremgmt.FlagZanzana) {
|
||||
return zanzana.NoopClient{}, nil
|
||||
}
|
||||
|
||||
logger := log.New("zanzana")
|
||||
|
||||
var client *zanzana.Client
|
||||
var client zanzana.Client
|
||||
switch cfg.Zanzana.Mode {
|
||||
case setting.ZanzanaModeClient:
|
||||
conn, err := grpc.NewClient(cfg.Zanzana.Addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err)
|
||||
}
|
||||
client = zanzana.NewClient(openfgav1.NewOpenFGAServiceClient(conn))
|
||||
client = zanzana.NewClient(conn)
|
||||
case setting.ZanzanaModeEmbedded:
|
||||
store, err := zanzana.NewEmbeddedStore(cfg, db, logger)
|
||||
if err != nil {
|
||||
@@ -53,7 +51,7 @@ func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureTogg
|
||||
|
||||
channel := &inprocgrpc.Channel{}
|
||||
openfgav1.RegisterOpenFGAServiceServer(channel, srv)
|
||||
client = zanzana.NewClient(openfgav1.NewOpenFGAServiceClient(channel))
|
||||
client = zanzana.NewClient(channel)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported zanzana mode: %s", cfg.Zanzana.Mode)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,46 @@
|
||||
package zanzana
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
)
|
||||
|
||||
// FIXME(kalleep): Build out our wrapper client for openFGA
|
||||
type Client struct {
|
||||
c openfgav1.OpenFGAServiceClient
|
||||
// Client is a wrapper around OpenFGAServiceClient with only methods using in Grafana included.
|
||||
type Client interface {
|
||||
Check(ctx context.Context, in *openfgav1.CheckRequest, opts ...grpc.CallOption) (*openfgav1.CheckResponse, error)
|
||||
ListObjects(ctx context.Context, in *openfgav1.ListObjectsRequest, opts ...grpc.CallOption) (*openfgav1.ListObjectsResponse, error)
|
||||
}
|
||||
|
||||
func NewClient(c openfgav1.OpenFGAServiceClient) *Client {
|
||||
return &Client{c}
|
||||
type zanzanaClient struct {
|
||||
client openfgav1.OpenFGAServiceClient
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func NewClient(cc grpc.ClientConnInterface) Client {
|
||||
return &zanzanaClient{
|
||||
client: openfgav1.NewOpenFGAServiceClient(cc),
|
||||
logger: log.New("zanzana-client"),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *zanzanaClient) Check(ctx context.Context, in *openfgav1.CheckRequest, opts ...grpc.CallOption) (*openfgav1.CheckResponse, error) {
|
||||
return c.client.Check(ctx, in, opts...)
|
||||
}
|
||||
|
||||
func (c *zanzanaClient) ListObjects(ctx context.Context, in *openfgav1.ListObjectsRequest, opts ...grpc.CallOption) (*openfgav1.ListObjectsResponse, error) {
|
||||
return c.client.ListObjects(ctx, in, opts...)
|
||||
}
|
||||
|
||||
type NoopClient struct{}
|
||||
|
||||
func (nc NoopClient) Check(ctx context.Context, in *openfgav1.CheckRequest, opts ...grpc.CallOption) (*openfgav1.CheckResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (nc NoopClient) ListObjects(ctx context.Context, in *openfgav1.ListObjectsRequest, opts ...grpc.CallOption) (*openfgav1.ListObjectsResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -18,4 +18,5 @@ var (
|
||||
ErrDataSourceNameInvalid = errutil.ValidationFailed("datasource.nameInvalid", errutil.WithPublicMessage("Invalid datasource name."))
|
||||
ErrDataSourceURLInvalid = errutil.ValidationFailed("datasource.urlInvalid", errutil.WithPublicMessage("Invalid datasource url."))
|
||||
ErrDataSourceAPIVersionInvalid = errutil.ValidationFailed("datasource.apiVersionInvalid", errutil.WithPublicMessage("Invalid datasource apiVersion."))
|
||||
ErrDataSourceUIDInvalid = errutil.ValidationFailed("datasource.uidInvalid", errutil.WithPublicMessage("Invalid datasource UID."))
|
||||
)
|
||||
|
||||
@@ -252,8 +252,8 @@ func (ss *SqlStore) AddDataSource(ctx context.Context, cmd *datasources.AddDataS
|
||||
cmd.UID = uid
|
||||
} else if err := util.ValidateUID(cmd.UID); err != nil {
|
||||
logDeprecatedInvalidDsUid(ss.logger, cmd.UID, cmd.Name, "create", err)
|
||||
if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagAutofixDSUID) {
|
||||
return fmt.Errorf("invalid UID for datasource %s: %w", cmd.Name, err)
|
||||
if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagFailWrongDSUID) {
|
||||
return datasources.ErrDataSourceUIDInvalid.Errorf("invalid UID for datasource %s: %w", cmd.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,8 +329,8 @@ func (ss *SqlStore) UpdateDataSource(ctx context.Context, cmd *datasources.Updat
|
||||
if cmd.UID != "" {
|
||||
if err := util.ValidateUID(cmd.UID); err != nil {
|
||||
logDeprecatedInvalidDsUid(ss.logger, cmd.UID, cmd.Name, "update", err)
|
||||
if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagAutofixDSUID) {
|
||||
cmd.UID = util.AutofixUID(cmd.UID)
|
||||
if ss.features != nil && ss.features.IsEnabled(ctx, featuremgmt.FlagFailWrongDSUID) {
|
||||
return datasources.ErrDataSourceUIDInvalid.Errorf("invalid UID for datasource %s: %w", cmd.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func TestIntegrationDataAccess(t *testing.T) {
|
||||
ss := SqlStore{
|
||||
db: db,
|
||||
logger: log.NewNopLogger(),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagAutofixDSUID),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagFailWrongDSUID),
|
||||
}
|
||||
cmd := defaultAddDatasourceCommand
|
||||
cmd.UID = "test/uid"
|
||||
@@ -232,28 +232,21 @@ func TestIntegrationDataAccess(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("updates UID with a valid one", func(t *testing.T) {
|
||||
t.Run("fails to update a datasource with an invalid uid", func(t *testing.T) {
|
||||
db := db.InitTestDB(t)
|
||||
ds := initDatasource(db)
|
||||
ss := SqlStore{
|
||||
db: db,
|
||||
logger: log.NewNopLogger(),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagAutofixDSUID),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagFailWrongDSUID),
|
||||
}
|
||||
require.NotEmpty(t, ds.UID)
|
||||
|
||||
cmd := defaultUpdateDatasourceCommand
|
||||
cmd.ID = ds.ID
|
||||
cmd.UID = "new/uid"
|
||||
res, err := ss.UpdateDataSource(context.Background(), &cmd)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "new-uid", res.UID)
|
||||
|
||||
// Return the datasource with the valid UID
|
||||
query := datasources.GetDataSourceQuery{UID: "new-uid", OrgID: 10}
|
||||
dataSource, err := ss.GetDataSource(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "new-uid", dataSource.UID)
|
||||
_, err := ss.UpdateDataSource(context.Background(), &cmd)
|
||||
require.ErrorContains(t, err, "invalid format of UID")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -480,13 +480,6 @@ var (
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDashboardsSquad,
|
||||
},
|
||||
{
|
||||
Name: "prometheusIncrementalQueryInstrumentation",
|
||||
Description: "Adds RudderStack events to incremental queries",
|
||||
FrontendOnly: true,
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaObservabilityMetricsSquad,
|
||||
},
|
||||
{
|
||||
Name: "logsExploreTableVisualisation",
|
||||
Description: "A table visualisation for logs in Explore",
|
||||
@@ -1222,12 +1215,6 @@ var (
|
||||
FrontendOnly: false,
|
||||
AllowSelfServe: false,
|
||||
},
|
||||
{
|
||||
Name: "autofixDSUID",
|
||||
Description: "Automatically migrates invalid datasource UIDs",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaPluginsPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "logsExploreTableDefaultVisualization",
|
||||
Description: "Sets the logs table as default visualisation in logs explore",
|
||||
@@ -1336,6 +1323,12 @@ var (
|
||||
HideFromDocs: true,
|
||||
HideFromAdminPage: true,
|
||||
},
|
||||
{
|
||||
Name: "failWrongDSUID",
|
||||
Description: "Throws an error if a datasource has an invalid UIDs",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaPluginsPlatformSquad,
|
||||
},
|
||||
{
|
||||
Name: "databaseReadReplica",
|
||||
Description: "Use a read replica for some database queries.",
|
||||
|
||||
@@ -63,7 +63,6 @@ frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,false,
|
||||
sqlDatasourceDatabaseSelection,preview,@grafana/dataviz-squad,false,false,true
|
||||
recordedQueriesMulti,GA,@grafana/observability-metrics,false,false,false
|
||||
vizAndWidgetSplit,experimental,@grafana/dashboards-squad,false,false,true
|
||||
prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,false,false,true
|
||||
logsExploreTableVisualisation,GA,@grafana/observability-logs,false,false,true
|
||||
awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,false,false,false
|
||||
transformationsRedesign,GA,@grafana/observability-metrics,false,false,true
|
||||
@@ -161,7 +160,6 @@ accessActionSets,experimental,@grafana/identity-access-team,false,false,false
|
||||
disableNumericMetricsSortingInExpressions,experimental,@grafana/observability-metrics,false,true,false
|
||||
grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false
|
||||
queryLibrary,experimental,@grafana/explore-squad,false,false,false
|
||||
autofixDSUID,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||
logsExploreTableDefaultVisualization,experimental,@grafana/observability-logs,false,false,true
|
||||
newDashboardSharingComponent,experimental,@grafana/sharing-squad,false,false,true
|
||||
alertingListViewV2,experimental,@grafana/alerting-squad,false,false,true
|
||||
@@ -177,5 +175,6 @@ pinNavItems,experimental,@grafana/grafana-frontend-platform,false,false,false
|
||||
authZGRPCServer,experimental,@grafana/identity-access-team,false,false,false
|
||||
openSearchBackendFlowEnabled,preview,@grafana/aws-datasources,false,false,false
|
||||
ssoSettingsLDAP,experimental,@grafana/identity-access-team,false,false,false
|
||||
failWrongDSUID,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||
databaseReadReplica,experimental,@grafana/grafana-backend-services-squad,false,false,false
|
||||
zanzana,experimental,@grafana/identity-access-team,false,false,false
|
||||
|
||||
|
@@ -263,10 +263,6 @@ const (
|
||||
// Split panels between visualizations and widgets
|
||||
FlagVizAndWidgetSplit = "vizAndWidgetSplit"
|
||||
|
||||
// FlagPrometheusIncrementalQueryInstrumentation
|
||||
// Adds RudderStack events to incremental queries
|
||||
FlagPrometheusIncrementalQueryInstrumentation = "prometheusIncrementalQueryInstrumentation"
|
||||
|
||||
// FlagLogsExploreTableVisualisation
|
||||
// A table visualisation for logs in Explore
|
||||
FlagLogsExploreTableVisualisation = "logsExploreTableVisualisation"
|
||||
@@ -655,10 +651,6 @@ const (
|
||||
// Enables Query Library feature in Explore
|
||||
FlagQueryLibrary = "queryLibrary"
|
||||
|
||||
// FlagAutofixDSUID
|
||||
// Automatically migrates invalid datasource UIDs
|
||||
FlagAutofixDSUID = "autofixDSUID"
|
||||
|
||||
// FlagLogsExploreTableDefaultVisualization
|
||||
// Sets the logs table as default visualisation in logs explore
|
||||
FlagLogsExploreTableDefaultVisualization = "logsExploreTableDefaultVisualization"
|
||||
@@ -719,6 +711,10 @@ const (
|
||||
// Use the new SSO Settings API to configure LDAP
|
||||
FlagSsoSettingsLDAP = "ssoSettingsLDAP"
|
||||
|
||||
// FlagFailWrongDSUID
|
||||
// Throws an error if a datasource has an invalid UIDs
|
||||
FlagFailWrongDSUID = "failWrongDSUID"
|
||||
|
||||
// FlagDatabaseReadReplica
|
||||
// Use a read replica for some database queries.
|
||||
FlagDatabaseReadReplica = "databaseReadReplica"
|
||||
|
||||
@@ -401,8 +401,9 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "autofixDSUID",
|
||||
"resourceVersion": "1718727528075",
|
||||
"creationTimestamp": "2024-05-03T11:32:07Z"
|
||||
"resourceVersion": "1717578796182",
|
||||
"creationTimestamp": "2024-05-03T11:32:07Z",
|
||||
"deletionTimestamp": "2024-06-18T14:28:32Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Automatically migrates invalid datasource UIDs",
|
||||
@@ -915,6 +916,18 @@
|
||||
"frontend": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "failWrongDSUID",
|
||||
"resourceVersion": "1718721033692",
|
||||
"creationTimestamp": "2024-06-18T14:30:33Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Throws an error if a datasource has an invalid UIDs",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/plugins-platform-backend"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "faroDatasourceSelector",
|
||||
@@ -1819,7 +1832,8 @@
|
||||
"metadata": {
|
||||
"name": "prometheusIncrementalQueryInstrumentation",
|
||||
"resourceVersion": "1718727528075",
|
||||
"creationTimestamp": "2023-07-05T19:39:49Z"
|
||||
"creationTimestamp": "2023-07-05T19:39:49Z",
|
||||
"deletionTimestamp": "2024-06-20T11:30:37Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Adds RudderStack events to incremental queries",
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
)
|
||||
@@ -22,8 +21,8 @@ type FakeRuleService struct {
|
||||
AuthorizeDatasourceAccessForRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) error
|
||||
HasAccessToRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) (bool, error)
|
||||
AuthorizeAccessToRuleGroupFunc func(context.Context, identity.Requester, models.RulesGroup) error
|
||||
HasAccessInFolderFunc func(context.Context, identity.Requester, accesscontrol.Namespaced) (bool, error)
|
||||
AuthorizeAccessInFolderFunc func(context.Context, identity.Requester, accesscontrol.Namespaced) error
|
||||
HasAccessInFolderFunc func(context.Context, identity.Requester, models.Namespaced) (bool, error)
|
||||
AuthorizeAccessInFolderFunc func(context.Context, identity.Requester, models.Namespaced) error
|
||||
AuthorizeRuleChangesFunc func(context.Context, identity.Requester, *store.GroupDelta) error
|
||||
|
||||
Calls []Call
|
||||
@@ -77,7 +76,7 @@ func (s *FakeRuleService) AuthorizeAccessToRuleGroup(ctx context.Context, user i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FakeRuleService) HasAccessInFolder(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) (bool, error) {
|
||||
func (s *FakeRuleService) HasAccessInFolder(ctx context.Context, user identity.Requester, namespaced models.Namespaced) (bool, error) {
|
||||
s.Calls = append(s.Calls, Call{"HasAccessInFolder", []interface{}{ctx, user, namespaced}})
|
||||
if s.HasAccessInFolderFunc != nil {
|
||||
return s.HasAccessInFolderFunc(ctx, user, namespaced)
|
||||
@@ -85,7 +84,7 @@ func (s *FakeRuleService) HasAccessInFolder(ctx context.Context, user identity.R
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *FakeRuleService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error {
|
||||
func (s *FakeRuleService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error {
|
||||
s.Calls = append(s.Calls, Call{"AuthorizeAccessInFolder", []interface{}{ctx, user, namespaced}})
|
||||
if s.AuthorizeAccessInFolderFunc != nil {
|
||||
return s.AuthorizeAccessInFolderFunc(ctx, user, namespaced)
|
||||
|
||||
@@ -31,10 +31,6 @@ func NewRuleService(ac accesscontrol.AccessControl) *RuleService {
|
||||
}
|
||||
}
|
||||
|
||||
type Namespaced interface {
|
||||
GetNamespaceUID() string
|
||||
}
|
||||
|
||||
// getReadFolderAccessEvaluator constructs accesscontrol.Evaluator that checks all permissions required to read rules in specific folder
|
||||
func getReadFolderAccessEvaluator(folderUID string) accesscontrol.Evaluator {
|
||||
return accesscontrol.EvalAll(
|
||||
@@ -130,7 +126,7 @@ func (r *RuleService) AuthorizeAccessToRuleGroup(ctx context.Context, user ident
|
||||
// - ("folders:read") read the folder
|
||||
// - ("alert.rules:read") read alert rules in the folder
|
||||
// Returns false if the requester does not have enough permissions, and error if something went wrong during the permission evaluation.
|
||||
func (r *RuleService) HasAccessInFolder(ctx context.Context, user identity.Requester, rule Namespaced) (bool, error) {
|
||||
func (r *RuleService) HasAccessInFolder(ctx context.Context, user identity.Requester, rule models.Namespaced) (bool, error) {
|
||||
eval := accesscontrol.EvalAll(getReadFolderAccessEvaluator(rule.GetNamespaceUID()))
|
||||
return r.HasAccess(ctx, user, eval)
|
||||
}
|
||||
@@ -140,7 +136,7 @@ func (r *RuleService) HasAccessInFolder(ctx context.Context, user identity.Reque
|
||||
// - ("folders:read") read the folder
|
||||
// - ("alert.rules:read") read alert rules in the folder
|
||||
// Returns error if at least one permission is missing or if something went wrong during the permission evaluation
|
||||
func (r *RuleService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, rule Namespaced) error {
|
||||
func (r *RuleService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, rule models.Namespaced) error {
|
||||
eval := accesscontrol.EvalAll(getReadFolderAccessEvaluator(rule.GetNamespaceUID()))
|
||||
return r.HasAccessOrError(ctx, user, eval, func() string {
|
||||
return fmt.Sprintf("access rules in folder '%s'", rule.GetNamespaceUID())
|
||||
|
||||
@@ -45,7 +45,7 @@ type RuleAccessControlService interface {
|
||||
AuthorizeRuleChanges(ctx context.Context, user identity.Requester, change *store.GroupDelta) error
|
||||
AuthorizeDatasourceAccessForRule(ctx context.Context, user identity.Requester, rule *models.AlertRule) error
|
||||
AuthorizeDatasourceAccessForRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) error
|
||||
AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error
|
||||
AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error
|
||||
}
|
||||
|
||||
// API handlers.
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||
@@ -147,7 +146,7 @@ func (f fakeRuleAccessControlService) AuthorizeAccessToRuleGroup(ctx context.Con
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakeRuleAccessControlService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error {
|
||||
func (f fakeRuleAccessControlService) AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -269,6 +269,11 @@ type AlertRule struct {
|
||||
NotificationSettings []NotificationSettings `xorm:"notification_settings"` // we use slice to workaround xorm mapping that does not serialize a struct to JSON unless it's a slice
|
||||
}
|
||||
|
||||
// Namespaced describes a class of resources that are stored in a specific namespace.
|
||||
type Namespaced interface {
|
||||
GetNamespaceUID() string
|
||||
}
|
||||
|
||||
// AlertRuleWithOptionals This is to avoid having to pass in additional arguments deep in the call stack. Alert rule
|
||||
// object is created in an early validation step without knowledge about current alert rule fields or if they need to be
|
||||
// overridden. This is done in a later step and, in that step, we did not have knowledge about if a field was optional
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
@@ -24,7 +23,7 @@ type SilenceService struct {
|
||||
}
|
||||
|
||||
type RuleAccessControlService interface {
|
||||
HasAccessInFolder(ctx context.Context, user identity.Requester, rule accesscontrol.Namespaced) (bool, error)
|
||||
HasAccessInFolder(ctx context.Context, user identity.Requester, rule models.Namespaced) (bool, error)
|
||||
}
|
||||
|
||||
// SilenceAccessControlService provides access control for silences.
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@@ -61,7 +60,7 @@ func TestWithRuleMetadata(t *testing.T) {
|
||||
user := ac.BackgroundUser("test", 1, org.RoleNone, nil)
|
||||
t.Run("Attach rule metadata to silences", func(t *testing.T) {
|
||||
ruleAuthz := fakes.FakeRuleService{}
|
||||
ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence accesscontrol.Namespaced) (bool, error) {
|
||||
ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence models.Namespaced) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -95,7 +94,7 @@ func TestWithRuleMetadata(t *testing.T) {
|
||||
})
|
||||
t.Run("Don't attach full rule metadata if no access or global", func(t *testing.T) {
|
||||
ruleAuthz := fakes.FakeRuleService{}
|
||||
ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence accesscontrol.Namespaced) (bool, error) {
|
||||
ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence models.Namespaced) (bool, error) {
|
||||
return silence.GetNamespaceUID() == "folder1", nil
|
||||
}
|
||||
|
||||
@@ -134,7 +133,7 @@ func TestWithRuleMetadata(t *testing.T) {
|
||||
})
|
||||
t.Run("Don't check same namespace access more than once", func(t *testing.T) {
|
||||
ruleAuthz := fakes.FakeRuleService{}
|
||||
ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence accesscontrol.Namespaced) (bool, error) {
|
||||
ruleAuthz.HasAccessInFolderFunc = func(ctx context.Context, user identity.Requester, silence models.Namespaced) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -159,7 +158,7 @@ func TestWithRuleMetadata(t *testing.T) {
|
||||
require.NoError(t, svc.WithRuleMetadata(context.Background(), user, silencesWithMetadata...))
|
||||
assert.Lenf(t, ruleAuthz.Calls, 1, "HasAccessInFolder should be called only once per namespace")
|
||||
assert.Equal(t, "HasAccessInFolder", ruleAuthz.Calls[0].MethodName)
|
||||
assert.Equal(t, "folder1", ruleAuthz.Calls[0].Arguments[2].(accesscontrol.Namespaced).GetNamespaceUID())
|
||||
assert.Equal(t, "folder1", ruleAuthz.Calls[0].Arguments[2].(models.Namespaced).GetNamespaceUID())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
)
|
||||
@@ -13,7 +12,7 @@ import (
|
||||
type RuleAccessControlService interface {
|
||||
HasAccess(ctx context.Context, user identity.Requester, evaluator ac.Evaluator) (bool, error)
|
||||
AuthorizeAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) error
|
||||
AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error
|
||||
AuthorizeAccessInFolder(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error
|
||||
AuthorizeRuleChanges(ctx context.Context, user identity.Requester, change *store.GroupDelta) error
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
accesscontrol2 "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
@@ -200,7 +199,7 @@ func TestAuthorizeAccessToRule(t *testing.T) {
|
||||
rs.HasAccessFunc = func(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
rs.AuthorizeAccessInFolderFunc = func(ctx context.Context, requester identity.Requester, namespaced accesscontrol2.Namespaced) error {
|
||||
rs.AuthorizeAccessInFolderFunc = func(ctx context.Context, requester identity.Requester, namespaced models.Namespaced) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -231,7 +230,7 @@ func TestAuthorizeAccessToRule(t *testing.T) {
|
||||
return false, nil
|
||||
}
|
||||
expected = errors.New("test2")
|
||||
rs.AuthorizeAccessInFolderFunc = func(ctx context.Context, requester identity.Requester, rule accesscontrol2.Namespaced) error {
|
||||
rs.AuthorizeAccessInFolderFunc = func(ctx context.Context, requester identity.Requester, rule models.Namespaced) error {
|
||||
return expected
|
||||
}
|
||||
|
||||
|
||||
@@ -1020,7 +1020,7 @@ func TestGetAlertRule(t *testing.T) {
|
||||
service, _, _, ac := initServiceWithData(t)
|
||||
|
||||
expected := errors.New("test")
|
||||
ac.AuthorizeAccessInFolderFunc = func(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error {
|
||||
ac.AuthorizeAccessInFolderFunc = func(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error {
|
||||
assert.Equal(t, u, user)
|
||||
assert.EqualValues(t, rule, namespaced)
|
||||
return expected
|
||||
@@ -1034,7 +1034,7 @@ func TestGetAlertRule(t *testing.T) {
|
||||
assert.Equal(t, "AuthorizeRuleRead", ac.Calls[0].Method)
|
||||
|
||||
ac.Calls = nil
|
||||
ac.AuthorizeAccessInFolderFunc = func(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error {
|
||||
ac.AuthorizeAccessInFolderFunc = func(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
@@ -161,7 +160,7 @@ type fakeRuleAccessControlService struct {
|
||||
mu sync.Mutex
|
||||
Calls []call
|
||||
AuthorizeAccessToRuleGroupFunc func(ctx context.Context, user identity.Requester, rules models.RulesGroup) error
|
||||
AuthorizeAccessInFolderFunc func(ctx context.Context, user identity.Requester, namespaced accesscontrol.Namespaced) error
|
||||
AuthorizeAccessInFolderFunc func(ctx context.Context, user identity.Requester, namespaced models.Namespaced) error
|
||||
AuthorizeRuleChangesFunc func(ctx context.Context, user identity.Requester, change *store.GroupDelta) error
|
||||
CanReadAllRulesFunc func(ctx context.Context, user identity.Requester) (bool, error)
|
||||
CanWriteAllRulesFunc func(ctx context.Context, user identity.Requester) (bool, error)
|
||||
|
||||
@@ -109,7 +109,7 @@ type broadcaster[T any] struct {
|
||||
|
||||
// subscription management
|
||||
|
||||
cache Cache[T]
|
||||
cache channelCache[T]
|
||||
subscribe chan chan T
|
||||
unsubscribe chan (<-chan T)
|
||||
subs map[<-chan T]chan T
|
||||
@@ -166,7 +166,7 @@ func (b *broadcaster[T]) init(ctx context.Context, connect ConnectFunc[T]) error
|
||||
|
||||
// initialize our internal state
|
||||
b.shouldTerminate = ctx.Done()
|
||||
b.cache = NewCache[T](ctx, 100)
|
||||
b.cache = newChannelCache[T](ctx, 100)
|
||||
b.subscribe = make(chan chan T, 100)
|
||||
b.unsubscribe = make(chan (<-chan T), 100)
|
||||
b.subs = make(map[<-chan T]chan T)
|
||||
@@ -239,9 +239,9 @@ func (b *broadcaster[T]) stream(input <-chan T) {
|
||||
}
|
||||
}
|
||||
|
||||
const DefaultCacheSize = 100
|
||||
const defaultCacheSize = 100
|
||||
|
||||
type Cache[T any] interface {
|
||||
type channelCache[T any] interface {
|
||||
Len() int
|
||||
Add(item T)
|
||||
Get(i int) T
|
||||
@@ -260,12 +260,12 @@ type cache[T any] struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewCache[T any](ctx context.Context, size int) Cache[T] {
|
||||
func newChannelCache[T any](ctx context.Context, size int) channelCache[T] {
|
||||
c := &cache[T]{}
|
||||
|
||||
c.ctx = ctx
|
||||
if size <= 0 {
|
||||
size = DefaultCacheSize
|
||||
size = defaultCacheSize
|
||||
}
|
||||
c.size = size
|
||||
c.cache = make([]T, c.size)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
c := NewCache[int](context.Background(), 10)
|
||||
c := newChannelCache[int](context.Background(), 10)
|
||||
|
||||
e := []int{}
|
||||
err := c.Range(func(i int) error {
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
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/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
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-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
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/grafana/pkg/apimachinery v0.0.0-20240613114114-5e2f08de316d h1:/UE5JdF+0hxll7EuuO7zRzAxXrvAxQo5M9eqOepc2mQ=
|
||||
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/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=
|
||||
|
||||
@@ -32,7 +32,6 @@ var mtx sync.Mutex
|
||||
// Legacy UID pattern
|
||||
var validUIDCharPattern = `a-zA-Z0-9\-\_`
|
||||
var validUIDPattern = regexp.MustCompile(`^[` + validUIDCharPattern + `]*$`).MatchString
|
||||
var validUIDReplacer = regexp.MustCompile(`[^` + validUIDCharPattern + `]`).ReplaceAllString
|
||||
|
||||
// IsValidShortUID checks if short unique identifier contains valid characters
|
||||
// NOTE: future Grafana UIDs will need conform to https://github.com/kubernetes/apimachinery/blob/master/pkg/util/validation/validation.go#L43
|
||||
@@ -93,13 +92,3 @@ func ValidateUID(uid string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AutofixUID(uid string) string {
|
||||
if IsShortUIDTooLong(uid) {
|
||||
return uid[:MaxUIDLength]
|
||||
}
|
||||
if !IsValidShortUID(uid) {
|
||||
uid = validUIDReplacer(uid, "-")
|
||||
}
|
||||
return uid
|
||||
}
|
||||
|
||||
@@ -143,32 +143,3 @@ func TestValidateUID(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutofixUID(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
uid string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "return input when input is valid",
|
||||
uid: "f8cc010c-ee72-4681-89d2-d46e1bd47d33",
|
||||
expected: "f8cc010c-ee72-4681-89d2-d46e1bd47d33",
|
||||
},
|
||||
{
|
||||
name: "generate new uid when input is too long",
|
||||
uid: strings.Repeat("1", MaxUIDLength+1),
|
||||
expected: strings.Repeat("1", MaxUIDLength),
|
||||
},
|
||||
{
|
||||
name: "generate new uid when input has invalid characters",
|
||||
uid: "f8cc010c.ee72.4681;89d2+d46e1bd47d33",
|
||||
expected: "f8cc010c-ee72-4681-89d2-d46e1bd47d33",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.expected, AutofixUID(tt.uid))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user