Code Diff
diff --git a/apps/dashboard/Makefile b/apps/dashboard/Makefile
index fe35c4971469..e176303e8416 100644
--- a/apps/dashboard/Makefile
+++ b/apps/dashboard/Makefile
@@ -22,6 +22,10 @@ post-generate-cleanup: ## Clean up the generated code
@rm ../../packages/grafana-schema/src/schema/dashboard/v1beta1/types.spec.gen.ts
@cp ./tshack/v1alpha1_spec_gen.ts ../../packages/grafana-schema/src/schema/dashboard/v1beta1/types.spec.gen.ts
+ # Variable spec currently requires a TS shim to expose Spec/defaultSpec.
+ @rm -f ../../packages/grafana-schema/src/schema/variable/v2beta1/types.spec.gen.ts
+ @cp ./tshack/variable_v2beta1_spec_gen.ts ../../packages/grafana-schema/src/schema/variable/v2beta1/types.spec.gen.ts
+
# Remove auto-generated DeepCopy and DeepCopyInto methods for Spec for v0alpha1.
@sed -e '/\/\/ DeepCopy creates a full deep copy of Spec/,+5d' ./pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go > ./pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go.tmp
@sed -e '/\/\/ DeepCopyInto deep copies Spec into another Spec object/,+3d' ./pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go.tmp > ./pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go.tmp2
@@ -61,6 +65,9 @@ post-generate-cleanup: ## Clean up the generated code
@echo "" >> ./pkg/apis/dashboard/v2/dashboard_spec.cue
@cat ./kinds/v2/dashboard_spec.cue >> ./pkg/apis/dashboard/v2/dashboard_spec.cue
+ # Temporary workaround until grafana-app-sdk supports selectableFields through union parent paths.
+ @bash ./cuehack/patch_variable_selectable_fields.sh ./pkg/apis/dashboard_manifest.go
+
@# Remove generated files for v1beta1 since it's a thin wrapper around v1
@rm -f ./pkg/apis/dashboard/v1beta1/dashboard_codec_gen.go
@rm -f ./pkg/apis/dashboard/v1beta1/dashboard_object_gen.go
diff --git a/apps/dashboard/cuehack/patch_variable_selectable_fields.sh b/apps/dashboard/cuehack/patch_variable_selectable_fields.sh
new file mode 100644
index 000000000000..80936dd869b5
--- /dev/null
+++ b/apps/dashboard/cuehack/patch_variable_selectable_fields.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+manifest_path="${1:-./pkg/apis/dashboard_manifest.go}"
+
+tmp_file="$(mktemp)"
+trap 'rm -f "$tmp_file"' EXIT
+
+awk '
+BEGIN { patched = 0 }
+{
+ print $0
+
+ if (!patched && $0 ~ /Schema: &versionSchemaVariablev2beta1,/) {
+ if (getline nextline > 0) {
+ if (nextline ~ /^[[:space:]]*SelectableFields: \[\]string\{/) {
+ print nextline
+ } else {
+ print "\t\t\t\t\tSelectableFields: []string{"
+ print "\t\t\t\t\t\t\"spec.spec.name\","
+ print "\t\t\t\t\t},"
+ print nextline
+ }
+ patched = 1
+ }
+ }
+}
+' "$manifest_path" > "$tmp_file"
+
+mv "$tmp_file" "$manifest_path"
diff --git a/apps/dashboard/kinds/globalvariable.cue b/apps/dashboard/kinds/globalvariable.cue
new file mode 100644
index 000000000000..55634fb0c0f3
--- /dev/null
+++ b/apps/dashboard/kinds/globalvariable.cue
@@ -0,0 +1,27 @@
+package kinds
+
+import (
+ v2beta1 "github.com/grafana/grafana/sdkkinds/dashboard/v2beta1"
+)
+
+globalVariableV2beta1: {
+ kind: "Variable"
+ pluralName: "Variables"
+ //TODO:
+ // Adding selectableFields here causes the codegen to fail because it's a union parent path.
+ // Until grafana-app-sdk supports selectableFields through union parent paths,
+ // we're not adding it here and patching the manifest.go file instead with
+ // apps/dashboard/cuehack/patch_variable_selectable_fields.sh
+ // selectableFields: [
+ // "spec.spec.name",
+ // ]
+ validation: {
+ operations: ["CREATE", "UPDATE"]
+ }
+ mutation: {
+ operations: ["CREATE", "UPDATE"]
+ }
+ schema: {
+ spec: v2beta1.VariableKind
+ }
+}
diff --git a/apps/dashboard/kinds/manifest.cue b/apps/dashboard/kinds/manifest.cue
index ff2567f4fa7e..1d592caf4a80 100644
--- a/apps/dashboard/kinds/manifest.cue
+++ b/apps/dashboard/kinds/manifest.cue
@@ -103,6 +103,7 @@ manifest: {
status: DashboardStatus
}
},
+ globalVariableV2beta1,
]
}
"v2": {
diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/register.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/register.go
index 29dfb0a3769b..ed7f6b270e50 100644
--- a/apps/dashboard/pkg/apis/dashboard/v2beta1/register.go
+++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/register.go
@@ -43,6 +43,32 @@ var DashboardResourceInfo = utils.NewResourceInfo(GROUP, VERSION,
},
)
+var VariableResourceInfo = utils.NewResourceInfo(GROUP, VERSION,
+ "variables", "variable", "Variable",
+ func() runtime.Object { return &Variable{} },
+ func() runtime.Object { return &VariableList{} },
+ utils.TableColumns{
+ Definition: []metav1.TableColumnDefinition{
+ {Name: "Name", Type: "string", Format: "name"},
+ {Name: "Variable Kind", Type: "string", Format: "string", Description: "The global variable type"},
+ {Name: "Created At", Type: "date"},
+ },
+ Reader: func(obj any) ([]interface{}, error) {
+ variable, ok := obj.(*Variable)
+ if ok {
+ if variable != nil {
+ return []interface{}{
+ variable.Name,
+ getVariableKindName(variable.Spec),
+ variable.CreationTimestamp.UTC().Format(time.RFC3339),
+ }, nil
+ }
+ }
+ return nil, fmt.Errorf("expected variable")
+ },
+ },
+)
+
var (
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
@@ -60,6 +86,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&Dashboard{},
&DashboardList{},
&DashboardWithAccessInfo{},
+ &Variable{},
+ &VariableList{},
&metav1.PartialObjectMetadata{},
&metav1.PartialObjectMetadataList{},
)
@@ -67,6 +95,31 @@ func addKnownTypes(scheme *runtime.Scheme) error {
return nil
}
+func getVariableKindName(spec VariableSpec) string {
+ switch {
+ case spec.QueryVariableKind != nil:
+ return spec.QueryVariableKind.Kind
+ case spec.TextVariableKind != nil:
+ return spec.TextVariableKind.Kind
+ case spec.ConstantVariableKind != nil:
+ return spec.ConstantVariableKind.Kind
+ case spec.DatasourceVariableKind != nil:
+ return spec.DatasourceVariableKind.Kind
+ case spec.IntervalVariableKind != nil:
+ return spec.IntervalVariableKind.Kind
+ case spec.CustomVariableKind != nil:
+ return spec.CustomVariableKind.Kind
+ case spec.GroupByVariableKind != nil:
+ return spec.GroupByVariableKind.Kind
+ case spec.AdhocVariableKind != nil:
+ return spec.AdhocVariableKind.Kind
+ case spec.SwitchVariableKind != nil:
+ return spec.SwitchVariableKind.Kind
+ default:
+ return ""
+ }
+}
+
func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme)
}
diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/variable_client_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/variable_client_gen.go
new file mode 100644
index 000000000000..139f42198512
--- /dev/null
+++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/variable_client_gen.go
@@ -0,0 +1,80 @@
+package v2beta1
+
+import (
+ "context"
+
+ "github.com/grafana/grafana-app-sdk/resource"
+)
+
+type VariableClient struct {
+ client *resource.TypedClient[*Variable, *VariableList]
+}
+
+func NewVariableClient(client resource.Client) *VariableClient {
+ return &VariableClient{
+ client: resource.NewTypedClient[*Variable, *VariableList](client, VariableKind()),
+ }
+}
+
+func NewVariableClientFromGenerator(generator resource.ClientGenerator) (*VariableClient, error) {
+ c, err := generator.ClientFor(VariableKind())
+ if err != nil {
+ return nil, err
+ }
+ return NewVariableClient(c), nil
+}
+
+func (c *VariableClient) Get(ctx context.Context, identifier resource.Identifier) (*Variable, error) {
+ return c.client.Get(ctx, identifier)
+}
+
+func (c *VariableClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*VariableList, error) {
+ return c.client.List(ctx, namespace, opts)
+}
+
+func (c *VariableClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*VariableList, 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 *VariableClient) Create(ctx context.Context, obj *Variable, opts resource.CreateOptions) (*Variable, error) {
+ // Make sure apiVersion and kind are set
+ obj.APIVersion = GroupVersion.Identifier()
+ obj.Kind = VariableKind().Kind()
+ return c.client.Create(ctx, obj, opts)
+}
+
+func (c *VariableClient) Update(ctx context.Context, obj *Variable, opts resource.UpdateOptions) (*Variable, error) {
+ return c.client.Update(ctx, obj, opts)
+}
+
+func (c *VariableClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Variable, error) {
+ return c.client.Patch(ctx, identifier, req, opts)
+}
+
+func (c *VariableClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
+ return c.client.Delete(ctx, identifier, opts)
+}
diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/variable_codec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/variable_codec_gen.go
new file mode 100644
index 000000000000..e2427b83bd8b
--- /dev/null
+++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/variable_codec_gen.go
@@ -0,0 +1,28 @@
+//
+// Code generated by grafana-app-sdk. DO NOT EDIT.
+//
+
+package v2beta1
+
+import (
+ "encoding/json"
+ "io"
+
+ "github.com/grafana/grafana-app-sdk/resource"
+)
+
+// VariableJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
+type VariableJSONCodec struct{}
+
+// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
+func (*VariableJSONCodec) 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 (*VariableJSONCodec) Write(writer io.Writer, from resource.Object) error {
+ return json.NewEncoder(writer).Encode(from)
+}
+
+// Interface compliance checks
+var _ resource.Codec = &VariableJSONCodec{}
diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/variable_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/variable_object_gen.go
new file mode 100644
index 000000000000..d514248775c2
--- /dev/null
+++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/variable_object_gen.go
@@ -0,0 +1,307 @@
+//
+// Code generated by grafana-app-sdk. DO NOT EDIT.
+//
+
+package v2beta1
+
+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 Variable struct {
+ metav1.TypeMeta `json:",inline" yaml:",inline"`
+ metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
+
+ // Spec is the spec of the Variable
+ Spec VariableSpec `json:"spec" yaml:"spec"`
+}
+
+func NewVariable() *Variable {
+ return &Variable{
+ Spec: *NewVariableSpec(),
+ }
+}
+
+func (o *Variable) GetSpec() any {
+ return o.Spec
+}
+
+func (o *Variable) SetSpec(spec any) error {
+ cast, ok := spec.(VariableSpec)
+ if !ok {
+ return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
+ }
+ o.Spec = cast
+ return nil
+}
+
+func (o *Variable) GetSubresources() map[string]any {
+ return map[string]any{}
+}
+
+func (o *Variable) GetSubresource(name string) (any, bool) {
+ switch name {
+ default:
+ return nil, false
+ }
+}
+
+func (o *Variable) SetSubresource(name string, value any) error {
+ switch name {
+ default:
+ return fmt.Errorf("subresource '%s' does not exist", name)
+ }
+}
+
+func (o *Variable) 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 *Variable) 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 *Variable) 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 *Variable) 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 := man
... [truncated]