diff --git a/apps/collections/Makefile b/apps/collections/Makefile index bc8d6d30cb5..a8ccff2522f 100644 --- a/apps/collections/Makefile +++ b/apps/collections/Makefile @@ -7,4 +7,4 @@ generate: install-app-sdk update-app-sdk --gogenpath=./pkg/apis \ --grouping=group \ --genoperatorstate=false \ - --defencoding=none \ No newline at end of file + --defencoding=none diff --git a/apps/collections/kinds/datasourcestacks.cue b/apps/collections/kinds/datasourcestacks.cue new file mode 100644 index 00000000000..1c0774be97a --- /dev/null +++ b/apps/collections/kinds/datasourcestacks.cue @@ -0,0 +1,35 @@ +package preferences + +datasourcestacksV1alpha1: { + kind: "Datasources" + pluralName: "Datasource" + scope: "Namespaced" + schema: { + spec: { + template: TemplateSpec + modes: [...Mode] + } + } +} + + +TemplateSpec: { + [string]: DataSourceTemplateSpec +} + +DataSourceTemplateSpec: { + group: string // type + name: string // variable name / display name +} + +Mode: { + name: string + uid: string + definition: ModeSpec +} + +ModeSpec: [string]: DataSourceRef + +DataSourceRef: { + name: string // grafana data source uid +} diff --git a/apps/collections/kinds/manifest.cue b/apps/collections/kinds/manifest.cue index 8052da29431..3b46d9e3cdd 100644 --- a/apps/collections/kinds/manifest.cue +++ b/apps/collections/kinds/manifest.cue @@ -6,12 +6,13 @@ manifest: { versions: { "v1alpha1": { codegen: { - ts: {enabled: false} + ts: {enabled: true} go: {enabled: true} } kinds: [ starsV1alpha1, + datasourcestacksV1alpha1 ] - } + }, } -} \ No newline at end of file +} diff --git a/apps/collections/pkg/apis/collections/v1alpha1/datasources_client_gen.go b/apps/collections/pkg/apis/collections/v1alpha1/datasources_client_gen.go new file mode 100644 index 00000000000..fed8e4c8b42 --- /dev/null +++ b/apps/collections/pkg/apis/collections/v1alpha1/datasources_client_gen.go @@ -0,0 +1,80 @@ +package v1alpha1 + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/resource" +) + +type DatasourcesClient struct { + client *resource.TypedClient[*Datasources, *DatasourcesList] +} + +func NewDatasourcesClient(client resource.Client) *DatasourcesClient { + return &DatasourcesClient{ + client: resource.NewTypedClient[*Datasources, *DatasourcesList](client, DatasourcesKind()), + } +} + +func NewDatasourcesClientFromGenerator(generator resource.ClientGenerator) (*DatasourcesClient, error) { + c, err := generator.ClientFor(DatasourcesKind()) + if err != nil { + return nil, err + } + return NewDatasourcesClient(c), nil +} + +func (c *DatasourcesClient) Get(ctx context.Context, identifier resource.Identifier) (*Datasources, error) { + return c.client.Get(ctx, identifier) +} + +func (c *DatasourcesClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*DatasourcesList, error) { + return c.client.List(ctx, namespace, opts) +} + +func (c *DatasourcesClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*DatasourcesList, error) { + resp, err := c.client.List(ctx, namespace, resource.ListOptions{ + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + for resp.GetContinue() != "" { + page, err := c.client.List(ctx, namespace, resource.ListOptions{ + Continue: resp.GetContinue(), + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + resp.SetContinue(page.GetContinue()) + resp.SetResourceVersion(page.GetResourceVersion()) + resp.SetItems(append(resp.GetItems(), page.GetItems()...)) + } + return resp, nil +} + +func (c *DatasourcesClient) Create(ctx context.Context, obj *Datasources, opts resource.CreateOptions) (*Datasources, error) { + // Make sure apiVersion and kind are set + obj.APIVersion = GroupVersion.Identifier() + obj.Kind = DatasourcesKind().Kind() + return c.client.Create(ctx, obj, opts) +} + +func (c *DatasourcesClient) Update(ctx context.Context, obj *Datasources, opts resource.UpdateOptions) (*Datasources, error) { + return c.client.Update(ctx, obj, opts) +} + +func (c *DatasourcesClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Datasources, error) { + return c.client.Patch(ctx, identifier, req, opts) +} + +func (c *DatasourcesClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + return c.client.Delete(ctx, identifier, opts) +} diff --git a/apps/collections/pkg/apis/collections/v1alpha1/datasources_codec_gen.go b/apps/collections/pkg/apis/collections/v1alpha1/datasources_codec_gen.go new file mode 100644 index 00000000000..30a9e82895f --- /dev/null +++ b/apps/collections/pkg/apis/collections/v1alpha1/datasources_codec_gen.go @@ -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" +) + +// DatasourcesJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type DatasourcesJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*DatasourcesJSONCodec) 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 (*DatasourcesJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &DatasourcesJSONCodec{} diff --git a/apps/collections/pkg/apis/collections/v1alpha1/datasources_metadata_gen.go b/apps/collections/pkg/apis/collections/v1alpha1/datasources_metadata_gen.go new file mode 100644 index 00000000000..b3b8951d3d7 --- /dev/null +++ b/apps/collections/pkg/apis/collections/v1alpha1/datasources_metadata_gen.go @@ -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 DatasourcesMetadata 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"` +} + +// NewDatasourcesMetadata creates a new DatasourcesMetadata object. +func NewDatasourcesMetadata() *DatasourcesMetadata { + return &DatasourcesMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/collections/pkg/apis/collections/v1alpha1/datasources_object_gen.go b/apps/collections/pkg/apis/collections/v1alpha1/datasources_object_gen.go new file mode 100644 index 00000000000..60101bceb19 --- /dev/null +++ b/apps/collections/pkg/apis/collections/v1alpha1/datasources_object_gen.go @@ -0,0 +1,293 @@ +// +// 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 Datasources struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the Datasources + Spec DatasourcesSpec `json:"spec" yaml:"spec"` +} + +func (o *Datasources) GetSpec() any { + return o.Spec +} + +func (o *Datasources) SetSpec(spec any) error { + cast, ok := spec.(DatasourcesSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *Datasources) GetSubresources() map[string]any { + return map[string]any{} +} + +func (o *Datasources) GetSubresource(name string) (any, bool) { + switch name { + default: + return nil, false + } +} + +func (o *Datasources) SetSubresource(name string, value any) error { + switch name { + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *Datasources) 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 *Datasources) 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 *Datasources) 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 *Datasources) 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 *Datasources) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *Datasources) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *Datasources) 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 *Datasources) 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 *Datasources) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *Datasources) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *Datasources) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *Datasources) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *Datasources) DeepCopy() *Datasources { + cpy := &Datasources{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *Datasources) DeepCopyInto(dst *Datasources) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) + o.Spec.DeepCopyInto(&dst.Spec) +} + +// Interface compliance compile-time check +var _ resource.Object = &Datasources{} + +// +k8s:openapi-gen=true +type DatasourcesList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []Datasources `json:"items" yaml:"items"` +} + +func (o *DatasourcesList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *DatasourcesList) Copy() resource.ListObject { + cpy := &DatasourcesList{ + TypeMeta: o.TypeMeta, + Items: make([]Datasources, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*Datasources); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *DatasourcesList) 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 *DatasourcesList) SetItems(items []resource.Object) { + o.Items = make([]Datasources, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*Datasources) + } +} + +func (o *DatasourcesList) DeepCopy() *DatasourcesList { + cpy := &DatasourcesList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *DatasourcesList) DeepCopyInto(dst *DatasourcesList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &DatasourcesList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *DatasourcesSpec) DeepCopy() *DatasourcesSpec { + cpy := &DatasourcesSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *DatasourcesSpec) DeepCopyInto(dst *DatasourcesSpec) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/collections/pkg/apis/collections/v1alpha1/datasources_schema_gen.go b/apps/collections/pkg/apis/collections/v1alpha1/datasources_schema_gen.go new file mode 100644 index 00000000000..4006fac4ed5 --- /dev/null +++ b/apps/collections/pkg/apis/collections/v1alpha1/datasources_schema_gen.go @@ -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 ( + schemaDatasources = resource.NewSimpleSchema("collections.grafana.app", "v1alpha1", &Datasources{}, &DatasourcesList{}, resource.WithKind("Datasources"), + resource.WithPlural("datasource"), resource.WithScope(resource.NamespacedScope)) + kindDatasources = resource.Kind{ + Schema: schemaDatasources, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &DatasourcesJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func DatasourcesKind() resource.Kind { + return kindDatasources +} + +// Schema returns a resource.SimpleSchema representation of Datasources +func DatasourcesSchema() *resource.SimpleSchema { + return schemaDatasources +} + +// Interface compliance checks +var _ resource.Schema = kindDatasources diff --git a/apps/collections/pkg/apis/collections/v1alpha1/datasources_spec_gen.go b/apps/collections/pkg/apis/collections/v1alpha1/datasources_spec_gen.go new file mode 100644 index 00000000000..919e83a1132 --- /dev/null +++ b/apps/collections/pkg/apis/collections/v1alpha1/datasources_spec_gen.go @@ -0,0 +1,58 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v1alpha1 + +// +k8s:openapi-gen=true +type DatasourcesTemplateSpec map[string]DatasourcesDataSourceTemplateSpec + +// +k8s:openapi-gen=true +type DatasourcesDataSourceTemplateSpec struct { + // type + Group string `json:"group"` + // variable name / display name + Name string `json:"name"` +} + +// NewDatasourcesDataSourceTemplateSpec creates a new DatasourcesDataSourceTemplateSpec object. +func NewDatasourcesDataSourceTemplateSpec() *DatasourcesDataSourceTemplateSpec { + return &DatasourcesDataSourceTemplateSpec{} +} + +// +k8s:openapi-gen=true +type DatasourcesMode struct { + Name string `json:"name"` + Uid string `json:"uid"` + Definition DatasourcesModeSpec `json:"definition"` +} + +// NewDatasourcesMode creates a new DatasourcesMode object. +func NewDatasourcesMode() *DatasourcesMode { + return &DatasourcesMode{} +} + +// +k8s:openapi-gen=true +type DatasourcesModeSpec map[string]DatasourcesDataSourceRef + +// +k8s:openapi-gen=true +type DatasourcesDataSourceRef struct { + // grafana data source uid + Name string `json:"name"` +} + +// NewDatasourcesDataSourceRef creates a new DatasourcesDataSourceRef object. +func NewDatasourcesDataSourceRef() *DatasourcesDataSourceRef { + return &DatasourcesDataSourceRef{} +} + +// +k8s:openapi-gen=true +type DatasourcesSpec struct { + Template DatasourcesTemplateSpec `json:"template"` + Modes []DatasourcesMode `json:"modes"` +} + +// NewDatasourcesSpec creates a new DatasourcesSpec object. +func NewDatasourcesSpec() *DatasourcesSpec { + return &DatasourcesSpec{ + Modes: []DatasourcesMode{}, + } +} diff --git a/apps/collections/pkg/apis/collections/v1alpha1/register.go b/apps/collections/pkg/apis/collections/v1alpha1/register.go index 6b72ab94146..4660e775648 100644 --- a/apps/collections/pkg/apis/collections/v1alpha1/register.go +++ b/apps/collections/pkg/apis/collections/v1alpha1/register.go @@ -32,6 +32,19 @@ var StarsResourceInfo = utils.NewResourceInfo(APIGroup, APIVersion, }, ) +var DatasourcesResourceInfo = utils.NewResourceInfo(APIGroup, APIVersion, + "datasources", "datasource", "Datasources", + func() runtime.Object { return &Datasources{} }, + func() runtime.Object { return &DatasourcesList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Created At", Type: "date"}, + }, + // TODO: Reader? + }, +) + var ( SchemeBuilder runtime.SchemeBuilder localSchemeBuilder = &SchemeBuilder @@ -48,6 +61,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(schemeGroupVersion, &Stars{}, &StarsList{}, + &Datasources{}, + &DatasourcesList{}, ) metav1.AddToGroupVersion(scheme, schemeGroupVersion) return nil diff --git a/apps/collections/pkg/apis/collections/v1alpha1/zz_generated.openapi.go b/apps/collections/pkg/apis/collections/v1alpha1/zz_generated.openapi.go index 5b486b690c0..4c9ec13bef8 100644 --- a/apps/collections/pkg/apis/collections/v1alpha1/zz_generated.openapi.go +++ b/apps/collections/pkg/apis/collections/v1alpha1/zz_generated.openapi.go @@ -14,10 +14,241 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.Stars": schema_pkg_apis_collections_v1alpha1_Stars(ref), - "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.StarsList": schema_pkg_apis_collections_v1alpha1_StarsList(ref), - "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.StarsResource": schema_pkg_apis_collections_v1alpha1_StarsResource(ref), - "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.StarsSpec": schema_pkg_apis_collections_v1alpha1_StarsSpec(ref), + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.Datasources": schema_pkg_apis_collections_v1alpha1_Datasources(ref), + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.DatasourcesDataSourceRef": schema_pkg_apis_collections_v1alpha1_DatasourcesDataSourceRef(ref), + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.DatasourcesDataSourceTemplateSpec": schema_pkg_apis_collections_v1alpha1_DatasourcesDataSourceTemplateSpec(ref), + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.DatasourcesList": schema_pkg_apis_collections_v1alpha1_DatasourcesList(ref), + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.DatasourcesMode": schema_pkg_apis_collections_v1alpha1_DatasourcesMode(ref), + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.DatasourcesSpec": schema_pkg_apis_collections_v1alpha1_DatasourcesSpec(ref), + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.Stars": schema_pkg_apis_collections_v1alpha1_Stars(ref), + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.StarsList": schema_pkg_apis_collections_v1alpha1_StarsList(ref), + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.StarsResource": schema_pkg_apis_collections_v1alpha1_StarsResource(ref), + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.StarsSpec": schema_pkg_apis_collections_v1alpha1_StarsSpec(ref), + } +} + +func schema_pkg_apis_collections_v1alpha1_Datasources(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 Datasources", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.DatasourcesSpec"), + }, + }, + }, + Required: []string{"metadata", "spec"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.DatasourcesSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_collections_v1alpha1_DatasourcesDataSourceRef(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "grafana data source uid", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_pkg_apis_collections_v1alpha1_DatasourcesDataSourceTemplateSpec(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{ + Description: "type", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "variable name / display name", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "name"}, + }, + }, + } +} + +func schema_pkg_apis_collections_v1alpha1_DatasourcesList(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/collections/pkg/apis/collections/v1alpha1.Datasources"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.Datasources", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_collections_v1alpha1_DatasourcesMode(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "definition": { + SchemaProps: spec.SchemaProps{ + 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/collections/pkg/apis/collections/v1alpha1.DatasourcesDataSourceRef"), + }, + }, + }, + }, + }, + }, + Required: []string{"name", "uid", "definition"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.DatasourcesDataSourceRef"}, + } +} + +func schema_pkg_apis_collections_v1alpha1_DatasourcesSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "template": { + SchemaProps: spec.SchemaProps{ + 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/collections/pkg/apis/collections/v1alpha1.DatasourcesDataSourceTemplateSpec"), + }, + }, + }, + }, + }, + "modes": { + 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/collections/pkg/apis/collections/v1alpha1.DatasourcesMode"), + }, + }, + }, + }, + }, + }, + Required: []string{"template", "modes"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.DatasourcesDataSourceTemplateSpec", "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1.DatasourcesMode"}, } } diff --git a/apps/collections/pkg/apis/collections/v1alpha1/zz_generated.openapi_violation_exceptions.list b/apps/collections/pkg/apis/collections/v1alpha1/zz_generated.openapi_violation_exceptions.list index efaa822c24f..242ff475a96 100644 --- a/apps/collections/pkg/apis/collections/v1alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/collections/pkg/apis/collections/v1alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,2 +1,4 @@ +API rule violation: list_type_missing,github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1,DatasourcesSpec,Modes API rule violation: list_type_missing,github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1,StarsSpec,Resource +API rule violation: streaming_list_type_json_tags,github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1,DatasourcesList,ListMeta API rule violation: streaming_list_type_json_tags,github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1,StarsList,ListMeta diff --git a/apps/collections/pkg/apis/collections_manifest.go b/apps/collections/pkg/apis/collections_manifest.go index 6c3f3cfe930..df9a9a0084d 100644 --- a/apps/collections/pkg/apis/collections_manifest.go +++ b/apps/collections/pkg/apis/collections_manifest.go @@ -10,19 +10,22 @@ import ( "fmt" "strings" + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kube-openapi/pkg/spec3" "k8s.io/kube-openapi/pkg/validation/spec" - "github.com/grafana/grafana-app-sdk/app" - "github.com/grafana/grafana-app-sdk/resource" - "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1" + v1alpha1 "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1" ) var ( - rawSchemaStarsv1alpha1 = []byte(`{"Resource":{"additionalProperties":false,"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"},"Stars":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"resource":{"items":{"$ref":"#/components/schemas/Resource"},"type":"array"}},"required":["resource"],"type":"object"}}`) - versionSchemaStarsv1alpha1 app.VersionSchema - _ = json.Unmarshal(rawSchemaStarsv1alpha1, &versionSchemaStarsv1alpha1) + rawSchemaStarsv1alpha1 = []byte(`{"Resource":{"additionalProperties":false,"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"},"Stars":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"resource":{"items":{"$ref":"#/components/schemas/Resource"},"type":"array"}},"required":["resource"],"type":"object"}}`) + versionSchemaStarsv1alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaStarsv1alpha1, &versionSchemaStarsv1alpha1) + rawSchemaDatasourcesv1alpha1 = []byte(`{"DataSourceRef":{"additionalProperties":false,"properties":{"name":{"description":"grafana data source uid","type":"string"}},"required":["name"],"type":"object"},"DataSourceTemplateSpec":{"additionalProperties":false,"properties":{"group":{"description":"type","type":"string"},"name":{"description":"variable name / display name","type":"string"}},"required":["group","name"],"type":"object"},"Datasources":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"Mode":{"additionalProperties":false,"properties":{"definition":{"$ref":"#/components/schemas/ModeSpec"},"name":{"type":"string"},"uid":{"type":"string"}},"required":["name","uid","definition"],"type":"object"},"ModeSpec":{"additionalProperties":{"$ref":"#/components/schemas/DataSourceRef"},"type":"object"},"TemplateSpec":{"additionalProperties":{"$ref":"#/components/schemas/DataSourceTemplateSpec"},"type":"object"},"spec":{"additionalProperties":false,"properties":{"modes":{"items":{"$ref":"#/components/schemas/Mode"},"type":"array"},"template":{"$ref":"#/components/schemas/TemplateSpec"}},"required":["template","modes"],"type":"object"}}`) + versionSchemaDatasourcesv1alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaDatasourcesv1alpha1, &versionSchemaDatasourcesv1alpha1) ) var appManifestData = app.ManifestData{ @@ -49,6 +52,14 @@ var appManifestData = app.ManifestData{ }, Schema: &versionSchemaStarsv1alpha1, }, + + { + Kind: "Datasources", + Plural: "Datasource", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaDatasourcesv1alpha1, + }, }, Routes: app.ManifestVersionRoutes{ Namespaced: map[string]spec3.PathProps{}, @@ -68,7 +79,8 @@ func RemoteManifest() app.Manifest { } var kindVersionToGoType = map[string]resource.Kind{ - "Stars/v1alpha1": v1alpha1.StarsKind(), + "Stars/v1alpha1": v1alpha1.StarsKind(), + "Datasources/v1alpha1": v1alpha1.DatasourcesKind(), } // ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. diff --git a/apps/collections/plugin/src/generated/datasources/v1alpha1/datasources_object_gen.ts b/apps/collections/plugin/src/generated/datasources/v1alpha1/datasources_object_gen.ts new file mode 100644 index 00000000000..1d556466dfd --- /dev/null +++ b/apps/collections/plugin/src/generated/datasources/v1alpha1/datasources_object_gen.ts @@ -0,0 +1,47 @@ +/* + * This file was generated by grafana-app-sdk. DO NOT EDIT. + */ +import { Spec } from './types.spec.gen'; + +export interface Metadata { + name: string; + namespace: string; + generateName?: string; + selfLink?: string; + uid?: string; + resourceVersion?: string; + generation?: number; + creationTimestamp?: string; + deletionTimestamp?: string; + deletionGracePeriodSeconds?: number; + labels?: Record; + annotations?: Record; + ownerReferences?: OwnerReference[]; + finalizers?: string[]; + managedFields?: ManagedFieldsEntry[]; +} + +export interface OwnerReference { + apiVersion: string; + kind: string; + name: string; + uid: string; + controller?: boolean; + blockOwnerDeletion?: boolean; +} + +export interface ManagedFieldsEntry { + manager?: string; + operation?: string; + apiVersion?: string; + time?: string; + fieldsType?: string; + subresource?: string; +} + +export interface Datasources { + kind: string; + apiVersion: string; + metadata: Metadata; + spec: Spec; +} diff --git a/apps/collections/plugin/src/generated/datasources/v1alpha1/types.metadata.gen.ts b/apps/collections/plugin/src/generated/datasources/v1alpha1/types.metadata.gen.ts new file mode 100644 index 00000000000..4377f3c1d08 --- /dev/null +++ b/apps/collections/plugin/src/generated/datasources/v1alpha1/types.metadata.gen.ts @@ -0,0 +1,30 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +// 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. +export interface Metadata { + updateTimestamp: string; + createdBy: string; + uid: string; + creationTimestamp: string; + deletionTimestamp?: string; + finalizers: string[]; + resourceVersion: string; + generation: number; + updatedBy: string; + labels: Record; +} + +export const defaultMetadata = (): Metadata => ({ + updateTimestamp: "", + createdBy: "", + uid: "", + creationTimestamp: "", + finalizers: [], + resourceVersion: "", + generation: 0, + updatedBy: "", + labels: {}, +}); + diff --git a/apps/collections/plugin/src/generated/datasources/v1alpha1/types.spec.gen.ts b/apps/collections/plugin/src/generated/datasources/v1alpha1/types.spec.gen.ts new file mode 100644 index 00000000000..10d3d0e5000 --- /dev/null +++ b/apps/collections/plugin/src/generated/datasources/v1alpha1/types.spec.gen.ts @@ -0,0 +1,53 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +export type TemplateSpec = Record; + +export const defaultTemplateSpec = (): TemplateSpec => ({}); + +export interface DataSourceTemplateSpec { + // type + group: string; + // variable name / display name + name: string; +} + +export const defaultDataSourceTemplateSpec = (): DataSourceTemplateSpec => ({ + group: "", + name: "", +}); + +export interface Mode { + name: string; + uid: string; + definition: ModeSpec; +} + +export const defaultMode = (): Mode => ({ + name: "", + uid: "", + definition: defaultModeSpec(), +}); + +export type ModeSpec = Record; + +export const defaultModeSpec = (): ModeSpec => ({}); + +export interface DataSourceRef { + // grafana data source uid + name: string; +} + +export const defaultDataSourceRef = (): DataSourceRef => ({ + name: "", +}); + +export interface Spec { + template: TemplateSpec; + modes: Mode[]; +} + +export const defaultSpec = (): Spec => ({ + template: defaultTemplateSpec(), + modes: [], +}); + diff --git a/apps/collections/plugin/src/generated/stars/v1alpha1/stars_object_gen.ts b/apps/collections/plugin/src/generated/stars/v1alpha1/stars_object_gen.ts new file mode 100644 index 00000000000..09e81ec55bd --- /dev/null +++ b/apps/collections/plugin/src/generated/stars/v1alpha1/stars_object_gen.ts @@ -0,0 +1,47 @@ +/* + * This file was generated by grafana-app-sdk. DO NOT EDIT. + */ +import { Spec } from './types.spec.gen'; + +export interface Metadata { + name: string; + namespace: string; + generateName?: string; + selfLink?: string; + uid?: string; + resourceVersion?: string; + generation?: number; + creationTimestamp?: string; + deletionTimestamp?: string; + deletionGracePeriodSeconds?: number; + labels?: Record; + annotations?: Record; + ownerReferences?: OwnerReference[]; + finalizers?: string[]; + managedFields?: ManagedFieldsEntry[]; +} + +export interface OwnerReference { + apiVersion: string; + kind: string; + name: string; + uid: string; + controller?: boolean; + blockOwnerDeletion?: boolean; +} + +export interface ManagedFieldsEntry { + manager?: string; + operation?: string; + apiVersion?: string; + time?: string; + fieldsType?: string; + subresource?: string; +} + +export interface Stars { + kind: string; + apiVersion: string; + metadata: Metadata; + spec: Spec; +} diff --git a/apps/collections/plugin/src/generated/stars/v1alpha1/types.metadata.gen.ts b/apps/collections/plugin/src/generated/stars/v1alpha1/types.metadata.gen.ts new file mode 100644 index 00000000000..4377f3c1d08 --- /dev/null +++ b/apps/collections/plugin/src/generated/stars/v1alpha1/types.metadata.gen.ts @@ -0,0 +1,30 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +// 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. +export interface Metadata { + updateTimestamp: string; + createdBy: string; + uid: string; + creationTimestamp: string; + deletionTimestamp?: string; + finalizers: string[]; + resourceVersion: string; + generation: number; + updatedBy: string; + labels: Record; +} + +export const defaultMetadata = (): Metadata => ({ + updateTimestamp: "", + createdBy: "", + uid: "", + creationTimestamp: "", + finalizers: [], + resourceVersion: "", + generation: 0, + updatedBy: "", + labels: {}, +}); + diff --git a/apps/collections/plugin/src/generated/stars/v1alpha1/types.spec.gen.ts b/apps/collections/plugin/src/generated/stars/v1alpha1/types.spec.gen.ts new file mode 100644 index 00000000000..d750253ad57 --- /dev/null +++ b/apps/collections/plugin/src/generated/stars/v1alpha1/types.spec.gen.ts @@ -0,0 +1,24 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +export interface Resource { + group: string; + kind: string; + // The set of resources + // +listType=set + names: string[]; +} + +export const defaultResource = (): Resource => ({ + group: "", + kind: "", + names: [], +}); + +export interface Spec { + resource: Resource[]; +} + +export const defaultSpec = (): Spec => ({ + resource: [], +}); + diff --git a/pkg/registry/apis/collections/datasources.go b/pkg/registry/apis/collections/datasources.go new file mode 100644 index 00000000000..f57cf6d7ab4 --- /dev/null +++ b/pkg/registry/apis/collections/datasources.go @@ -0,0 +1,47 @@ +package collections + +import ( + "context" + + authlib "github.com/grafana/authlib/types" + collections "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" + grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +var _ grafanarest.Storage = (*datasourceStorage)(nil) + +type datasourceStorage struct { + grafanarest.Storage +} + +// When using list, we really just want to get the value for the single user +func (s *datasourceStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + user, err := identity.GetRequester(ctx) + if err != nil { + return nil, err + } + + switch user.GetIdentityType() { + case authlib.TypeAnonymous: + return s.NewList(), nil + + // Get the single user stars + case authlib.TypeUser: + datasources := &collections.DatasourcesList{} + obj, _ := s.Get(ctx, "user-"+user.GetIdentifier(), &v1.GetOptions{}) + if obj != nil { + d, ok := obj.(*collections.Datasources) + if ok { + datasources.Items = []collections.Datasources{*d} + } + } + return datasources, nil + + default: + return s.Storage.List(ctx, options) + } +} diff --git a/pkg/registry/apis/collections/register.go b/pkg/registry/apis/collections/register.go index 9be4412c377..2dcb92388bd 100644 --- a/pkg/registry/apis/collections/register.go +++ b/pkg/registry/apis/collections/register.go @@ -94,21 +94,31 @@ func (b *APIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupI storage := map[string]rest.Storage{} // Configure Stars Dual writer - resource := collections.StarsResourceInfo + starsResource := collections.StarsResourceInfo var stars grafanarest.Storage - stars, err := grafanaregistry.NewRegistryStore(opts.Scheme, resource, opts.OptsGetter) + stars, err := grafanaregistry.NewRegistryStore(opts.Scheme, starsResource, opts.OptsGetter) if err != nil { return err } stars = &starStorage{Storage: stars} // wrap List so we only return one value if b.legacyStars != nil && opts.DualWriteBuilder != nil { - stars, err = opts.DualWriteBuilder(resource.GroupResource(), b.legacyStars, stars) + stars, err = opts.DualWriteBuilder(starsResource.GroupResource(), b.legacyStars, stars) if err != nil { return err } } - storage[resource.StoragePath()] = stars - storage[resource.StoragePath("update")] = &starsREST{store: stars} + storage[starsResource.StoragePath()] = stars + storage[starsResource.StoragePath("update")] = &starsREST{store: stars} + + // Configure Datasources dual writer + datasourcesResource := collections.DatasourcesResourceInfo + var datasources grafanarest.Storage + datasources, err = grafanaregistry.NewRegistryStore(opts.Scheme, datasourcesResource, opts.OptsGetter) + if err != nil { + return err + } + datasources = &datasourceStorage{Storage: datasources} // wrap List so we only return one value + storage[datasourcesResource.StoragePath()] = datasources apiGroupInfo.VersionedResourcesStorageMap[collections.APIVersion] = storage return nil