Preferences: Add read-only APIServer for preferences and dashboard stars (#106109)
This commit is contained in:
@@ -102,6 +102,7 @@ COPY apps/investigations apps/investigations
|
||||
COPY apps/advisor apps/advisor
|
||||
COPY apps/dashboard apps/dashboard
|
||||
COPY apps/folder apps/folder
|
||||
COPY apps/preferences apps/preferences
|
||||
COPY apps/iam apps/iam
|
||||
COPY apps apps
|
||||
COPY kindsv2 kindsv2
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-aler
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.7.0 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect
|
||||
|
||||
+2
-2
@@ -72,8 +72,8 @@ cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2zn
|
||||
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
|
||||
cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0=
|
||||
cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ require (
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
cloud.google.com/go/storage v1.55.0 // indirect
|
||||
cuelang.org/go v0.11.1 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect
|
||||
|
||||
+2
-2
@@ -80,8 +80,8 @@ cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSB
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg=
|
||||
cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0=
|
||||
cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
|
||||
@@ -4,7 +4,9 @@ go 1.24.6
|
||||
|
||||
require (
|
||||
github.com/grafana/grafana-app-sdk v0.40.3
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2
|
||||
k8s.io/apimachinery v0.33.3
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -34,11 +36,13 @@ require (
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.23.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
@@ -54,7 +58,6 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/client-go v0.33.3 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
|
||||
@@ -36,6 +36,8 @@ github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhck
|
||||
github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.40.3 h1:2VXsXXEQiqAavRP8wusRDB6rDqf5lufP7A6NfjELqPE=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.40.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
||||
@@ -10,7 +10,8 @@ manifest: {
|
||||
go: {enabled: true}
|
||||
}
|
||||
kinds: [
|
||||
preferencesv1alpha1,
|
||||
preferencesV1alpha1,
|
||||
starsV1alpha1,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package preferences
|
||||
|
||||
preferencesv1alpha1: {
|
||||
preferencesV1alpha1: {
|
||||
kind: "Preferences"
|
||||
pluralName: "Preferences"
|
||||
scope: "Namespaced"
|
||||
|
||||
validation: {
|
||||
operations: [
|
||||
"CREATE",
|
||||
"UPDATE",
|
||||
]
|
||||
}
|
||||
schema: {
|
||||
spec: {
|
||||
// UID for the home dashboard
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package preferences
|
||||
|
||||
starsV1alpha1: {
|
||||
kind: "Stars"
|
||||
pluralName: "Stars"
|
||||
scope: "Namespaced"
|
||||
|
||||
validation: {
|
||||
operations: [
|
||||
"CREATE",
|
||||
"UPDATE",
|
||||
]
|
||||
}
|
||||
schema: {
|
||||
#Resource: {
|
||||
group: string
|
||||
kind: string
|
||||
|
||||
// The set of resources
|
||||
// +listType=set
|
||||
names: [...string]
|
||||
}
|
||||
spec: {
|
||||
resource: [...#Resource]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
time "time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
)
|
||||
|
||||
var PreferencesResourceInfo = utils.NewResourceInfo(APIGroup, APIVersion,
|
||||
"preferences", "preferences", "Preferences",
|
||||
func() runtime.Object { return &Preferences{} },
|
||||
func() runtime.Object { return &PreferencesList{} },
|
||||
utils.TableColumns{
|
||||
Definition: []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Created At", Type: "date"},
|
||||
},
|
||||
Reader: func(obj any) ([]any, error) {
|
||||
p, ok := obj.(*Preferences)
|
||||
if ok && p != nil {
|
||||
return []any{
|
||||
p.Name,
|
||||
p.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("expected preferences")
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
var StarsResourceInfo = utils.NewResourceInfo(APIGroup, APIVersion,
|
||||
"stars", "stars", "Stars",
|
||||
func() runtime.Object { return &Stars{} },
|
||||
func() runtime.Object { return &StarsList{} },
|
||||
utils.TableColumns{
|
||||
Definition: []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Created At", Type: "date"},
|
||||
},
|
||||
Reader: func(obj any) ([]any, error) {
|
||||
p, ok := obj.(*Stars)
|
||||
if ok && p != nil {
|
||||
return []any{
|
||||
p.Name,
|
||||
p.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("expected stars")
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
var (
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
schemeGroupVersion = GroupVersion
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
|
||||
}
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(schemeGroupVersion,
|
||||
&Preferences{},
|
||||
&PreferencesList{},
|
||||
&Stars{},
|
||||
&StarsList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, schemeGroupVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
return nil // return RegisterDefaults(scheme)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
)
|
||||
|
||||
// StarsJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
||||
type StarsJSONCodec struct{}
|
||||
|
||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
||||
func (*StarsJSONCodec) Read(reader io.Reader, into resource.Object) error {
|
||||
return json.NewDecoder(reader).Decode(into)
|
||||
}
|
||||
|
||||
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
|
||||
func (*StarsJSONCodec) Write(writer io.Writer, from resource.Object) error {
|
||||
return json.NewEncoder(writer).Encode(from)
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Codec = &StarsJSONCodec{}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
time "time"
|
||||
)
|
||||
|
||||
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
||||
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
||||
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
||||
type StarsMetadata struct {
|
||||
UpdateTimestamp time.Time `json:"updateTimestamp"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Uid string `json:"uid"`
|
||||
CreationTimestamp time.Time `json:"creationTimestamp"`
|
||||
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
|
||||
Finalizers []string `json:"finalizers"`
|
||||
ResourceVersion string `json:"resourceVersion"`
|
||||
Generation int64 `json:"generation"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
||||
|
||||
// NewStarsMetadata creates a new StarsMetadata object.
|
||||
func NewStarsMetadata() *StarsMetadata {
|
||||
return &StarsMetadata{
|
||||
Finalizers: []string{},
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type Stars struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||
|
||||
// Spec is the spec of the Stars
|
||||
Spec StarsSpec `json:"spec" yaml:"spec"`
|
||||
|
||||
Status StarsStatus `json:"status" yaml:"status"`
|
||||
}
|
||||
|
||||
func (o *Stars) GetSpec() any {
|
||||
return o.Spec
|
||||
}
|
||||
|
||||
func (o *Stars) SetSpec(spec any) error {
|
||||
cast, ok := spec.(StarsSpec)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
||||
}
|
||||
o.Spec = cast
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Stars) GetSubresources() map[string]any {
|
||||
return map[string]any{
|
||||
"status": o.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Stars) GetSubresource(name string) (any, bool) {
|
||||
switch name {
|
||||
case "status":
|
||||
return o.Status, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Stars) SetSubresource(name string, value any) error {
|
||||
switch name {
|
||||
case "status":
|
||||
cast, ok := value.(StarsStatus)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set status type %#v, not of type StarsStatus", value)
|
||||
}
|
||||
o.Status = cast
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Stars) GetStaticMetadata() resource.StaticMetadata {
|
||||
gvk := o.GroupVersionKind()
|
||||
return resource.StaticMetadata{
|
||||
Name: o.ObjectMeta.Name,
|
||||
Namespace: o.ObjectMeta.Namespace,
|
||||
Group: gvk.Group,
|
||||
Version: gvk.Version,
|
||||
Kind: gvk.Kind,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Stars) SetStaticMetadata(metadata resource.StaticMetadata) {
|
||||
o.Name = metadata.Name
|
||||
o.Namespace = metadata.Namespace
|
||||
o.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: metadata.Group,
|
||||
Version: metadata.Version,
|
||||
Kind: metadata.Kind,
|
||||
})
|
||||
}
|
||||
|
||||
func (o *Stars) GetCommonMetadata() resource.CommonMetadata {
|
||||
dt := o.DeletionTimestamp
|
||||
var deletionTimestamp *time.Time
|
||||
if dt != nil {
|
||||
deletionTimestamp = &dt.Time
|
||||
}
|
||||
// Legacy ExtraFields support
|
||||
extraFields := make(map[string]any)
|
||||
if o.Annotations != nil {
|
||||
extraFields["annotations"] = o.Annotations
|
||||
}
|
||||
if o.ManagedFields != nil {
|
||||
extraFields["managedFields"] = o.ManagedFields
|
||||
}
|
||||
if o.OwnerReferences != nil {
|
||||
extraFields["ownerReferences"] = o.OwnerReferences
|
||||
}
|
||||
return resource.CommonMetadata{
|
||||
UID: string(o.UID),
|
||||
ResourceVersion: o.ResourceVersion,
|
||||
Generation: o.Generation,
|
||||
Labels: o.Labels,
|
||||
CreationTimestamp: o.CreationTimestamp.Time,
|
||||
DeletionTimestamp: deletionTimestamp,
|
||||
Finalizers: o.Finalizers,
|
||||
UpdateTimestamp: o.GetUpdateTimestamp(),
|
||||
CreatedBy: o.GetCreatedBy(),
|
||||
UpdatedBy: o.GetUpdatedBy(),
|
||||
ExtraFields: extraFields,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Stars) SetCommonMetadata(metadata resource.CommonMetadata) {
|
||||
o.UID = types.UID(metadata.UID)
|
||||
o.ResourceVersion = metadata.ResourceVersion
|
||||
o.Generation = metadata.Generation
|
||||
o.Labels = metadata.Labels
|
||||
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
|
||||
if metadata.DeletionTimestamp != nil {
|
||||
dt := metav1.NewTime(*metadata.DeletionTimestamp)
|
||||
o.DeletionTimestamp = &dt
|
||||
} else {
|
||||
o.DeletionTimestamp = nil
|
||||
}
|
||||
o.Finalizers = metadata.Finalizers
|
||||
if o.Annotations == nil {
|
||||
o.Annotations = make(map[string]string)
|
||||
}
|
||||
if !metadata.UpdateTimestamp.IsZero() {
|
||||
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
|
||||
}
|
||||
if metadata.CreatedBy != "" {
|
||||
o.SetCreatedBy(metadata.CreatedBy)
|
||||
}
|
||||
if metadata.UpdatedBy != "" {
|
||||
o.SetUpdatedBy(metadata.UpdatedBy)
|
||||
}
|
||||
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
|
||||
if metadata.ExtraFields != nil {
|
||||
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
|
||||
if cast, ok := annotations.(map[string]string); ok {
|
||||
o.Annotations = cast
|
||||
}
|
||||
}
|
||||
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
|
||||
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
|
||||
o.ManagedFields = cast
|
||||
}
|
||||
}
|
||||
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
|
||||
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
|
||||
o.OwnerReferences = cast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Stars) GetCreatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
||||
}
|
||||
|
||||
func (o *Stars) SetCreatedBy(createdBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
||||
}
|
||||
|
||||
func (o *Stars) GetUpdateTimestamp() time.Time {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
|
||||
return parsed
|
||||
}
|
||||
|
||||
func (o *Stars) SetUpdateTimestamp(updateTimestamp time.Time) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func (o *Stars) GetUpdatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
||||
}
|
||||
|
||||
func (o *Stars) SetUpdatedBy(updatedBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
||||
}
|
||||
|
||||
func (o *Stars) Copy() resource.Object {
|
||||
return resource.CopyObject(o)
|
||||
}
|
||||
|
||||
func (o *Stars) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *Stars) DeepCopy() *Stars {
|
||||
cpy := &Stars{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *Stars) DeepCopyInto(dst *Stars) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
||||
o.Spec.DeepCopyInto(&dst.Spec)
|
||||
o.Status.DeepCopyInto(&dst.Status)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.Object = &Stars{}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StarsList struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
||||
Items []Stars `json:"items" yaml:"items"`
|
||||
}
|
||||
|
||||
func (o *StarsList) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *StarsList) Copy() resource.ListObject {
|
||||
cpy := &StarsList{
|
||||
TypeMeta: o.TypeMeta,
|
||||
Items: make([]Stars, len(o.Items)),
|
||||
}
|
||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
||||
for i := 0; i < len(o.Items); i++ {
|
||||
if item, ok := o.Items[i].Copy().(*Stars); ok {
|
||||
cpy.Items[i] = *item
|
||||
}
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *StarsList) GetItems() []resource.Object {
|
||||
items := make([]resource.Object, len(o.Items))
|
||||
for i := 0; i < len(o.Items); i++ {
|
||||
items[i] = &o.Items[i]
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func (o *StarsList) SetItems(items []resource.Object) {
|
||||
o.Items = make([]Stars, len(items))
|
||||
for i := 0; i < len(items); i++ {
|
||||
o.Items[i] = *items[i].(*Stars)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *StarsList) DeepCopy() *StarsList {
|
||||
cpy := &StarsList{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *StarsList) DeepCopyInto(dst *StarsList) {
|
||||
resource.CopyObjectInto(dst, o)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.ListObject = &StarsList{}
|
||||
|
||||
// Copy methods for all subresource types
|
||||
|
||||
// DeepCopy creates a full deep copy of Spec
|
||||
func (s *StarsSpec) DeepCopy() *StarsSpec {
|
||||
cpy := &StarsSpec{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies Spec into another Spec object
|
||||
func (s *StarsSpec) DeepCopyInto(dst *StarsSpec) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
|
||||
// DeepCopy creates a full deep copy of StarsStatus
|
||||
func (s *StarsStatus) DeepCopy() *StarsStatus {
|
||||
cpy := &StarsStatus{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies StarsStatus into another StarsStatus object
|
||||
func (s *StarsStatus) DeepCopyInto(dst *StarsStatus) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
)
|
||||
|
||||
// schema is unexported to prevent accidental overwrites
|
||||
var (
|
||||
schemaStars = resource.NewSimpleSchema("preferences.grafana.app", "v1alpha1", &Stars{}, &StarsList{}, resource.WithKind("Stars"),
|
||||
resource.WithPlural("stars"), resource.WithScope(resource.NamespacedScope))
|
||||
kindStars = resource.Kind{
|
||||
Schema: schemaStars,
|
||||
Codecs: map[resource.KindEncoding]resource.Codec{
|
||||
resource.KindEncodingJSON: &StarsJSONCodec{},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Kind returns a resource.Kind for this Schema with a JSON codec
|
||||
func StarsKind() resource.Kind {
|
||||
return kindStars
|
||||
}
|
||||
|
||||
// Schema returns a resource.SimpleSchema representation of Stars
|
||||
func StarsSchema() *resource.SimpleSchema {
|
||||
return schemaStars
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Schema = kindStars
|
||||
@@ -0,0 +1,31 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StarsResource struct {
|
||||
Group string `json:"group"`
|
||||
Kind string `json:"kind"`
|
||||
// The set of resources
|
||||
// +listType=set
|
||||
Names []string `json:"names"`
|
||||
}
|
||||
|
||||
// NewStarsResource creates a new StarsResource object.
|
||||
func NewStarsResource() *StarsResource {
|
||||
return &StarsResource{
|
||||
Names: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StarsSpec struct {
|
||||
Resource []StarsResource `json:"resource"`
|
||||
}
|
||||
|
||||
// NewStarsSpec creates a new StarsSpec object.
|
||||
func NewStarsSpec() *StarsSpec {
|
||||
return &StarsSpec{
|
||||
Resource: []StarsResource{},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StarsstatusOperatorState struct {
|
||||
// lastEvaluation is the ResourceVersion last evaluated
|
||||
LastEvaluation string `json:"lastEvaluation"`
|
||||
// state describes the state of the lastEvaluation.
|
||||
// It is limited to three possible states for machine evaluation.
|
||||
State StarsStatusOperatorStateState `json:"state"`
|
||||
// descriptiveState is an optional more descriptive state field which has no requirements on format
|
||||
DescriptiveState *string `json:"descriptiveState,omitempty"`
|
||||
// details contains any extra information that is operator-specific
|
||||
Details map[string]interface{} `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// NewStarsstatusOperatorState creates a new StarsstatusOperatorState object.
|
||||
func NewStarsstatusOperatorState() *StarsstatusOperatorState {
|
||||
return &StarsstatusOperatorState{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StarsStatus struct {
|
||||
// operatorStates is a map of operator ID to operator state evaluations.
|
||||
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
||||
OperatorStates map[string]StarsstatusOperatorState `json:"operatorStates,omitempty"`
|
||||
// additionalFields is reserved for future use
|
||||
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
|
||||
}
|
||||
|
||||
// NewStarsStatus creates a new StarsStatus object.
|
||||
func NewStarsStatus() *StarsStatus {
|
||||
return &StarsStatus{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StarsStatusOperatorStateState string
|
||||
|
||||
const (
|
||||
StarsStatusOperatorStateStateSuccess StarsStatusOperatorStateState = "success"
|
||||
StarsStatusOperatorStateStateInProgress StarsStatusOperatorStateState = "in_progress"
|
||||
StarsStatusOperatorStateStateFailed StarsStatusOperatorStateState = "failed"
|
||||
)
|
||||
@@ -0,0 +1,639 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by openapi-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
common "k8s.io/kube-openapi/pkg/common"
|
||||
spec "k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
|
||||
return map[string]common.OpenAPIDefinition{
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.Preferences": schema_pkg_apis_preferences_v1alpha1_Preferences(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesCookiePreferences": schema_pkg_apis_preferences_v1alpha1_PreferencesCookiePreferences(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesList": schema_pkg_apis_preferences_v1alpha1_PreferencesList(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesNavbarPreference": schema_pkg_apis_preferences_v1alpha1_PreferencesNavbarPreference(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesQueryHistoryPreference": schema_pkg_apis_preferences_v1alpha1_PreferencesQueryHistoryPreference(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesSpec": schema_pkg_apis_preferences_v1alpha1_PreferencesSpec(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesStatus": schema_pkg_apis_preferences_v1alpha1_PreferencesStatus(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesstatusOperatorState": schema_pkg_apis_preferences_v1alpha1_PreferencesstatusOperatorState(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.Stars": schema_pkg_apis_preferences_v1alpha1_Stars(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsList": schema_pkg_apis_preferences_v1alpha1_StarsList(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsResource": schema_pkg_apis_preferences_v1alpha1_StarsResource(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsSpec": schema_pkg_apis_preferences_v1alpha1_StarsSpec(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsStatus": schema_pkg_apis_preferences_v1alpha1_StarsStatus(ref),
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsstatusOperatorState": schema_pkg_apis_preferences_v1alpha1_StarsstatusOperatorState(ref),
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_Preferences(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Spec is the spec of the Preferences",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesSpec"),
|
||||
},
|
||||
},
|
||||
"status": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesStatus"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"metadata", "spec", "status"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesSpec", "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_PreferencesCookiePreferences(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"analytics": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"performance": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"functional": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_PreferencesList(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.Preferences"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"metadata", "items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.Preferences", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_PreferencesNavbarPreference(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"bookmarkUrls": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"bookmarkUrls"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_PreferencesQueryHistoryPreference(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"homeTab": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "one of: '' | 'query' | 'starred';",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_PreferencesSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"homeDashboardUID": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "UID for the home dashboard",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"timezone": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The timezone selection",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"weekStart": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "day of the week (sunday, monday, etc)",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"theme": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "light, dark, empty is default",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"language": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Selected language (beta)",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"regionalFormat": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Selected locale (beta)",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"queryHistory": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Explore query history preferences",
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesQueryHistoryPreference"),
|
||||
},
|
||||
},
|
||||
"cookiePreferences": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Cookie preferences",
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesCookiePreferences"),
|
||||
},
|
||||
},
|
||||
"navbar": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Navigation preferences",
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesNavbarPreference"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesCookiePreferences", "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesNavbarPreference", "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesQueryHistoryPreference"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_PreferencesStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"operatorStates": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesstatusOperatorState"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"additionalFields": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "additionalFields is reserved for future use",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesstatusOperatorState"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_PreferencesstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"lastEvaluation": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "lastEvaluation is the ResourceVersion last evaluated",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"state": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"descriptiveState": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "descriptiveState is an optional more descriptive state field which has no requirements on format",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"details": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "details contains any extra information that is operator-specific",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"lastEvaluation", "state"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_Stars(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Spec is the spec of the Stars",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsSpec"),
|
||||
},
|
||||
},
|
||||
"status": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsStatus"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"metadata", "spec", "status"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsSpec", "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_StarsList(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.Stars"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"metadata", "items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.Stars", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_StarsResource(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"group": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"names": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "set",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The set of resources",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"group", "kind", "names"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_StarsSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"resource": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsResource"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"resource"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsResource"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_StarsStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"operatorStates": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsstatusOperatorState"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"additionalFields": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "additionalFields is reserved for future use",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsstatusOperatorState"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_preferences_v1alpha1_StarsstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"lastEvaluation": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "lastEvaluation is the ResourceVersion last evaluated",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"state": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"descriptiveState": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "descriptiveState is an optional more descriptive state field which has no requirements on format",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"details": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "details contains any extra information that is operator-specific",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"lastEvaluation", "state"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1,PreferencesNavbarPreference,BookmarkUrls
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1,StarsSpec,Resource
|
||||
API rule violation: streaming_list_type_json_tags,github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1,PreferencesList,ListMeta
|
||||
API rule violation: streaming_list_type_json_tags,github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1,StarsList,ListMeta
|
||||
@@ -20,6 +20,9 @@ var (
|
||||
rawSchemaPreferencesv1alpha1 = []byte(`{"spec":{"properties":{"cookiePreferences":{"description":"Cookie preferences","properties":{"analytics":{"type":"object"},"functional":{"type":"object"},"performance":{"type":"object"}},"type":"object"},"homeDashboardUID":{"description":"UID for the home dashboard","type":"string"},"language":{"description":"Selected language (beta)","type":"string"},"navbar":{"description":"Navigation preferences","properties":{"bookmarkUrls":{"items":{"type":"string"},"type":"array"}},"required":["bookmarkUrls"],"type":"object"},"queryHistory":{"description":"Explore query history preferences","properties":{"homeTab":{"description":"one of: '' | 'query' | 'starred';","type":"string"}},"type":"object"},"regionalFormat":{"description":"Selected locale (beta)","type":"string"},"theme":{"description":"light, dark, empty is default","type":"string"},"timezone":{"description":"The timezone selection\nTODO: this should use the timezone defined in common","type":"string"},"weekStart":{"description":"day of the week (sunday, monday, etc)","type":"string"}},"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaPreferencesv1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaPreferencesv1alpha1, &versionSchemaPreferencesv1alpha1)
|
||||
rawSchemaStarsv1alpha1 = []byte(`{"spec":{"properties":{"resource":{"items":{"properties":{"group":{"type":"string"},"kind":{"type":"string"},"names":{"description":"The set of resources\n+listType=set","items":{"type":"string"},"type":"array"}},"required":["group","kind","names"],"type":"object"},"type":"array"}},"required":["resource"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaStarsv1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaStarsv1alpha1, &versionSchemaStarsv1alpha1)
|
||||
)
|
||||
|
||||
var appManifestData = app.ManifestData{
|
||||
@@ -35,7 +38,31 @@ var appManifestData = app.ManifestData{
|
||||
Plural: "Preferences",
|
||||
Scope: "Namespaced",
|
||||
Conversion: false,
|
||||
Schema: &versionSchemaPreferencesv1alpha1,
|
||||
Admission: &app.AdmissionCapabilities{
|
||||
Validation: &app.ValidationCapability{
|
||||
Operations: []app.AdmissionOperation{
|
||||
app.AdmissionOperationCreate,
|
||||
app.AdmissionOperationUpdate,
|
||||
},
|
||||
},
|
||||
},
|
||||
Schema: &versionSchemaPreferencesv1alpha1,
|
||||
},
|
||||
|
||||
{
|
||||
Kind: "Stars",
|
||||
Plural: "Stars",
|
||||
Scope: "Namespaced",
|
||||
Conversion: false,
|
||||
Admission: &app.AdmissionCapabilities{
|
||||
Validation: &app.ValidationCapability{
|
||||
Operations: []app.AdmissionOperation{
|
||||
app.AdmissionOperationCreate,
|
||||
app.AdmissionOperationUpdate,
|
||||
},
|
||||
},
|
||||
},
|
||||
Schema: &versionSchemaStarsv1alpha1,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -52,6 +79,7 @@ func RemoteManifest() app.Manifest {
|
||||
|
||||
var kindVersionToGoType = map[string]resource.Kind{
|
||||
"Preferences/v1alpha1": v1alpha1.PreferencesKind(),
|
||||
"Stars/v1alpha1": v1alpha1.StarsKind(),
|
||||
}
|
||||
|
||||
// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists.
|
||||
|
||||
@@ -9,6 +9,7 @@ require (
|
||||
cloud.google.com/go/storage v1.55.0 // @grafana/grafana-backend-group
|
||||
connectrpc.com/connect v1.18.1 // @grafana/observability-traces-and-profiling
|
||||
cuelang.org/go v0.11.1 // @grafana/grafana-as-code
|
||||
dario.cat/mergo v1.0.2 // @grafana/grafana-app-platform-squad
|
||||
filippo.io/age v1.2.1 // @grafana/identity-access-team
|
||||
github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2 // @grafana/grafana-search-and-storage
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // @grafana/partner-datasources
|
||||
@@ -285,7 +286,6 @@ require (
|
||||
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
|
||||
|
||||
@@ -634,8 +634,8 @@ cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSB
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg=
|
||||
cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0=
|
||||
cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
|
||||
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
|
||||
|
||||
@@ -506,6 +506,7 @@ contrib.go.opencensus.io/exporter/zipkin v0.1.2 h1:YqE293IZrKtqPnpwDPH/lOqTWD/s3
|
||||
contrib.go.opencensus.io/exporter/zipkin v0.1.2/go.mod h1:mP5xM3rrgOjpn79MM8fZbj3gsxcuytSqtH0dxSWW1RE=
|
||||
contrib.go.opencensus.io/integrations/ocsql v0.1.7 h1:G3k7C0/W44zcqkpRSFyjU9f6HZkbwIrL//qqnlqWZ60=
|
||||
contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY=
|
||||
gioui.org v0.0.0-20210308172011-57750fc8a0a6 h1:K72hopUosKG3ntOPNG4OzzbuhxGuVf06fa2la1/H/Ho=
|
||||
git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38=
|
||||
|
||||
@@ -89,6 +89,7 @@ grafana::codegen:run pkg/aggregator
|
||||
grafana::codegen:run apps/dashboard/pkg
|
||||
grafana::codegen:run apps/provisioning/pkg
|
||||
grafana::codegen:run apps/folder/pkg
|
||||
grafana::codegen:run apps/preferences/pkg
|
||||
|
||||
if [ -d "pkg/extensions/apis" ]; then
|
||||
grafana::codegen:run pkg/extensions
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/folders"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/ofrep"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/preferences"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/query"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret"
|
||||
@@ -27,6 +28,7 @@ func ProvideRegistryServiceSink(
|
||||
_ *iam.IdentityAccessManagementAPIBuilder,
|
||||
_ *query.QueryAPIBuilder,
|
||||
_ *userstorage.UserStorageAPIBuilder,
|
||||
_ *preferences.APIBuilder,
|
||||
_ *provisioning.APIBuilder,
|
||||
_ *ofrep.APIBuilder,
|
||||
_ *secret.DependencyRegisterer,
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package preferences
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/preferences/utils"
|
||||
)
|
||||
|
||||
func (b *APIBuilder) GetAuthorizer() authorizer.Authorizer {
|
||||
return authorizer.AuthorizerFunc(
|
||||
func(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, "valid user is required", err
|
||||
}
|
||||
|
||||
if !attr.IsResourceRequest() || user.GetIsGrafanaAdmin() || attr.GetName() == "" {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
name, found := utils.ParseOwnerFromName(attr.GetName())
|
||||
if !found {
|
||||
return authorizer.DecisionDeny, "invalid name", nil
|
||||
}
|
||||
|
||||
if attr.GetResource() == "stars" && name.Owner != utils.UserResourceOwner {
|
||||
return authorizer.DecisionDeny, "stars only support users", nil
|
||||
}
|
||||
|
||||
switch name.Owner {
|
||||
case utils.NamespaceResourceOwner:
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
|
||||
case utils.UserResourceOwner:
|
||||
if user.GetUID() == name.Name {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
return authorizer.DecisionDeny, "you may only fetch your own preferences", nil
|
||||
|
||||
case utils.TeamResourceOwner:
|
||||
admin := !attr.IsReadOnly() // we need admin to for non read only commands
|
||||
teams, err := b.sql.GetTeams(ctx, user.GetOrgID(), user.GetUID(), admin)
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, "error fetching teams", err
|
||||
}
|
||||
if slices.Contains(teams, name.Name) {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
return authorizer.DecisionDeny, "not a team member", nil
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
return authorizer.DecisionDeny, "invalid name", nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package preferences
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"dario.cat/mergo"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
|
||||
preferences "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/preferences/legacy"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util/errhttp"
|
||||
)
|
||||
|
||||
type calculator struct {
|
||||
defaults preferences.PreferencesSpec
|
||||
sql *legacy.LegacySQL
|
||||
}
|
||||
|
||||
func newCalculator(cfg *setting.Cfg, sql *legacy.LegacySQL) *calculator {
|
||||
return &calculator{
|
||||
sql: sql,
|
||||
defaults: preferences.PreferencesSpec{
|
||||
Theme: &cfg.DefaultTheme,
|
||||
Timezone: &cfg.DateFormats.DefaultTimezone,
|
||||
WeekStart: &cfg.DateFormats.DefaultWeekStart,
|
||||
Language: &cfg.DefaultLanguage,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *calculator) GetAPIRoutes(defs map[string]common.OpenAPIDefinition) *builder.APIRoutes {
|
||||
schema := defs["github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.Preference"].Schema
|
||||
|
||||
return &builder.APIRoutes{
|
||||
Namespace: []builder.APIRouteHandler{
|
||||
{
|
||||
Path: "current", // calculate?
|
||||
Spec: &spec3.PathProps{
|
||||
Get: &spec3.Operation{
|
||||
OperationProps: spec3.OperationProps{
|
||||
OperationId: "currentPreferences",
|
||||
Tags: []string{"Preferences"},
|
||||
Description: "Get preferences for requester. This combines the user preferences with the team and global defaults",
|
||||
Parameters: []*spec3.Parameter{
|
||||
{
|
||||
ParameterProps: spec3.ParameterProps{
|
||||
Name: "namespace",
|
||||
In: "path",
|
||||
Required: true,
|
||||
Example: "default",
|
||||
Description: "workspace",
|
||||
Schema: spec.StringProperty(),
|
||||
},
|
||||
// Allow getting theme+language from accept
|
||||
},
|
||||
},
|
||||
Responses: &spec3.Responses{
|
||||
ResponsesProps: spec3.ResponsesProps{
|
||||
StatusCodeResponses: map[int]*spec3.Response{
|
||||
200: {
|
||||
ResponseProps: spec3.ResponseProps{
|
||||
Content: map[string]*spec3.MediaType{
|
||||
"application/json": {
|
||||
MediaTypeProps: spec3.MediaTypeProps{
|
||||
Schema: &schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Handler: s.Current,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *calculator) Current(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
errhttp.Write(ctx, err, w)
|
||||
return
|
||||
}
|
||||
ns := user.GetNamespace() // namespace not in context!
|
||||
list, err := s.sql.ListPreferences(ctx, ns, user, false)
|
||||
if err != nil {
|
||||
errhttp.Write(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := merge(s.defaults, list.Items)
|
||||
if err != nil {
|
||||
errhttp.Write(ctx, err, w)
|
||||
return
|
||||
}
|
||||
p.Namespace = ns
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(p)
|
||||
}
|
||||
|
||||
// items should be in ascending order of importance
|
||||
func merge(defaults preferences.PreferencesSpec, items []preferences.Preferences) (*preferences.Preferences, error) {
|
||||
p := &preferences.Preferences{
|
||||
TypeMeta: preferences.PreferencesResourceInfo.TypeMeta(),
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
CreationTimestamp: v1.Now(),
|
||||
},
|
||||
Spec: defaults,
|
||||
}
|
||||
|
||||
// Iterate in reverse order (least relevant to most relevant)
|
||||
for _, v := range items {
|
||||
if err := mergo.Merge(&p.Spec, &v.Spec, mergo.WithOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package preferences
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
preferences "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
|
||||
)
|
||||
|
||||
func TestStarsQueries(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
defaults preferences.PreferencesSpec
|
||||
items []preferences.Preferences
|
||||
expect preferences.PreferencesSpec
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
defaults: preferences.PreferencesSpec{
|
||||
Theme: ptr.To("settings.ini"),
|
||||
Language: ptr.To("settings.ini"),
|
||||
HomeDashboardUID: ptr.To("settings.ini"),
|
||||
Timezone: ptr.To("settings.ini"),
|
||||
},
|
||||
items: []preferences.Preferences{
|
||||
{Spec: preferences.PreferencesSpec{
|
||||
Theme: ptr.To("namespace"),
|
||||
Language: ptr.To("namespace"),
|
||||
HomeDashboardUID: ptr.To("namespace"),
|
||||
}},
|
||||
{Spec: preferences.PreferencesSpec{
|
||||
Theme: ptr.To("team"),
|
||||
Language: ptr.To("team"),
|
||||
}},
|
||||
{Spec: preferences.PreferencesSpec{
|
||||
Theme: ptr.To("user"),
|
||||
}},
|
||||
},
|
||||
expect: preferences.PreferencesSpec{
|
||||
Theme: ptr.To("user"),
|
||||
Language: ptr.To("team"),
|
||||
HomeDashboardUID: ptr.To("namespace"),
|
||||
Timezone: ptr.To("settings.ini"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
current, err := merge(tt.defaults, tt.items)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expect, current.Spec)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
requestK8s "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
var (
|
||||
_ rest.Scoper = (*preferenceStorage)(nil)
|
||||
_ rest.SingularNameProvider = (*preferenceStorage)(nil)
|
||||
_ rest.Getter = (*preferenceStorage)(nil)
|
||||
_ rest.Lister = (*preferenceStorage)(nil)
|
||||
_ rest.Storage = (*preferenceStorage)(nil)
|
||||
// _ rest.Creater = (*preferenceStorage)(nil)
|
||||
// _ rest.Updater = (*preferenceStorage)(nil)
|
||||
// _ rest.GracefulDeleter = (*preferenceStorage)(nil)
|
||||
)
|
||||
|
||||
func NewPreferencesStorage(namespacer request.NamespaceMapper, sql *LegacySQL) *preferenceStorage {
|
||||
return &preferenceStorage{
|
||||
namespacer: namespacer,
|
||||
sql: sql,
|
||||
tableConverter: preferences.PreferencesResourceInfo.TableConverter(),
|
||||
}
|
||||
}
|
||||
|
||||
type preferenceStorage struct {
|
||||
namespacer request.NamespaceMapper
|
||||
tableConverter rest.TableConvertor
|
||||
sql *LegacySQL
|
||||
}
|
||||
|
||||
func (s *preferenceStorage) New() runtime.Object {
|
||||
return preferences.PreferencesKind().ZeroValue()
|
||||
}
|
||||
|
||||
func (s *preferenceStorage) Destroy() {}
|
||||
|
||||
func (s *preferenceStorage) NamespaceScoped() bool {
|
||||
return true // namespace == org
|
||||
}
|
||||
|
||||
func (s *preferenceStorage) GetSingularName() string {
|
||||
return strings.ToLower(preferences.PreferencesKind().Kind())
|
||||
}
|
||||
|
||||
func (s *preferenceStorage) NewList() runtime.Object {
|
||||
return preferences.PreferencesKind().ZeroListValue()
|
||||
}
|
||||
|
||||
func (s *preferenceStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
func (s *preferenceStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns := requestK8s.NamespaceValue(ctx)
|
||||
if user.GetIsGrafanaAdmin() {
|
||||
user = nil // nill user can see everything
|
||||
}
|
||||
return s.sql.ListPreferences(ctx, ns, user, true)
|
||||
}
|
||||
|
||||
func (s *preferenceStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
ns, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
owner, ok := utils.ParseOwnerFromName(name)
|
||||
if !ok {
|
||||
return nil, preferences.PreferencesResourceInfo.NewNotFound(name)
|
||||
}
|
||||
|
||||
found, _, err := s.sql.listPreferences(ctx, ns.Value, ns.OrgID, func(req *preferencesQuery) (bool, error) {
|
||||
switch owner.Owner {
|
||||
case utils.UserResourceOwner:
|
||||
if !user.GetIsGrafanaAdmin() && name != user.GetUID() {
|
||||
return false, fmt.Errorf("you may only fetch your own preferences")
|
||||
}
|
||||
req.UserUID = owner.Name
|
||||
return false, nil
|
||||
case utils.TeamResourceOwner:
|
||||
if !user.GetIsGrafanaAdmin() {
|
||||
teams, err := s.sql.GetTeams(ctx, ns.OrgID, user.GetRawIdentifier(), false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !slices.Contains(teams, owner.Name) {
|
||||
return false, fmt.Errorf("you may only fetch teams you belong to")
|
||||
}
|
||||
}
|
||||
req.TeamUID = owner.Name
|
||||
return false, nil
|
||||
case utils.NamespaceResourceOwner:
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported name")
|
||||
}
|
||||
}, func(p *preferenceModel) bool {
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(found) == 1 {
|
||||
return &found[0], nil
|
||||
}
|
||||
return nil, preferences.PreferencesResourceInfo.NewNotFound(name)
|
||||
}
|
||||
|
||||
func asPreferencesResource(ns string, p *preferenceModel) preferences.Preferences {
|
||||
owner := utils.OwnerReference{}
|
||||
if p.TeamUID.Valid {
|
||||
owner.Owner = utils.TeamResourceOwner
|
||||
owner.Name = p.TeamUID.String
|
||||
} else if p.UserUID.Valid {
|
||||
owner.Owner = utils.UserResourceOwner
|
||||
owner.Name = p.UserUID.String
|
||||
} else {
|
||||
owner.Owner = utils.NamespaceResourceOwner
|
||||
owner.Name = ""
|
||||
}
|
||||
obj := preferences.Preferences{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: owner.AsName(),
|
||||
Namespace: ns,
|
||||
ResourceVersion: strconv.FormatInt(p.Updated.UnixMilli(), 10),
|
||||
CreationTimestamp: metav1.NewTime(p.Created.UTC()),
|
||||
},
|
||||
Spec: preferences.PreferencesSpec{
|
||||
Theme: asPointer(p.Theme.String),
|
||||
HomeDashboardUID: asPointer(p.HomeDashboardUID.String),
|
||||
Timezone: asPointer(p.Timezone.String),
|
||||
WeekStart: asPointer(p.WeekStart.String),
|
||||
},
|
||||
}
|
||||
|
||||
if !p.Created.Equal(p.Updated) {
|
||||
obj.Annotations = map[string]string{
|
||||
utilsOrig.AnnoKeyUpdatedTimestamp: p.Updated.UTC().Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
if p.JSONData != nil {
|
||||
obj.Spec.Language = asPointer(p.JSONData.Language)
|
||||
obj.Spec.RegionalFormat = asPointer(p.JSONData.RegionalFormat)
|
||||
|
||||
if p.JSONData.QueryHistory.HomeTab != "" {
|
||||
obj.Spec.QueryHistory = &preferences.PreferencesQueryHistoryPreference{
|
||||
HomeTab: &p.JSONData.QueryHistory.HomeTab,
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.JSONData.CookiePreferences) > 0 {
|
||||
// Analytics interface{} `json:"analytics,omitempty"`
|
||||
// Performance interface{} `json:"performance,omitempty"`
|
||||
// Functional interface{} `json:"functional,omitempty"`
|
||||
obj.Spec.CookiePreferences = preferences.NewPreferencesCookiePreferences()
|
||||
v, ok := p.JSONData.CookiePreferences["analytics"]
|
||||
if ok {
|
||||
obj.Spec.CookiePreferences.Analytics = v
|
||||
}
|
||||
v, ok = p.JSONData.CookiePreferences["performance"]
|
||||
if ok {
|
||||
obj.Spec.CookiePreferences.Performance = v
|
||||
}
|
||||
v, ok = p.JSONData.CookiePreferences["functional"]
|
||||
if ok {
|
||||
obj.Spec.CookiePreferences.Functional = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.JSONData.Navbar.BookmarkUrls) > 0 {
|
||||
obj.Spec.Navbar = &preferences.PreferencesNavbarPreference{
|
||||
BookmarkUrls: p.JSONData.Navbar.BookmarkUrls,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func asPointer(v string) *string {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return &v
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
)
|
||||
|
||||
// Templates setup.
|
||||
var (
|
||||
//go:embed *.sql
|
||||
sqlTemplatesFS embed.FS
|
||||
|
||||
sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `*.sql`))
|
||||
)
|
||||
|
||||
func mustTemplate(filename string) *template.Template {
|
||||
if t := sqlTemplates.Lookup(filename); t != nil {
|
||||
return t
|
||||
}
|
||||
panic(fmt.Sprintf("template file not found: %s", filename))
|
||||
}
|
||||
|
||||
// Templates.
|
||||
var (
|
||||
sqlStarsQuery = mustTemplate("sql_stars_query.sql")
|
||||
sqlStarsRV = mustTemplate("sql_stars_rv.sql")
|
||||
sqlPreferencesQuery = mustTemplate("sql_preferences_query.sql")
|
||||
sqlPreferencesRV = mustTemplate("sql_preferences_rv.sql")
|
||||
sqlTeams = mustTemplate("sql_teams.sql")
|
||||
)
|
||||
|
||||
type starQuery struct {
|
||||
sqltemplate.SQLTemplate
|
||||
|
||||
OrgID int64 // >= 1 if UserID != ""
|
||||
UserUID string
|
||||
|
||||
StarTable string
|
||||
UserTable string
|
||||
}
|
||||
|
||||
func (r starQuery) Validate() error {
|
||||
if r.UserUID != "" && r.OrgID < 1 {
|
||||
return fmt.Errorf("requests with a userid, must include an orgID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newStarQueryReq(sql *legacysql.LegacyDatabaseHelper, user string, orgId int64) starQuery {
|
||||
return starQuery{
|
||||
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
|
||||
|
||||
UserUID: user,
|
||||
OrgID: orgId,
|
||||
|
||||
StarTable: sql.Table("star"),
|
||||
UserTable: sql.Table("user"),
|
||||
}
|
||||
}
|
||||
|
||||
type preferencesQuery struct {
|
||||
sqltemplate.SQLTemplate
|
||||
|
||||
OrgID int64 // required
|
||||
UserUID string
|
||||
UserTeams []string // also requires user UID
|
||||
TeamUID string
|
||||
|
||||
UserTable string
|
||||
TeamTable string
|
||||
PreferencesTable string
|
||||
}
|
||||
|
||||
func (r preferencesQuery) CheckTeams() bool {
|
||||
return r.UserTeams != nil
|
||||
}
|
||||
|
||||
func (r preferencesQuery) HasTeams() bool {
|
||||
return len(r.UserTeams) > 0
|
||||
}
|
||||
|
||||
func (r preferencesQuery) Validate() error {
|
||||
if r.OrgID < 1 {
|
||||
return fmt.Errorf("must include an orgID")
|
||||
}
|
||||
if len(r.UserTeams) > 0 && r.UserUID == "" {
|
||||
return fmt.Errorf("user required when filtering by a set of teams")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newPreferencesQueryReq(sql *legacysql.LegacyDatabaseHelper, orgId int64) preferencesQuery {
|
||||
return preferencesQuery{
|
||||
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
|
||||
|
||||
OrgID: orgId,
|
||||
|
||||
PreferencesTable: sql.Table("preferences"),
|
||||
UserTable: sql.Table("user"),
|
||||
TeamTable: sql.Table("team"),
|
||||
}
|
||||
}
|
||||
|
||||
type teamQuery struct {
|
||||
sqltemplate.SQLTemplate
|
||||
|
||||
OrgID int64
|
||||
UserUID string
|
||||
IsAdmin bool
|
||||
|
||||
TeamMemberTable string
|
||||
TeamTable string
|
||||
UserTable string
|
||||
}
|
||||
|
||||
func (r teamQuery) Validate() error {
|
||||
if r.UserUID != "" && r.OrgID < 1 {
|
||||
return fmt.Errorf("requests with a userid, must include an orgID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newTeamsQueryReq(sql *legacysql.LegacyDatabaseHelper, orgId int64, user string, admin bool) teamQuery {
|
||||
return teamQuery{
|
||||
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
|
||||
|
||||
OrgID: orgId,
|
||||
UserUID: user,
|
||||
IsAdmin: admin,
|
||||
|
||||
TeamMemberTable: sql.Table("team_member"),
|
||||
TeamTable: sql.Table("team"),
|
||||
UserTable: sql.Table("user"),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks"
|
||||
)
|
||||
|
||||
func TestStarsQueries(t *testing.T) {
|
||||
// prefix tables with grafana
|
||||
nodb := &legacysql.LegacyDatabaseHelper{
|
||||
Table: func(n string) string {
|
||||
return "grafana." + n
|
||||
},
|
||||
}
|
||||
|
||||
getStarQuery := func(orgId int64, user string) sqltemplate.SQLTemplate {
|
||||
v := newStarQueryReq(nodb, user, orgId)
|
||||
v.SQLTemplate = mocks.NewTestingSQLTemplate()
|
||||
return &v
|
||||
}
|
||||
|
||||
getPreferencesQuery := func(orgId int64, cb func(q *preferencesQuery)) sqltemplate.SQLTemplate {
|
||||
v := newPreferencesQueryReq(nodb, orgId)
|
||||
v.SQLTemplate = mocks.NewTestingSQLTemplate()
|
||||
cb(&v)
|
||||
return &v
|
||||
}
|
||||
|
||||
getTeamQuery := func(orgId int64, user string, admin bool) sqltemplate.SQLTemplate {
|
||||
v := newTeamsQueryReq(nodb, orgId, user, admin)
|
||||
v.SQLTemplate = mocks.NewTestingSQLTemplate()
|
||||
return &v
|
||||
}
|
||||
|
||||
mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
|
||||
RootDir: "testdata",
|
||||
SQLTemplatesFS: sqlTemplatesFS,
|
||||
Templates: map[*template.Template][]mocks.TemplateTestCase{
|
||||
sqlStarsQuery: {
|
||||
{
|
||||
Name: "all",
|
||||
Data: getStarQuery(0, ""),
|
||||
},
|
||||
{
|
||||
Name: "org",
|
||||
Data: getStarQuery(3, ""),
|
||||
},
|
||||
{
|
||||
Name: "user",
|
||||
Data: getStarQuery(3, "abc"),
|
||||
},
|
||||
},
|
||||
sqlStarsRV: {
|
||||
{
|
||||
Name: "get",
|
||||
Data: getStarQuery(0, ""),
|
||||
},
|
||||
},
|
||||
sqlPreferencesQuery: {
|
||||
{
|
||||
Name: "all",
|
||||
Data: getPreferencesQuery(1, func(q *preferencesQuery) {}),
|
||||
},
|
||||
{
|
||||
Name: "user-no-teams",
|
||||
Data: getPreferencesQuery(1, func(q *preferencesQuery) {
|
||||
q.UserUID = "uuu"
|
||||
q.UserTeams = []string{}
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "current", // user + user teams
|
||||
Data: getPreferencesQuery(1, func(q *preferencesQuery) {
|
||||
q.UserUID = "uuu"
|
||||
q.UserTeams = []string{"a", "b", "c"}
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "user",
|
||||
Data: getPreferencesQuery(1, func(q *preferencesQuery) {
|
||||
q.UserUID = "uuu"
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "team",
|
||||
Data: getPreferencesQuery(1, func(q *preferencesQuery) {
|
||||
q.TeamUID = "ttt"
|
||||
}),
|
||||
},
|
||||
},
|
||||
sqlPreferencesRV: {
|
||||
{
|
||||
Name: "get",
|
||||
Data: getPreferencesQuery(1, func(q *preferencesQuery) {}),
|
||||
},
|
||||
},
|
||||
sqlTeams: {
|
||||
{
|
||||
Name: "members",
|
||||
Data: getTeamQuery(1, "uuu", false),
|
||||
},
|
||||
{
|
||||
Name: "admin",
|
||||
Data: getTeamQuery(1, "uuu", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
preferences "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
)
|
||||
|
||||
type dashboardStars struct {
|
||||
OrgID int64
|
||||
UserUID string
|
||||
First int64
|
||||
Last int64
|
||||
|
||||
Dashboards []string
|
||||
}
|
||||
|
||||
type preferenceModel struct {
|
||||
ID int64
|
||||
OrgID int64
|
||||
UserUID sql.NullString
|
||||
TeamUID sql.NullString
|
||||
JSONData *pref.PreferenceJSONData
|
||||
HomeDashboardUID sql.NullString
|
||||
Timezone sql.NullString
|
||||
Theme sql.NullString
|
||||
WeekStart sql.NullString
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
type LegacySQL struct {
|
||||
db legacysql.LegacyDatabaseProvider
|
||||
startup time.Time
|
||||
}
|
||||
|
||||
func NewLegacySQL(db legacysql.LegacyDatabaseProvider) *LegacySQL {
|
||||
return &LegacySQL{db: db, startup: time.Now()}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var max sql.NullString
|
||||
sql, err := s.db(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
req := newStarQueryReq(sql, user, orgId)
|
||||
|
||||
q, err := sqltemplate.Execute(sqlStarsQuery, req)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("execute template %q: %w", sqlStarsQuery.Name(), err)
|
||||
}
|
||||
|
||||
sess := sql.DB.GetSqlxSession()
|
||||
rows, err := sess.Query(ctx, q, req.GetArgs()...)
|
||||
defer func() {
|
||||
if rows != nil {
|
||||
_ = rows.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
stars := []dashboardStars{}
|
||||
current := &dashboardStars{}
|
||||
var orgID int64
|
||||
var userUID string
|
||||
var dashboardUID string
|
||||
var updated time.Time
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&orgID, &userUID, &dashboardUID, &updated)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if orgID != current.OrgID || userUID != current.UserUID {
|
||||
if current.UserUID != "" {
|
||||
stars = append(stars, *current)
|
||||
}
|
||||
current = &dashboardStars{
|
||||
OrgID: orgID,
|
||||
UserUID: userUID,
|
||||
}
|
||||
}
|
||||
ts := updated.UnixMilli()
|
||||
if ts > current.Last {
|
||||
current.Last = ts
|
||||
}
|
||||
if ts < current.First || current.First == 0 {
|
||||
current.First = ts
|
||||
}
|
||||
current.Dashboards = append(current.Dashboards, dashboardUID)
|
||||
}
|
||||
|
||||
// Add the last value
|
||||
if current.UserUID != "" {
|
||||
stars = append(stars, *current)
|
||||
}
|
||||
|
||||
// Find the RV unless it is a user query
|
||||
if userUID == "" {
|
||||
req.Reset()
|
||||
q, err = sqltemplate.Execute(sqlStarsRV, req)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("execute template %q: %w", sqlPreferencesRV.Name(), err)
|
||||
}
|
||||
err = sess.Get(ctx, &max, q)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("unable to get RV %w", err)
|
||||
}
|
||||
if max.Valid && max.String != "" {
|
||||
fmt.Printf("max RV: %s\n", max.String)
|
||||
} else {
|
||||
updated = s.startup
|
||||
}
|
||||
}
|
||||
|
||||
return stars, updated.UnixMilli(), err
|
||||
}
|
||||
|
||||
// List all defined preferences in an org (valid for admin users only)
|
||||
func (s *LegacySQL) listPreferences(ctx context.Context,
|
||||
ns string, orgId int64,
|
||||
cb func(req *preferencesQuery) (bool, error),
|
||||
access func(p *preferenceModel) bool,
|
||||
) ([]preferences.Preferences, int64, error) {
|
||||
var results []preferences.Preferences
|
||||
var rv sql.NullTime
|
||||
var max sql.NullString
|
||||
|
||||
sql, err := s.db(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
req := newPreferencesQueryReq(sql, orgId)
|
||||
needsRV, err := cb(&req)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
q, err := sqltemplate.Execute(sqlPreferencesQuery, req)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("execute template %q: %w", sqlPreferencesQuery.Name(), err)
|
||||
}
|
||||
|
||||
sess := sql.DB.GetSqlxSession()
|
||||
rows, err := sess.Query(ctx, q, req.GetArgs()...)
|
||||
defer func() {
|
||||
if rows != nil {
|
||||
_ = rows.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
// SELECT p.id, p.org_id,
|
||||
// p.json_data,
|
||||
// p.timezone,
|
||||
// p.theme,
|
||||
// p.week_start,
|
||||
// p.home_dashboard_uid,
|
||||
// u.uid as user_uid,
|
||||
// t.uid as team_uid,
|
||||
// p.created, p.updated
|
||||
|
||||
pref := preferenceModel{}
|
||||
err := rows.Scan(&pref.ID, &pref.OrgID,
|
||||
&pref.JSONData,
|
||||
&pref.Timezone,
|
||||
&pref.Theme,
|
||||
&pref.WeekStart,
|
||||
&pref.HomeDashboardUID,
|
||||
&pref.UserUID, &pref.TeamUID,
|
||||
&pref.Created, &pref.Updated)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if pref.Updated.After(rv.Time) {
|
||||
rv.Time = pref.Updated
|
||||
}
|
||||
if !access(&pref) {
|
||||
continue // user does not have access
|
||||
}
|
||||
results = append(results, asPreferencesResource(ns, &pref))
|
||||
}
|
||||
|
||||
if needsRV {
|
||||
req.Reset()
|
||||
q, err = sqltemplate.Execute(sqlPreferencesRV, req)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("execute template %q: %w", sqlPreferencesRV.Name(), err)
|
||||
}
|
||||
err = sess.Get(ctx, &max, q)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("unable to get RV %w", err)
|
||||
}
|
||||
if max.Valid && max.String != "" {
|
||||
fmt.Printf("max RV: %s\n", max.String)
|
||||
} else {
|
||||
rv.Time = s.startup
|
||||
}
|
||||
}
|
||||
return results, rv.Time.UnixMilli(), err
|
||||
}
|
||||
|
||||
func (s *LegacySQL) ListPreferences(ctx context.Context, ns string, user identity.Requester, needsRV bool) (*preferences.PreferencesList, error) {
|
||||
if ns == "" {
|
||||
return nil, fmt.Errorf("namespace is required")
|
||||
}
|
||||
|
||||
info, err := authlib.ParseNamespace(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// when the user is nil, it is actually admin and can see everything
|
||||
var teams []string
|
||||
found, rv, err := s.listPreferences(ctx, ns, info.OrgID,
|
||||
func(req *preferencesQuery) (bool, error) {
|
||||
if user != nil {
|
||||
req.UserUID = user.GetRawIdentifier()
|
||||
teams, err = s.GetTeams(ctx, info.OrgID, req.UserUID, false)
|
||||
req.UserTeams = teams
|
||||
}
|
||||
return needsRV, err
|
||||
},
|
||||
func(p *preferenceModel) bool {
|
||||
if user == nil || user.GetIsGrafanaAdmin() {
|
||||
return true
|
||||
}
|
||||
if p.UserUID.String != "" {
|
||||
return user.GetRawIdentifier() == p.UserUID.String
|
||||
}
|
||||
if p.TeamUID.String != "" {
|
||||
return slices.Contains(teams, p.TeamUID.String)
|
||||
}
|
||||
return true
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list := &preferences.PreferencesList{
|
||||
Items: found,
|
||||
}
|
||||
if rv > 0 {
|
||||
list.ResourceVersion = strconv.FormatInt(rv, 10)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *LegacySQL) GetTeams(ctx context.Context, orgId int64, user string, admin bool) ([]string, error) {
|
||||
sql, err := s.db(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := newTeamsQueryReq(sql, orgId, user, admin)
|
||||
|
||||
q, err := sqltemplate.Execute(sqlTeams, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlTeams.Name(), err)
|
||||
}
|
||||
teams := []string{}
|
||||
sess := sql.DB.GetSqlxSession()
|
||||
err = sess.Select(ctx, &teams, q, req.GetArgs()...)
|
||||
return teams, err
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM {{ .Ident .PreferencesTable }} as p
|
||||
LEFT JOIN {{ .Ident .UserTable }} as u ON p.user_id = u.id
|
||||
LEFT JOIN {{ .Ident .TeamTable }} as t ON p.team_id = t.id
|
||||
WHERE p.org_id = {{ .Arg .OrgID }}
|
||||
{{ if .TeamUID }}
|
||||
AND t.uid = {{ .Arg .TeamUID }}
|
||||
{{ else if .UserUID }}
|
||||
AND (u.uid = {{ .Arg .UserUID }}
|
||||
{{ if .CheckTeams }}
|
||||
{{ if .HasTeams }}
|
||||
OR t.uid IN ({{ .ArgList .UserTeams }})
|
||||
{{ end }}
|
||||
OR p.user_id = 0
|
||||
{{ end }}
|
||||
)
|
||||
{{ end }}
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
@@ -0,0 +1 @@
|
||||
SELECT MAX(updated) FROM {{ .Ident .PreferencesTable }}
|
||||
@@ -0,0 +1,10 @@
|
||||
SELECT s.org_id, u.uid as user_uid, s.dashboard_uid, s.updated
|
||||
FROM {{ .Ident .StarTable }} as s
|
||||
JOIN {{ .Ident .UserTable }} as u ON s.user_id = u.id
|
||||
{{ if ge .OrgID 1 }}
|
||||
WHERE s.org_id = {{ .Arg .OrgID }}
|
||||
{{ if .UserUID }}
|
||||
AND u.uid = {{ .Arg .UserUID }}
|
||||
{{ end }}{{ end }}
|
||||
ORDER BY
|
||||
s.org_id asc, s.user_id asc, s.updated asc
|
||||
@@ -0,0 +1 @@
|
||||
SELECT MAX(updated) FROM {{ .Ident .StarTable }}
|
||||
@@ -0,0 +1,9 @@
|
||||
SELECT t.uid FROM {{ .Ident .TeamMemberTable }} as tm
|
||||
JOIN {{ .Ident .UserTable }} as u ON tm.user_id = u.id
|
||||
JOIN {{ .Ident .TeamTable }} as t ON tm.team_id = t.id
|
||||
WHERE tm.org_id = {{ .Arg .OrgID }}
|
||||
AND u.uid = {{ .Arg .UserUID }}
|
||||
{{ if .IsAdmin }}
|
||||
AND tm.permission = 4
|
||||
{{ end }}
|
||||
ORDER BY t.id
|
||||
@@ -0,0 +1,130 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
dashboardsV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
||||
preferences "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
)
|
||||
|
||||
var (
|
||||
_ rest.Scoper = (*starsStorage)(nil)
|
||||
_ rest.SingularNameProvider = (*starsStorage)(nil)
|
||||
_ rest.Getter = (*starsStorage)(nil)
|
||||
_ rest.Lister = (*starsStorage)(nil)
|
||||
_ rest.Storage = (*starsStorage)(nil)
|
||||
// _ rest.Creater = (*starsStorage)(nil)
|
||||
// _ rest.Updater = (*starsStorage)(nil)
|
||||
// _ rest.GracefulDeleter = (*starsStorage)(nil)
|
||||
)
|
||||
|
||||
func NewStarsStorage(namespacer request.NamespaceMapper, sql *LegacySQL) *starsStorage {
|
||||
return &starsStorage{
|
||||
namespacer: namespacer,
|
||||
sql: sql,
|
||||
tableConverter: preferences.StarsResourceInfo.TableConverter(),
|
||||
}
|
||||
}
|
||||
|
||||
type starsStorage struct {
|
||||
namespacer request.NamespaceMapper
|
||||
tableConverter rest.TableConvertor
|
||||
sql *LegacySQL
|
||||
}
|
||||
|
||||
func (s *starsStorage) New() runtime.Object {
|
||||
return preferences.StarsKind().ZeroValue()
|
||||
}
|
||||
|
||||
func (s *starsStorage) Destroy() {}
|
||||
|
||||
func (s *starsStorage) NamespaceScoped() bool {
|
||||
return true // namespace == org
|
||||
}
|
||||
|
||||
func (s *starsStorage) GetSingularName() string {
|
||||
return strings.ToLower(preferences.StarsKind().Kind())
|
||||
}
|
||||
|
||||
func (s *starsStorage) NewList() runtime.Object {
|
||||
return preferences.StarsKind().ZeroListValue()
|
||||
}
|
||||
|
||||
func (s *starsStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
func (s *starsStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
ns, err := request.NamespaceInfoFrom(ctx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ns.Value == "" {
|
||||
return nil, fmt.Errorf("cross cluster listing is not supported")
|
||||
}
|
||||
|
||||
list := &preferences.StarsList{}
|
||||
found, rv, err := s.sql.GetStars(ctx, ns.OrgID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range found {
|
||||
list.Items = append(list.Items, asStarsResource(s.namespacer(v.OrgID), &v))
|
||||
}
|
||||
if rv > 0 {
|
||||
list.ResourceVersion = strconv.FormatInt(rv, 10)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *starsStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ut, uid, err := authlib.ParseTypeID(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid name %w", err)
|
||||
}
|
||||
if ut != authlib.TypeUser {
|
||||
return nil, fmt.Errorf("expecting name with prefix: %s", authlib.TypeUser)
|
||||
}
|
||||
|
||||
found, _, err := s.sql.GetStars(ctx, info.OrgID, uid)
|
||||
if err != nil || len(found) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
obj := asStarsResource(info.Value, &found[0])
|
||||
return &obj, nil
|
||||
}
|
||||
|
||||
func asStarsResource(ns string, v *dashboardStars) preferences.Stars {
|
||||
return preferences.Stars{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("user:%s", v.UserUID),
|
||||
Namespace: ns,
|
||||
ResourceVersion: strconv.FormatInt(v.Last, 10),
|
||||
CreationTimestamp: metav1.NewTime(time.UnixMilli(v.First)),
|
||||
},
|
||||
Spec: preferences.StarsSpec{
|
||||
Resource: []preferences.StarsResource{{
|
||||
Group: dashboardsV1.APIGroup,
|
||||
Kind: "Dashboard",
|
||||
Names: v.Dashboards,
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
||||
Vendored
Executable
+14
@@ -0,0 +1,14 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM `grafana`.`preferences` as p
|
||||
LEFT JOIN `grafana`.`user` as u ON p.user_id = u.id
|
||||
LEFT JOIN `grafana`.`team` as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+18
@@ -0,0 +1,18 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM `grafana`.`preferences` as p
|
||||
LEFT JOIN `grafana`.`user` as u ON p.user_id = u.id
|
||||
LEFT JOIN `grafana`.`team` as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND (u.uid = 'uuu'
|
||||
OR t.uid IN ('a', 'b', 'c')
|
||||
OR p.user_id = 0
|
||||
)
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+15
@@ -0,0 +1,15 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM `grafana`.`preferences` as p
|
||||
LEFT JOIN `grafana`.`user` as u ON p.user_id = u.id
|
||||
LEFT JOIN `grafana`.`team` as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND t.uid = 'ttt'
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+17
@@ -0,0 +1,17 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM `grafana`.`preferences` as p
|
||||
LEFT JOIN `grafana`.`user` as u ON p.user_id = u.id
|
||||
LEFT JOIN `grafana`.`team` as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND (u.uid = 'uuu'
|
||||
OR p.user_id = 0
|
||||
)
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+16
@@ -0,0 +1,16 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM `grafana`.`preferences` as p
|
||||
LEFT JOIN `grafana`.`user` as u ON p.user_id = u.id
|
||||
LEFT JOIN `grafana`.`team` as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND (u.uid = 'uuu'
|
||||
)
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
+1
@@ -0,0 +1 @@
|
||||
SELECT MAX(updated) FROM `grafana`.`preferences`
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
SELECT s.org_id, u.uid as user_uid, s.dashboard_uid, s.updated
|
||||
FROM `grafana`.`star` as s
|
||||
JOIN `grafana`.`user` as u ON s.user_id = u.id
|
||||
ORDER BY
|
||||
s.org_id asc, s.user_id asc, s.updated asc
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
SELECT s.org_id, u.uid as user_uid, s.dashboard_uid, s.updated
|
||||
FROM `grafana`.`star` as s
|
||||
JOIN `grafana`.`user` as u ON s.user_id = u.id
|
||||
WHERE s.org_id = 3
|
||||
ORDER BY
|
||||
s.org_id asc, s.user_id asc, s.updated asc
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
SELECT s.org_id, u.uid as user_uid, s.dashboard_uid, s.updated
|
||||
FROM `grafana`.`star` as s
|
||||
JOIN `grafana`.`user` as u ON s.user_id = u.id
|
||||
WHERE s.org_id = 3
|
||||
AND u.uid = 'abc'
|
||||
ORDER BY
|
||||
s.org_id asc, s.user_id asc, s.updated asc
|
||||
+1
@@ -0,0 +1 @@
|
||||
SELECT MAX(updated) FROM `grafana`.`star`
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
SELECT t.uid FROM `grafana`.`team_member` as tm
|
||||
JOIN `grafana`.`user` as u ON tm.user_id = u.id
|
||||
JOIN `grafana`.`team` as t ON tm.team_id = t.id
|
||||
WHERE tm.org_id = 1
|
||||
AND u.uid = 'uuu'
|
||||
AND tm.permission = 4
|
||||
ORDER BY t.id
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
SELECT t.uid FROM `grafana`.`team_member` as tm
|
||||
JOIN `grafana`.`user` as u ON tm.user_id = u.id
|
||||
JOIN `grafana`.`team` as t ON tm.team_id = t.id
|
||||
WHERE tm.org_id = 1
|
||||
AND u.uid = 'uuu'
|
||||
ORDER BY t.id
|
||||
Vendored
Executable
+14
@@ -0,0 +1,14 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM "grafana"."preferences" as p
|
||||
LEFT JOIN "grafana"."user" as u ON p.user_id = u.id
|
||||
LEFT JOIN "grafana"."team" as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+18
@@ -0,0 +1,18 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM "grafana"."preferences" as p
|
||||
LEFT JOIN "grafana"."user" as u ON p.user_id = u.id
|
||||
LEFT JOIN "grafana"."team" as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND (u.uid = 'uuu'
|
||||
OR t.uid IN ('a', 'b', 'c')
|
||||
OR p.user_id = 0
|
||||
)
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+15
@@ -0,0 +1,15 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM "grafana"."preferences" as p
|
||||
LEFT JOIN "grafana"."user" as u ON p.user_id = u.id
|
||||
LEFT JOIN "grafana"."team" as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND t.uid = 'ttt'
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+17
@@ -0,0 +1,17 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM "grafana"."preferences" as p
|
||||
LEFT JOIN "grafana"."user" as u ON p.user_id = u.id
|
||||
LEFT JOIN "grafana"."team" as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND (u.uid = 'uuu'
|
||||
OR p.user_id = 0
|
||||
)
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+16
@@ -0,0 +1,16 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM "grafana"."preferences" as p
|
||||
LEFT JOIN "grafana"."user" as u ON p.user_id = u.id
|
||||
LEFT JOIN "grafana"."team" as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND (u.uid = 'uuu'
|
||||
)
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+1
@@ -0,0 +1 @@
|
||||
SELECT MAX(updated) FROM "grafana"."preferences"
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
SELECT s.org_id, u.uid as user_uid, s.dashboard_uid, s.updated
|
||||
FROM "grafana"."star" as s
|
||||
JOIN "grafana"."user" as u ON s.user_id = u.id
|
||||
ORDER BY
|
||||
s.org_id asc, s.user_id asc, s.updated asc
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
SELECT s.org_id, u.uid as user_uid, s.dashboard_uid, s.updated
|
||||
FROM "grafana"."star" as s
|
||||
JOIN "grafana"."user" as u ON s.user_id = u.id
|
||||
WHERE s.org_id = 3
|
||||
ORDER BY
|
||||
s.org_id asc, s.user_id asc, s.updated asc
|
||||
Vendored
Executable
+7
@@ -0,0 +1,7 @@
|
||||
SELECT s.org_id, u.uid as user_uid, s.dashboard_uid, s.updated
|
||||
FROM "grafana"."star" as s
|
||||
JOIN "grafana"."user" as u ON s.user_id = u.id
|
||||
WHERE s.org_id = 3
|
||||
AND u.uid = 'abc'
|
||||
ORDER BY
|
||||
s.org_id asc, s.user_id asc, s.updated asc
|
||||
+1
@@ -0,0 +1 @@
|
||||
SELECT MAX(updated) FROM "grafana"."star"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
SELECT t.uid FROM "grafana"."team_member" as tm
|
||||
JOIN "grafana"."user" as u ON tm.user_id = u.id
|
||||
JOIN "grafana"."team" as t ON tm.team_id = t.id
|
||||
WHERE tm.org_id = 1
|
||||
AND u.uid = 'uuu'
|
||||
AND tm.permission = 4
|
||||
ORDER BY t.id
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
SELECT t.uid FROM "grafana"."team_member" as tm
|
||||
JOIN "grafana"."user" as u ON tm.user_id = u.id
|
||||
JOIN "grafana"."team" as t ON tm.team_id = t.id
|
||||
WHERE tm.org_id = 1
|
||||
AND u.uid = 'uuu'
|
||||
ORDER BY t.id
|
||||
Vendored
Executable
+14
@@ -0,0 +1,14 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM "grafana"."preferences" as p
|
||||
LEFT JOIN "grafana"."user" as u ON p.user_id = u.id
|
||||
LEFT JOIN "grafana"."team" as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+18
@@ -0,0 +1,18 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM "grafana"."preferences" as p
|
||||
LEFT JOIN "grafana"."user" as u ON p.user_id = u.id
|
||||
LEFT JOIN "grafana"."team" as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND (u.uid = 'uuu'
|
||||
OR t.uid IN ('a', 'b', 'c')
|
||||
OR p.user_id = 0
|
||||
)
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+15
@@ -0,0 +1,15 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM "grafana"."preferences" as p
|
||||
LEFT JOIN "grafana"."user" as u ON p.user_id = u.id
|
||||
LEFT JOIN "grafana"."team" as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND t.uid = 'ttt'
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+17
@@ -0,0 +1,17 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM "grafana"."preferences" as p
|
||||
LEFT JOIN "grafana"."user" as u ON p.user_id = u.id
|
||||
LEFT JOIN "grafana"."team" as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND (u.uid = 'uuu'
|
||||
OR p.user_id = 0
|
||||
)
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+16
@@ -0,0 +1,16 @@
|
||||
SELECT p.id, p.org_id,
|
||||
p.json_data,
|
||||
p.timezone,
|
||||
p.theme,
|
||||
p.week_start,
|
||||
p.home_dashboard_uid,
|
||||
u.uid as user_uid,
|
||||
t.uid as team_uid,
|
||||
p.created, p.updated
|
||||
FROM "grafana"."preferences" as p
|
||||
LEFT JOIN "grafana"."user" as u ON p.user_id = u.id
|
||||
LEFT JOIN "grafana"."team" as t ON p.team_id = t.id
|
||||
WHERE p.org_id = 1
|
||||
AND (u.uid = 'uuu'
|
||||
)
|
||||
ORDER BY p.user_id asc, p.team_id asc, p.org_id asc
|
||||
Vendored
Executable
+1
@@ -0,0 +1 @@
|
||||
SELECT MAX(updated) FROM "grafana"."preferences"
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
SELECT s.org_id, u.uid as user_uid, s.dashboard_uid, s.updated
|
||||
FROM "grafana"."star" as s
|
||||
JOIN "grafana"."user" as u ON s.user_id = u.id
|
||||
ORDER BY
|
||||
s.org_id asc, s.user_id asc, s.updated asc
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
SELECT s.org_id, u.uid as user_uid, s.dashboard_uid, s.updated
|
||||
FROM "grafana"."star" as s
|
||||
JOIN "grafana"."user" as u ON s.user_id = u.id
|
||||
WHERE s.org_id = 3
|
||||
ORDER BY
|
||||
s.org_id asc, s.user_id asc, s.updated asc
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
SELECT s.org_id, u.uid as user_uid, s.dashboard_uid, s.updated
|
||||
FROM "grafana"."star" as s
|
||||
JOIN "grafana"."user" as u ON s.user_id = u.id
|
||||
WHERE s.org_id = 3
|
||||
AND u.uid = 'abc'
|
||||
ORDER BY
|
||||
s.org_id asc, s.user_id asc, s.updated asc
|
||||
+1
@@ -0,0 +1 @@
|
||||
SELECT MAX(updated) FROM "grafana"."star"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
SELECT t.uid FROM "grafana"."team_member" as tm
|
||||
JOIN "grafana"."user" as u ON tm.user_id = u.id
|
||||
JOIN "grafana"."team" as t ON tm.team_id = t.id
|
||||
WHERE tm.org_id = 1
|
||||
AND u.uid = 'uuu'
|
||||
AND tm.permission = 4
|
||||
ORDER BY t.id
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
SELECT t.uid FROM "grafana"."team_member" as tm
|
||||
JOIN "grafana"."user" as u ON tm.user_id = u.id
|
||||
JOIN "grafana"."team" as t ON tm.team_id = t.id
|
||||
WHERE tm.org_id = 1
|
||||
AND u.uid = 'uuu'
|
||||
ORDER BY t.id
|
||||
@@ -0,0 +1,101 @@
|
||||
package preferences
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
|
||||
preferences "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/preferences/legacy"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql"
|
||||
)
|
||||
|
||||
var _ builder.APIGroupBuilder = (*APIBuilder)(nil)
|
||||
|
||||
type APIBuilder struct {
|
||||
namespacer request.NamespaceMapper
|
||||
sql *legacy.LegacySQL
|
||||
|
||||
prefs pref.Service
|
||||
calculator *calculator // joins all preferences
|
||||
}
|
||||
|
||||
func RegisterAPIService(
|
||||
cfg *setting.Cfg,
|
||||
features featuremgmt.FeatureToggles,
|
||||
db db.DB,
|
||||
prefs pref.Service,
|
||||
apiregistration builder.APIRegistrar,
|
||||
) *APIBuilder {
|
||||
// Requires development settings and clearly experimental
|
||||
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
|
||||
return nil
|
||||
}
|
||||
|
||||
sql := legacy.NewLegacySQL(legacysql.NewDatabaseProvider(db))
|
||||
builder := &APIBuilder{
|
||||
prefs: prefs, // for writing
|
||||
namespacer: request.GetNamespaceMapper(cfg),
|
||||
sql: sql,
|
||||
calculator: newCalculator(cfg, sql),
|
||||
}
|
||||
apiregistration.RegisterAPI(builder)
|
||||
return builder
|
||||
}
|
||||
|
||||
// AllowedV0Alpha1Resources implements builder.APIGroupBuilder.
|
||||
func (b *APIBuilder) AllowedV0Alpha1Resources() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *APIBuilder) GetGroupVersion() schema.GroupVersion {
|
||||
return preferences.GroupVersion
|
||||
}
|
||||
|
||||
func (b *APIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
||||
gv := preferences.GroupVersion
|
||||
err := preferences.AddToScheme(scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metav1.AddToGroupVersion(scheme, gv)
|
||||
return scheme.SetVersionPriority(gv)
|
||||
}
|
||||
|
||||
func (b *APIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error {
|
||||
storage := map[string]rest.Storage{}
|
||||
|
||||
stars := preferences.StarsResourceInfo
|
||||
storage[stars.StoragePath()] = legacy.NewStarsStorage(b.namespacer, b.sql)
|
||||
|
||||
prefs := preferences.PreferencesResourceInfo
|
||||
// Unified storage
|
||||
// store, err := grafanaregistry.NewRegistryStore(opts.Scheme, resourceInfo, opts.OptsGetter)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
storage[prefs.StoragePath()] = legacy.NewPreferencesStorage(b.namespacer, b.sql)
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap[preferences.APIVersion] = storage
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *APIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
|
||||
return preferences.GetOpenAPIDefinitions
|
||||
}
|
||||
|
||||
func (b *APIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.APIRoutes {
|
||||
defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} })
|
||||
return b.calculator.GetAPIRoutes(defs)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
|
||||
// +enum
|
||||
type ResourceOwner string
|
||||
|
||||
const (
|
||||
// Used when there is a single resource for a given org|stack
|
||||
NamespaceResourceOwner ResourceOwner = "namespace"
|
||||
// Resource with a 1:1 user mapping
|
||||
UserResourceOwner ResourceOwner = "user"
|
||||
// Resource with a 1:1 team mapping
|
||||
TeamResourceOwner ResourceOwner = "team"
|
||||
// The name does not match user, team or namespace
|
||||
UnknownResourceOwner ResourceOwner = ""
|
||||
)
|
||||
|
||||
type OwnerReference struct {
|
||||
Owner ResourceOwner // the resource owner
|
||||
Name string // the team|user name
|
||||
}
|
||||
|
||||
func (o OwnerReference) AsName() string {
|
||||
if o.Name == "" || o.Owner == NamespaceResourceOwner {
|
||||
return string(o.Owner)
|
||||
}
|
||||
return string(o.Owner) + ":" + o.Name
|
||||
}
|
||||
|
||||
func ParseOwnerFromName(name string) (OwnerReference, bool) {
|
||||
before, after, found := strings.Cut(name, ":")
|
||||
if found && len(after) > 0 {
|
||||
switch before {
|
||||
case "user":
|
||||
return OwnerReference{Owner: UserResourceOwner, Name: after}, true
|
||||
case "team":
|
||||
return OwnerReference{Owner: TeamResourceOwner, Name: after}, true
|
||||
}
|
||||
} else if name == "namespace" {
|
||||
return OwnerReference{Owner: NamespaceResourceOwner}, true
|
||||
}
|
||||
return OwnerReference{}, false
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/preferences/utils"
|
||||
)
|
||||
|
||||
func TestLegacyAuthorizer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
output utils.OwnerReference
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
name: "invalid",
|
||||
input: "xxx:yyy",
|
||||
output: utils.OwnerReference{},
|
||||
found: false,
|
||||
},
|
||||
{
|
||||
name: "with user",
|
||||
input: "user:a",
|
||||
output: utils.OwnerReference{Owner: utils.UserResourceOwner, Name: "a"},
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "missing user",
|
||||
input: "user:",
|
||||
output: utils.OwnerReference{},
|
||||
found: false,
|
||||
},
|
||||
{
|
||||
name: "with team",
|
||||
input: "team:b",
|
||||
output: utils.OwnerReference{Owner: utils.TeamResourceOwner, Name: "b"},
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "missing team",
|
||||
input: "team:",
|
||||
output: utils.OwnerReference{},
|
||||
found: false,
|
||||
},
|
||||
{
|
||||
name: "for namespace",
|
||||
input: "namespace",
|
||||
output: utils.OwnerReference{Owner: utils.NamespaceResourceOwner},
|
||||
found: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output, found := utils.ParseOwnerFromName(tt.input)
|
||||
require.Equal(t, tt.output, output)
|
||||
require.Equal(t, tt.found, found)
|
||||
if tt.found {
|
||||
require.Equal(t, tt.input, output.AsName())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/ofrep"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/preferences"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/query"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret"
|
||||
@@ -47,6 +48,7 @@ var WireSet = wire.NewSet(
|
||||
provisioning.RegisterAPIService,
|
||||
service.RegisterAPIService,
|
||||
query.RegisterAPIService,
|
||||
preferences.RegisterAPIService,
|
||||
userstorage.RegisterAPIService,
|
||||
ofrep.RegisterAPIService,
|
||||
)
|
||||
|
||||
@@ -57,6 +57,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/ofrep"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/preferences"
|
||||
provisioning2 "github.com/grafana/grafana/pkg/registry/apis/provisioning"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/extras"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
@@ -802,6 +803,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
|
||||
return nil, err
|
||||
}
|
||||
userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer)
|
||||
apiBuilder := preferences.RegisterAPIService(cfg, featureToggles, sqlStore, prefService, apiserverService)
|
||||
legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, dashboardPermissionsService, accessControl, featureToggles)
|
||||
webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, renderingService, resourceClient, eventualRestConfigProvider)
|
||||
v3 := extras.ProvideProvisioningOSSExtras(webhookExtraBuilder)
|
||||
@@ -820,7 +822,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, accessClient, legacyMigrator, dualwriteService, usageStats, tracingService, v3, repositoryFactory)
|
||||
provisioningAPIBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, accessClient, legacyMigrator, dualwriteService, usageStats, tracingService, v3, repositoryFactory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -833,7 +835,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, apiBuilder, ofrepAPIBuilder, dependencyRegisterer)
|
||||
apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, apiBuilder, provisioningAPIBuilder, ofrepAPIBuilder, dependencyRegisterer)
|
||||
teamPermissionsService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1383,6 +1385,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
|
||||
return nil, err
|
||||
}
|
||||
userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer)
|
||||
apiBuilder := preferences.RegisterAPIService(cfg, featureToggles, sqlStore, prefService, apiserverService)
|
||||
legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, dashboardPermissionsService, accessControl, featureToggles)
|
||||
webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, renderingService, resourceClient, eventualRestConfigProvider)
|
||||
v3 := extras.ProvideProvisioningOSSExtras(webhookExtraBuilder)
|
||||
@@ -1401,7 +1404,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, accessClient, legacyMigrator, dualwriteService, usageStats, tracingService, v3, repositoryFactory)
|
||||
provisioningAPIBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, accessClient, legacyMigrator, dualwriteService, usageStats, tracingService, v3, repositoryFactory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1414,7 +1417,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, apiBuilder, ofrepAPIBuilder, dependencyRegisterer)
|
||||
apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, apiBuilder, provisioningAPIBuilder, ofrepAPIBuilder, dependencyRegisterer)
|
||||
teamPermissionsService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -332,6 +332,8 @@ type OrgUsers struct {
|
||||
Editor User
|
||||
Viewer User
|
||||
|
||||
OrgID int64
|
||||
|
||||
// Separate standalone service accounts with different roles
|
||||
AdminServiceAccount serviceaccounts.ServiceAccountDTO
|
||||
AdminServiceAccountToken string
|
||||
@@ -559,18 +561,19 @@ func (c *K8sTestHelper) createTestUsers(orgName string) OrgUsers {
|
||||
Editor: c.CreateUser("editor", orgName, org.RoleEditor, nil),
|
||||
Viewer: c.CreateUser("viewer", orgName, org.RoleViewer, nil),
|
||||
}
|
||||
users.OrgID = users.Admin.Identity.GetOrgID()
|
||||
|
||||
// Create service accounts
|
||||
users.AdminServiceAccount = c.CreateServiceAccount(users.Admin, "admin-sa", users.Admin.Identity.GetOrgID(), org.RoleAdmin)
|
||||
users.AdminServiceAccountToken = c.CreateServiceAccountToken(users.Admin, users.AdminServiceAccount.Id, users.Admin.Identity.GetOrgID(), "admin-token", 0)
|
||||
users.AdminServiceAccount = c.CreateServiceAccount(users.Admin, "admin-sa", users.OrgID, org.RoleAdmin)
|
||||
users.AdminServiceAccountToken = c.CreateServiceAccountToken(users.Admin, users.AdminServiceAccount.Id, users.OrgID, "admin-token", 0)
|
||||
|
||||
users.EditorServiceAccount = c.CreateServiceAccount(users.Admin, "editor-sa", users.Admin.Identity.GetOrgID(), org.RoleEditor)
|
||||
users.EditorServiceAccountToken = c.CreateServiceAccountToken(users.Admin, users.EditorServiceAccount.Id, users.Admin.Identity.GetOrgID(), "editor-token", 0)
|
||||
users.EditorServiceAccount = c.CreateServiceAccount(users.Admin, "editor-sa", users.OrgID, org.RoleEditor)
|
||||
users.EditorServiceAccountToken = c.CreateServiceAccountToken(users.Admin, users.EditorServiceAccount.Id, users.OrgID, "editor-token", 0)
|
||||
|
||||
users.ViewerServiceAccount = c.CreateServiceAccount(users.Admin, "viewer-sa", users.Admin.Identity.GetOrgID(), org.RoleViewer)
|
||||
users.ViewerServiceAccountToken = c.CreateServiceAccountToken(users.Admin, users.ViewerServiceAccount.Id, users.Admin.Identity.GetOrgID(), "viewer-token", 0)
|
||||
users.ViewerServiceAccount = c.CreateServiceAccount(users.Admin, "viewer-sa", users.OrgID, org.RoleViewer)
|
||||
users.ViewerServiceAccountToken = c.CreateServiceAccountToken(users.Admin, users.ViewerServiceAccount.Id, users.OrgID, "viewer-token", 0)
|
||||
|
||||
users.Staff = c.CreateTeam("staff", "staff@"+orgName, users.Admin.Identity.GetOrgID())
|
||||
users.Staff = c.CreateTeam("staff", "staff@"+orgName, users.OrgID)
|
||||
|
||||
// Add Admin and Editor to Staff team as Admin and Member, respectively.
|
||||
c.AddOrUpdateTeamMember(users.Admin, users.Staff.ID, team.PermissionTypeAdmin)
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package preferences
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
preferences "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
|
||||
"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 TestIntegrationPreferences(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: false, // required for experimental APIs
|
||||
DisableAnonymous: true,
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
|
||||
featuremgmt.FlagMultiTenantFrontend, // So we can compare to boot-data preferences
|
||||
},
|
||||
})
|
||||
|
||||
t.Run("legacy preferences api", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
clientAdmin := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
GVR: preferences.PreferencesResourceInfo.GroupVersionResource(),
|
||||
})
|
||||
clientViewer := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Viewer,
|
||||
GVR: preferences.PreferencesResourceInfo.GroupVersionResource(),
|
||||
})
|
||||
|
||||
// List is empty when we start
|
||||
rsp, err := clientAdmin.Resource.List(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, rsp.Items, "no preferences saved yet")
|
||||
|
||||
raw := make(map[string]any)
|
||||
legacyResponse := apis.DoRequest(helper, apis.RequestParams{
|
||||
User: clientAdmin.Args.User,
|
||||
Method: http.MethodPut,
|
||||
Path: "/api/user/preferences",
|
||||
Body: []byte(`{
|
||||
"weekStart": "saturday"
|
||||
}`),
|
||||
}, &raw)
|
||||
require.Equal(t, http.StatusOK, legacyResponse.Response.StatusCode, "create preference for user")
|
||||
|
||||
// http://localhost:3000/api/teams/1/preferences
|
||||
legacyResponse = apis.DoRequest(helper, apis.RequestParams{
|
||||
User: clientAdmin.Args.User,
|
||||
Method: http.MethodPut,
|
||||
Path: fmt.Sprintf("/api/teams/%d/preferences", helper.Org1.Staff.ID),
|
||||
Body: []byte(`{
|
||||
"weekStart": "sunday",
|
||||
"timezone": "africa"
|
||||
}`),
|
||||
}, &raw)
|
||||
require.Equal(t, http.StatusOK, legacyResponse.Response.StatusCode, "create preference for user")
|
||||
|
||||
// http://localhost:3000/api/org/preferences
|
||||
legacyResponse = apis.DoRequest(helper, apis.RequestParams{
|
||||
User: clientAdmin.Args.User,
|
||||
Method: http.MethodPut,
|
||||
Path: "/api/org/preferences",
|
||||
Body: []byte(`{
|
||||
"weekStart": "sunday",
|
||||
"timezone": "africa",
|
||||
"theme": "dark"
|
||||
}`),
|
||||
}, &raw)
|
||||
require.Equal(t, http.StatusOK, legacyResponse.Response.StatusCode, "create preference for user")
|
||||
|
||||
// Admin has access to all three (namespace, team, and user)
|
||||
rsp, err = clientAdmin.Resource.List(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
names := []string{}
|
||||
for _, item := range rsp.Items {
|
||||
names = append(names, item.GetName())
|
||||
}
|
||||
require.Equal(t, []string{
|
||||
"namespace",
|
||||
fmt.Sprintf("team:%s", helper.Org1.Staff.UID),
|
||||
clientAdmin.Args.User.Identity.GetUID(),
|
||||
}, names)
|
||||
|
||||
// The viewer should only have namespace (eg org level) permissions
|
||||
rsp, err = clientViewer.Resource.List(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
names = []string{}
|
||||
for _, item := range rsp.Items {
|
||||
names = append(names, item.GetName())
|
||||
}
|
||||
require.Equal(t, []string{"namespace"}, names)
|
||||
|
||||
// Pull the preferences out of bootdata (many other things are included!)
|
||||
type shim struct {
|
||||
User preferences.PreferencesSpec `json:"user"` // pretend
|
||||
}
|
||||
bootdata := apis.DoRequest(helper, apis.RequestParams{
|
||||
User: clientAdmin.Args.User,
|
||||
Method: http.MethodGet,
|
||||
Path: "/bootdata",
|
||||
}, &shim{})
|
||||
require.Equal(t, http.StatusOK, bootdata.Response.StatusCode, "get bootdata preferences")
|
||||
|
||||
jj, _ := json.Marshal(bootdata.Result.User)
|
||||
require.JSONEq(t, `{
|
||||
"timezone":"africa",
|
||||
"weekStart":"saturday",
|
||||
"theme":"dark",
|
||||
"language":"en-US", `+ // FROM global default!
|
||||
`"regionalFormat":""
|
||||
}`, string(jj))
|
||||
|
||||
current := apis.DoRequest(helper, apis.RequestParams{
|
||||
User: clientAdmin.Args.User,
|
||||
Method: http.MethodGet,
|
||||
Path: "/apis/preferences.grafana.app/v1alpha1/namespaces/default/current",
|
||||
}, &preferences.Preferences{})
|
||||
require.Equal(t, http.StatusOK, current.Response.StatusCode, "get current preferences")
|
||||
require.Equal(t, "saturday", *current.Result.Spec.WeekStart) // from user
|
||||
require.Equal(t, "africa", *current.Result.Spec.Timezone) // from team
|
||||
require.Equal(t, "dark", *current.Result.Spec.Theme) // from org
|
||||
require.Equal(t, "en-US", *current.Result.Spec.Language) // settings.ini
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user