Preferences: Support Create+Update+Delete over apiserver (#111715)
This commit is contained in:
@@ -13,11 +13,13 @@ import (
|
||||
requestK8s "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
preferences "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
utilsOrig "github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/preferences/utils"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -26,13 +28,14 @@ var (
|
||||
_ rest.Getter = (*preferenceStorage)(nil)
|
||||
_ rest.Lister = (*preferenceStorage)(nil)
|
||||
_ rest.Storage = (*preferenceStorage)(nil)
|
||||
// _ rest.Creater = (*preferenceStorage)(nil)
|
||||
// _ rest.Updater = (*preferenceStorage)(nil)
|
||||
// _ rest.GracefulDeleter = (*preferenceStorage)(nil)
|
||||
_ rest.Creater = (*preferenceStorage)(nil)
|
||||
_ rest.Updater = (*preferenceStorage)(nil)
|
||||
_ rest.GracefulDeleter = (*preferenceStorage)(nil)
|
||||
)
|
||||
|
||||
func NewPreferencesStorage(namespacer request.NamespaceMapper, sql *LegacySQL) *preferenceStorage {
|
||||
func NewPreferencesStorage(pref pref.Service, namespacer request.NamespaceMapper, sql *LegacySQL) *preferenceStorage {
|
||||
return &preferenceStorage{
|
||||
prefs: pref,
|
||||
namespacer: namespacer,
|
||||
sql: sql,
|
||||
tableConverter: preferences.PreferencesResourceInfo.TableConverter(),
|
||||
@@ -43,6 +46,7 @@ type preferenceStorage struct {
|
||||
namespacer request.NamespaceMapper
|
||||
tableConverter rest.TableConvertor
|
||||
sql *LegacySQL
|
||||
prefs pref.Service
|
||||
}
|
||||
|
||||
func (s *preferenceStorage) New() runtime.Object {
|
||||
@@ -73,7 +77,7 @@ func (s *preferenceStorage) List(ctx context.Context, options *internalversion.L
|
||||
return nil, err
|
||||
}
|
||||
ns := requestK8s.NamespaceValue(ctx)
|
||||
if user.GetIsGrafanaAdmin() {
|
||||
if user.GetIdentityType() == authlib.TypeAccessPolicy {
|
||||
user = nil // nill user can see everything
|
||||
}
|
||||
return s.sql.ListPreferences(ctx, ns, user, true)
|
||||
@@ -116,6 +120,151 @@ func (s *preferenceStorage) Get(ctx context.Context, name string, options *metav
|
||||
return nil, preferences.PreferencesResourceInfo.NewNotFound(name)
|
||||
}
|
||||
|
||||
func (s *preferenceStorage) save(ctx context.Context, obj runtime.Object) (runtime.Object, error) {
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, ok := obj.(*preferences.Preferences)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected preferences")
|
||||
}
|
||||
|
||||
owner, ok := utils.ParseOwnerFromName(p.Name)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid name")
|
||||
}
|
||||
|
||||
cmd := &pref.SavePreferenceCommand{
|
||||
OrgID: user.GetOrgID(),
|
||||
HomeDashboardUID: p.Spec.HomeDashboardUID,
|
||||
}
|
||||
if p.Spec.Timezone != nil {
|
||||
cmd.Timezone = *p.Spec.Timezone
|
||||
}
|
||||
if p.Spec.WeekStart != nil {
|
||||
cmd.WeekStart = *p.Spec.WeekStart
|
||||
}
|
||||
if p.Spec.Theme != nil {
|
||||
cmd.Theme = *p.Spec.Theme
|
||||
}
|
||||
if p.Spec.Language != nil {
|
||||
cmd.Language = *p.Spec.Language
|
||||
}
|
||||
if p.Spec.RegionalFormat != nil {
|
||||
cmd.RegionalFormat = *p.Spec.RegionalFormat
|
||||
}
|
||||
if p.Spec.QueryHistory != nil {
|
||||
cmd.QueryHistory = &pref.QueryHistoryPreference{
|
||||
HomeTab: *p.Spec.QueryHistory.HomeTab,
|
||||
}
|
||||
}
|
||||
if p.Spec.Navbar != nil {
|
||||
cmd.Navbar = &pref.NavbarPreference{
|
||||
BookmarkUrls: p.Spec.Navbar.BookmarkUrls,
|
||||
}
|
||||
}
|
||||
if p.Spec.CookiePreferences != nil {
|
||||
cmd.CookiePreferences = []pref.CookieType{}
|
||||
if p.Spec.CookiePreferences.Analytics != nil {
|
||||
cmd.CookiePreferences = append(cmd.CookiePreferences, "analytics")
|
||||
}
|
||||
if p.Spec.CookiePreferences.Functional != nil {
|
||||
cmd.CookiePreferences = append(cmd.CookiePreferences, "functional")
|
||||
}
|
||||
if p.Spec.CookiePreferences.Performance != nil {
|
||||
cmd.CookiePreferences = append(cmd.CookiePreferences, "performance")
|
||||
}
|
||||
}
|
||||
|
||||
switch owner.Owner {
|
||||
case utils.NamespaceResourceOwner:
|
||||
// the org ID is already set
|
||||
|
||||
case utils.UserResourceOwner:
|
||||
if user.GetIdentifier() != owner.Identifier {
|
||||
return nil, fmt.Errorf("only the user can save preferences")
|
||||
}
|
||||
cmd.UserID, err = user.GetInternalID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case utils.TeamResourceOwner:
|
||||
cmd.TeamID, err = s.sql.getLegacyTeamID(ctx, user.GetOrgID(), owner.Identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported name")
|
||||
}
|
||||
|
||||
if err = s.prefs.Save(ctx, cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.Get(ctx, owner.AsName(), &metav1.GetOptions{})
|
||||
}
|
||||
|
||||
// Create implements rest.Creater.
|
||||
func (s *preferenceStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||
return s.save(ctx, obj)
|
||||
}
|
||||
|
||||
// Update implements rest.Updater.
|
||||
func (s *preferenceStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
|
||||
old, err := s.Get(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
obj, err := objInfo.UpdatedObject(ctx, old)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
obj, err = s.save(ctx, obj)
|
||||
return obj, false, err
|
||||
}
|
||||
|
||||
// Delete implements rest.GracefulDeleter.
|
||||
func (s *preferenceStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
owner, ok := utils.ParseOwnerFromName(name)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("invalid name")
|
||||
}
|
||||
|
||||
cmd := &pref.DeleteCommand{}
|
||||
|
||||
switch owner.Owner {
|
||||
case utils.TeamResourceOwner:
|
||||
cmd.TeamID, err = user.GetInternalID()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
case utils.UserResourceOwner:
|
||||
cmd.UserID, err = user.GetInternalID()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
case utils.NamespaceResourceOwner:
|
||||
cmd.OrgID = user.GetOrgID()
|
||||
|
||||
default:
|
||||
return nil, false, fmt.Errorf("unsupported owner")
|
||||
}
|
||||
|
||||
err = s.prefs.Delete(ctx, cmd)
|
||||
return nil, (err == nil), err
|
||||
}
|
||||
|
||||
func asPreferencesResource(ns string, p *preferenceModel) preferences.Preferences {
|
||||
owner := utils.OwnerReference{}
|
||||
if p.TeamUID.Valid {
|
||||
|
||||
@@ -49,7 +49,7 @@ func NewLegacySQL(db legacysql.LegacyDatabaseProvider) *LegacySQL {
|
||||
}
|
||||
|
||||
// NOTE: this does not support paging -- lets check if that will be a problem in cloud
|
||||
func (s *LegacySQL) GetStars(ctx context.Context, orgId int64, user string) ([]dashboardStars, int64, error) {
|
||||
func (s *LegacySQL) getDashboardStars(ctx context.Context, orgId int64, user string) ([]dashboardStars, int64, error) {
|
||||
var max sql.NullString
|
||||
sql, err := s.db(ctx)
|
||||
if err != nil {
|
||||
@@ -120,7 +120,10 @@ func (s *LegacySQL) GetStars(ctx context.Context, orgId int64, user string) ([]d
|
||||
return nil, 0, fmt.Errorf("unable to get RV %w", err)
|
||||
}
|
||||
if max.Valid && max.String != "" {
|
||||
fmt.Printf("max RV: %s\n", max.String)
|
||||
t, _ := time.Parse(time.RFC3339, max.String)
|
||||
if !t.IsZero() {
|
||||
updated = t
|
||||
}
|
||||
} else {
|
||||
updated = s.startup
|
||||
}
|
||||
@@ -206,7 +209,10 @@ func (s *LegacySQL) listPreferences(ctx context.Context,
|
||||
return nil, 0, fmt.Errorf("unable to get RV %w", err)
|
||||
}
|
||||
if max.Valid && max.String != "" {
|
||||
fmt.Printf("max RV: %s\n", max.String)
|
||||
t, _ := time.Parse(time.RFC3339, max.String)
|
||||
if !t.IsZero() {
|
||||
rv.Time = t
|
||||
}
|
||||
} else {
|
||||
rv.Time = s.startup
|
||||
}
|
||||
@@ -229,7 +235,7 @@ func (s *LegacySQL) ListPreferences(ctx context.Context, ns string, user identit
|
||||
found, rv, err := s.listPreferences(ctx, ns, info.OrgID,
|
||||
func(req *preferencesQuery) (bool, error) {
|
||||
if user != nil {
|
||||
req.UserUID = user.GetRawIdentifier()
|
||||
req.UserUID = user.GetIdentifier()
|
||||
teams, err = s.GetTeams(ctx, &identity.StaticRequester{
|
||||
OrgID: info.OrgID,
|
||||
UserUID: req.UserUID,
|
||||
@@ -243,7 +249,7 @@ func (s *LegacySQL) ListPreferences(ctx context.Context, ns string, user identit
|
||||
return true
|
||||
}
|
||||
if p.UserUID.String != "" {
|
||||
return user.GetRawIdentifier() == p.UserUID.String
|
||||
return user.GetIdentifier() == p.UserUID.String
|
||||
}
|
||||
if p.TeamUID.String != "" {
|
||||
return slices.Contains(teams, p.TeamUID.String)
|
||||
@@ -293,3 +299,15 @@ func (s *LegacySQL) GetTeams(ctx context.Context, id authlib.AuthInfo, admin boo
|
||||
err = sess.Select(ctx, &teams, q, req.GetArgs()...)
|
||||
return teams, err
|
||||
}
|
||||
|
||||
func (s *LegacySQL) getLegacyTeamID(ctx context.Context, orgId int64, team string) (int64, error) {
|
||||
sql, err := s.db(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var id int64
|
||||
sess := sql.DB.GetSqlxSession()
|
||||
err = sess.Select(ctx, &id, "SELECT id FROM team WHERE org_id=? AND uid=?", orgId, team)
|
||||
return id, err
|
||||
}
|
||||
|
||||
@@ -97,13 +97,13 @@ func (s *DashboardStarsStorage) List(ctx context.Context, options *internalversi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := userInfo.GetUID()
|
||||
if userInfo.GetIsGrafanaAdmin() || userInfo.GetIdentityType() == authlib.TypeAccessPolicy {
|
||||
user := userInfo.GetIdentifier()
|
||||
if userInfo.GetIdentityType() == authlib.TypeAccessPolicy {
|
||||
user = "" // can see everything
|
||||
}
|
||||
|
||||
list := &preferences.StarsList{}
|
||||
found, rv, err := s.sql.GetStars(ctx, ns.OrgID, user)
|
||||
found, rv, err := s.sql.getDashboardStars(ctx, ns.OrgID, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -137,7 +137,7 @@ func (s *DashboardStarsStorage) Get(ctx context.Context, name string, options *m
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found, _, err := s.sql.GetStars(ctx, ns.OrgID, owner.Identifier)
|
||||
found, _, err := s.sql.getDashboardStars(ctx, ns.OrgID, owner.Identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -187,7 +187,7 @@ func (s *DashboardStarsStorage) write(ctx context.Context, obj *preferences.Star
|
||||
}}, err
|
||||
}
|
||||
|
||||
current, _, err := s.sql.GetStars(ctx, ns.OrgID, owner.Identifier)
|
||||
current, _, err := s.sql.getDashboardStars(ctx, ns.OrgID, owner.Identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func RegisterAPIService(
|
||||
|
||||
namespacer := request.GetNamespaceMapper(cfg)
|
||||
if prefs != nil {
|
||||
builder.legacyPrefs = legacy.NewPreferencesStorage(namespacer, sql)
|
||||
builder.legacyPrefs = legacy.NewPreferencesStorage(prefs, namespacer, sql)
|
||||
}
|
||||
if stars != nil {
|
||||
builder.legacyStars = legacy.NewDashboardStarsStorage(stars, users, namespacer, sql)
|
||||
|
||||
Reference in New Issue
Block a user