mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Scuemata: Add grafana-cli command to validate basic scuemata (#33523)
* Add grafana-cli command to validate basic scuemata * Fix c/p outdated message * Fix linting - naming * Add basic testing * Add cue schema validation * Add tests * Fix linting errors * Remove code - refactored tests * Remove unnecessary files - leftovers * Fix linting * Try adding public folder in testdata
This commit is contained in:
parent
03e91eb1a6
commit
47af158ddb
@ -134,6 +134,14 @@ var adminCommands = []*cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cueCommands = []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "validate-schema",
|
||||||
|
Usage: "validate *.cue files in the project",
|
||||||
|
Action: runPluginCommand(cmd.validateScuemataBasics),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var Commands = []*cli.Command{
|
var Commands = []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: "plugins",
|
Name: "plugins",
|
||||||
@ -145,4 +153,9 @@ var Commands = []*cli.Command{
|
|||||||
Usage: "Grafana admin commands",
|
Usage: "Grafana admin commands",
|
||||||
Subcommands: adminCommands,
|
Subcommands: adminCommands,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "cue",
|
||||||
|
Usage: "Cue validation commands",
|
||||||
|
Subcommands: cueCommands,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
43
pkg/cmd/grafana-cli/commands/scuemata_validation_command.go
Normal file
43
pkg/cmd/grafana-cli/commands/scuemata_validation_command.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
|
"github.com/grafana/grafana/pkg/schema"
|
||||||
|
"github.com/grafana/grafana/pkg/schema/load"
|
||||||
|
)
|
||||||
|
|
||||||
|
var paths = load.GetDefaultLoadPaths()
|
||||||
|
|
||||||
|
func (cmd Command) validateScuemataBasics(c utils.CommandLine) error {
|
||||||
|
if err := validate(paths, load.BaseDashboardFamily); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validate(paths, load.DistDashboardFamily); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(p load.BaseLoadPaths, loader func(p load.BaseLoadPaths) (schema.VersionedCueSchema, error)) error {
|
||||||
|
dash, err := loader(p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error while loading dashboard scuemata, err: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that a CUE value exists.
|
||||||
|
cueValue := dash.CUE()
|
||||||
|
if !cueValue.Exists() {
|
||||||
|
return fmt.Errorf("cue value for schema does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check CUE validity.
|
||||||
|
if err := cueValue.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("all schema should be valid with respect to basic CUE rules, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/schema/load"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed testdata/public/*/*.cue testdata/public/*/plugin.json
|
||||||
|
var base embed.FS
|
||||||
|
var pluginSchema, _ = fs.Sub(base, "testdata/public")
|
||||||
|
|
||||||
|
func TestValidateScuemataBasics(t *testing.T) {
|
||||||
|
t.Run("Testing scuemata validity with valid cue schemas", func(t *testing.T) {
|
||||||
|
tempDir := os.DirFS(filepath.Join("testdata", "valid_scuemata"))
|
||||||
|
|
||||||
|
var baseLoadPaths = load.BaseLoadPaths{
|
||||||
|
BaseCueFS: tempDir,
|
||||||
|
DistPluginCueFS: pluginSchema,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validate(baseLoadPaths, load.BaseDashboardFamily)
|
||||||
|
require.NoError(t, err, "error while loading base dashboard scuemata")
|
||||||
|
|
||||||
|
err = validate(baseLoadPaths, load.DistDashboardFamily)
|
||||||
|
require.NoError(t, err, "error while loading dist dashboard scuemata")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Testing scuemata validity with invalid cue schemas - family missing", func(t *testing.T) {
|
||||||
|
tempDir := os.DirFS(filepath.Join("testdata", "invalid_scuemata_missing_family"))
|
||||||
|
|
||||||
|
var baseLoadPaths = load.BaseLoadPaths{
|
||||||
|
BaseCueFS: tempDir,
|
||||||
|
DistPluginCueFS: pluginSchema,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validate(baseLoadPaths, load.BaseDashboardFamily)
|
||||||
|
assert.EqualError(t, err, "error while loading dashboard scuemata, err: dashboard schema family did not exist at expected path in expected file")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Testing scuemata validity with invalid cue schemas - panel missing", func(t *testing.T) {
|
||||||
|
tempDir := os.DirFS(filepath.Join("testdata", "invalid_scuemata_missing_panel"))
|
||||||
|
|
||||||
|
var baseLoadPaths = load.BaseLoadPaths{
|
||||||
|
BaseCueFS: tempDir,
|
||||||
|
DistPluginCueFS: pluginSchema,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validate(baseLoadPaths, load.BaseDashboardFamily)
|
||||||
|
require.NoError(t, err, "error while loading base dashboard scuemata")
|
||||||
|
|
||||||
|
err = validate(baseLoadPaths, load.DistDashboardFamily)
|
||||||
|
assert.EqualError(t, err, "all schema should be valid with respect to basic CUE rules, Family.lineages.0.0: field #Panel not allowed")
|
||||||
|
})
|
||||||
|
}
|
83
pkg/cmd/grafana-cli/commands/testdata/invalid_scuemata_missing_family/cue/data/gen.cue
vendored
Normal file
83
pkg/cmd/grafana-cli/commands/testdata/invalid_scuemata_missing_family/cue/data/gen.cue
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package grafanaschema
|
||||||
|
|
||||||
|
import "github.com/grafana/grafana/cue/scuemata"
|
||||||
|
|
||||||
|
Dummy: scuemata.#Family & {
|
||||||
|
lineages: [
|
||||||
|
[
|
||||||
|
{ // 0.0
|
||||||
|
// Unique numeric identifier for the dashboard.
|
||||||
|
// TODO must isolate or remove identifiers local to a Grafana instance...?
|
||||||
|
id?: number
|
||||||
|
// Unique dashboard identifier that can be generated by anyone. string (8-40)
|
||||||
|
uid: string
|
||||||
|
// Title of dashboard.
|
||||||
|
title?: string
|
||||||
|
// Description of dashboard.
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
gnetId?: string
|
||||||
|
// Tags associated with dashboard.
|
||||||
|
tags?: [...string]
|
||||||
|
// Theme of dashboard.
|
||||||
|
style: *"light" | "dark"
|
||||||
|
// Timezone of dashboard,
|
||||||
|
timezone?: *"browser" | "utc"
|
||||||
|
// Whether a dashboard is editable or not.
|
||||||
|
editable: bool | *true
|
||||||
|
// 0 for no shared crosshair or tooltip (default).
|
||||||
|
// 1 for shared crosshair.
|
||||||
|
// 2 for shared crosshair AND shared tooltip.
|
||||||
|
graphTooltip: >=0 & <=2 | *0
|
||||||
|
// Time range for dashboard, e.g. last 6 hours, last 7 days, etc
|
||||||
|
time?: {
|
||||||
|
from: string | *"now-6h"
|
||||||
|
to: string | *"now"
|
||||||
|
}
|
||||||
|
// Timepicker metadata.
|
||||||
|
timepicker?: {
|
||||||
|
// Whether timepicker is collapsed or not.
|
||||||
|
collapse: bool | *false
|
||||||
|
// Whether timepicker is enabled or not.
|
||||||
|
enable: bool | *true
|
||||||
|
// Whether timepicker is visible or not.
|
||||||
|
hidden: bool | *false
|
||||||
|
// Selectable intervals for auto-refresh.
|
||||||
|
refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
|
||||||
|
}
|
||||||
|
// Templating.
|
||||||
|
templating?: list: [...{...}]
|
||||||
|
// Annotations.
|
||||||
|
annotations?: list: [...{
|
||||||
|
builtIn: number | *0
|
||||||
|
// Datasource to use for annotation.
|
||||||
|
datasource: string
|
||||||
|
// Whether annotation is enabled.
|
||||||
|
enable?: bool | *true
|
||||||
|
// Whether to hide annotation.
|
||||||
|
hide?: bool | *false
|
||||||
|
// Annotation icon color.
|
||||||
|
iconColor?: string
|
||||||
|
// Name of annotation.
|
||||||
|
name?: string
|
||||||
|
type: string | *"dashboard"
|
||||||
|
// Query for annotation data.
|
||||||
|
rawQuery?: string
|
||||||
|
showIn: number | *0
|
||||||
|
}]
|
||||||
|
// Auto-refresh interval.
|
||||||
|
refresh?: string
|
||||||
|
// Version of the JSON schema, incremented each time a Grafana update brings
|
||||||
|
// changes to said schema.
|
||||||
|
schemaVersion: number | *25
|
||||||
|
// Version of the dashboard, incremented each time the dashboard is updated.
|
||||||
|
version?: number
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#Latest: {
|
||||||
|
#Dashboard: Dummy.latest
|
||||||
|
#Panel: Dummy.latest._Panel
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package scuemata
|
||||||
|
|
||||||
|
// Definition of the shape of a panel plugin's schema declarations in its
|
||||||
|
// schema.cue file.
|
||||||
|
//
|
||||||
|
// Note that these keys do not appear directly in any real JSON artifact;
|
||||||
|
// rather, they are composed into panel structures as they are defined within
|
||||||
|
// the larger Dashboard schema.
|
||||||
|
#PanelSchema: {
|
||||||
|
PanelOptions: {...}
|
||||||
|
PanelFieldConfig: {...}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A lineage of panel schema
|
||||||
|
#PanelLineage: [#PanelSchema, ...#PanelSchema]
|
||||||
|
|
||||||
|
// Panel plugin-specific Family
|
||||||
|
#PanelFamily: {
|
||||||
|
lineages: [#PanelLineage, ...#PanelLineage]
|
||||||
|
migrations: [...#Migration]
|
||||||
|
}
|
60
pkg/cmd/grafana-cli/commands/testdata/invalid_scuemata_missing_family/cue/scuemata/scuemata.cue
vendored
Normal file
60
pkg/cmd/grafana-cli/commands/testdata/invalid_scuemata_missing_family/cue/scuemata/scuemata.cue
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package scuemata
|
||||||
|
|
||||||
|
// A family is a collection of schemas that specify a single kind of object,
|
||||||
|
// allowing evolution of the canonical schema for that kind of object over time.
|
||||||
|
//
|
||||||
|
// The schemas are organized into a list of Lineages, which are themselves ordered
|
||||||
|
// lists of schemas where each schema with its predecessor in the lineage.
|
||||||
|
//
|
||||||
|
// If it is desired to define a schema with a breaking schema relative to its
|
||||||
|
// predecessors, a new Lineage must be created, as well as a Migration that defines
|
||||||
|
// a mapping to the new schema from the latest schema in prior Lineage.
|
||||||
|
//
|
||||||
|
// The version number of a schema is not controlled by the schema itself, but by
|
||||||
|
// its position in the list of lineages - e.g., 0.0 corresponds to the first
|
||||||
|
// schema in the first lineage.
|
||||||
|
#Family: {
|
||||||
|
lineages: [#Lineage, ...#Lineage]
|
||||||
|
migrations: [...#Migration]
|
||||||
|
let lseq = lineages[len(lineages)-1]
|
||||||
|
latest: #LastSchema & {_p: lseq}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Lineage is a non-empty list containing an ordered series of schemas that
|
||||||
|
// all describe a single kind of object, where each schema is backwards
|
||||||
|
// compatible with its predecessor.
|
||||||
|
#Lineage: [{...}, ...{...}]
|
||||||
|
|
||||||
|
#LastSchema: {
|
||||||
|
_p: #Lineage
|
||||||
|
_p[len(_p)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Migration defines a relation between two schemas, "_from" and "_to". The
|
||||||
|
// relation expresses any complex mappings that must be performed to
|
||||||
|
// transform an input artifact valid with respect to the _from schema, into
|
||||||
|
// an artifact valid with respect to the _to schema. This is accomplished
|
||||||
|
// in two stages:
|
||||||
|
// 1. A Migration is initially defined by passing in schemas for _from and _to,
|
||||||
|
// and mappings that translate _from to _to are defined in _rel.
|
||||||
|
// 2. A concrete object may then be unified with _to, resulting in its values
|
||||||
|
// being mapped onto "result" by way of _rel.
|
||||||
|
//
|
||||||
|
// This is the absolute simplest possible definition of a Migration. It's
|
||||||
|
// incumbent on the implementor to manually ensure the correctness and
|
||||||
|
// completeness of the mapping. The primary value in defining such a generic
|
||||||
|
// structure is to allow comparably generic logic for migrating concrete
|
||||||
|
// artifacts through schema changes.
|
||||||
|
//
|
||||||
|
// If _to isn't backwards compatible (accretion-only) with _from, then _rel must
|
||||||
|
// explicitly enumerate every field in _from and map it to a field in _to, even
|
||||||
|
// if they're identical. This is laborious for anything outside trivially tiny
|
||||||
|
// schema. We'll want to eventually add helpers for whitelisting or blacklisting
|
||||||
|
// of paths in _from, so that migrations of larger schema can focus narrowly on
|
||||||
|
// the points of actual change.
|
||||||
|
#Migration: {
|
||||||
|
from: {...}
|
||||||
|
to: {...}
|
||||||
|
rel: {...}
|
||||||
|
result: to & rel
|
||||||
|
}
|
83
pkg/cmd/grafana-cli/commands/testdata/invalid_scuemata_missing_panel/cue/data/gen.cue
vendored
Normal file
83
pkg/cmd/grafana-cli/commands/testdata/invalid_scuemata_missing_panel/cue/data/gen.cue
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package grafanaschema
|
||||||
|
|
||||||
|
import "github.com/grafana/grafana/cue/scuemata"
|
||||||
|
|
||||||
|
Family: scuemata.#Family & {
|
||||||
|
lineages: [
|
||||||
|
[
|
||||||
|
{ // 0.0
|
||||||
|
// Unique numeric identifier for the dashboard.
|
||||||
|
// TODO must isolate or remove identifiers local to a Grafana instance...?
|
||||||
|
id?: number
|
||||||
|
// Unique dashboard identifier that can be generated by anyone. string (8-40)
|
||||||
|
uid: string
|
||||||
|
// Title of dashboard.
|
||||||
|
title?: string
|
||||||
|
// Description of dashboard.
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
gnetId?: string
|
||||||
|
// Tags associated with dashboard.
|
||||||
|
tags?: [...string]
|
||||||
|
// Theme of dashboard.
|
||||||
|
style: *"light" | "dark"
|
||||||
|
// Timezone of dashboard,
|
||||||
|
timezone?: *"browser" | "utc"
|
||||||
|
// Whether a dashboard is editable or not.
|
||||||
|
editable: bool | *true
|
||||||
|
// 0 for no shared crosshair or tooltip (default).
|
||||||
|
// 1 for shared crosshair.
|
||||||
|
// 2 for shared crosshair AND shared tooltip.
|
||||||
|
graphTooltip: >=0 & <=2 | *0
|
||||||
|
// Time range for dashboard, e.g. last 6 hours, last 7 days, etc
|
||||||
|
time?: {
|
||||||
|
from: string | *"now-6h"
|
||||||
|
to: string | *"now"
|
||||||
|
}
|
||||||
|
// Timepicker metadata.
|
||||||
|
timepicker?: {
|
||||||
|
// Whether timepicker is collapsed or not.
|
||||||
|
collapse: bool | *false
|
||||||
|
// Whether timepicker is enabled or not.
|
||||||
|
enable: bool | *true
|
||||||
|
// Whether timepicker is visible or not.
|
||||||
|
hidden: bool | *false
|
||||||
|
// Selectable intervals for auto-refresh.
|
||||||
|
refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
|
||||||
|
}
|
||||||
|
// Templating.
|
||||||
|
templating?: list: [...{...}]
|
||||||
|
// Annotations.
|
||||||
|
annotations?: list: [...{
|
||||||
|
builtIn: number | *0
|
||||||
|
// Datasource to use for annotation.
|
||||||
|
datasource: string
|
||||||
|
// Whether annotation is enabled.
|
||||||
|
enable?: bool | *true
|
||||||
|
// Whether to hide annotation.
|
||||||
|
hide?: bool | *false
|
||||||
|
// Annotation icon color.
|
||||||
|
iconColor?: string
|
||||||
|
// Name of annotation.
|
||||||
|
name?: string
|
||||||
|
type: string | *"dashboard"
|
||||||
|
// Query for annotation data.
|
||||||
|
rawQuery?: string
|
||||||
|
showIn: number | *0
|
||||||
|
}]
|
||||||
|
// Auto-refresh interval.
|
||||||
|
refresh?: string
|
||||||
|
// Version of the JSON schema, incremented each time a Grafana update brings
|
||||||
|
// changes to said schema.
|
||||||
|
schemaVersion: number | *25
|
||||||
|
// Version of the dashboard, incremented each time the dashboard is updated.
|
||||||
|
version?: number
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#Latest: {
|
||||||
|
#Dashboard: Family.latest
|
||||||
|
#Panel: Family.latest._Panel
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package scuemata
|
||||||
|
|
||||||
|
// Definition of the shape of a panel plugin's schema declarations in its
|
||||||
|
// schema.cue file.
|
||||||
|
//
|
||||||
|
// Note that these keys do not appear directly in any real JSON artifact;
|
||||||
|
// rather, they are composed into panel structures as they are defined within
|
||||||
|
// the larger Dashboard schema.
|
||||||
|
#PanelSchema: {
|
||||||
|
PanelOptions: {...}
|
||||||
|
PanelFieldConfig: {...}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A lineage of panel schema
|
||||||
|
#PanelLineage: [#PanelSchema, ...#PanelSchema]
|
||||||
|
|
||||||
|
// Panel plugin-specific Family
|
||||||
|
#PanelFamily: {
|
||||||
|
lineages: [#PanelLineage, ...#PanelLineage]
|
||||||
|
migrations: [...#Migration]
|
||||||
|
}
|
60
pkg/cmd/grafana-cli/commands/testdata/invalid_scuemata_missing_panel/cue/scuemata/scuemata.cue
vendored
Normal file
60
pkg/cmd/grafana-cli/commands/testdata/invalid_scuemata_missing_panel/cue/scuemata/scuemata.cue
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package scuemata
|
||||||
|
|
||||||
|
// A family is a collection of schemas that specify a single kind of object,
|
||||||
|
// allowing evolution of the canonical schema for that kind of object over time.
|
||||||
|
//
|
||||||
|
// The schemas are organized into a list of Lineages, which are themselves ordered
|
||||||
|
// lists of schemas where each schema with its predecessor in the lineage.
|
||||||
|
//
|
||||||
|
// If it is desired to define a schema with a breaking schema relative to its
|
||||||
|
// predecessors, a new Lineage must be created, as well as a Migration that defines
|
||||||
|
// a mapping to the new schema from the latest schema in prior Lineage.
|
||||||
|
//
|
||||||
|
// The version number of a schema is not controlled by the schema itself, but by
|
||||||
|
// its position in the list of lineages - e.g., 0.0 corresponds to the first
|
||||||
|
// schema in the first lineage.
|
||||||
|
#Family: {
|
||||||
|
lineages: [#Lineage, ...#Lineage]
|
||||||
|
migrations: [...#Migration]
|
||||||
|
let lseq = lineages[len(lineages)-1]
|
||||||
|
latest: #LastSchema & {_p: lseq}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Lineage is a non-empty list containing an ordered series of schemas that
|
||||||
|
// all describe a single kind of object, where each schema is backwards
|
||||||
|
// compatible with its predecessor.
|
||||||
|
#Lineage: [{...}, ...{...}]
|
||||||
|
|
||||||
|
#LastSchema: {
|
||||||
|
_p: #Lineage
|
||||||
|
_p[len(_p)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Migration defines a relation between two schemas, "_from" and "_to". The
|
||||||
|
// relation expresses any complex mappings that must be performed to
|
||||||
|
// transform an input artifact valid with respect to the _from schema, into
|
||||||
|
// an artifact valid with respect to the _to schema. This is accomplished
|
||||||
|
// in two stages:
|
||||||
|
// 1. A Migration is initially defined by passing in schemas for _from and _to,
|
||||||
|
// and mappings that translate _from to _to are defined in _rel.
|
||||||
|
// 2. A concrete object may then be unified with _to, resulting in its values
|
||||||
|
// being mapped onto "result" by way of _rel.
|
||||||
|
//
|
||||||
|
// This is the absolute simplest possible definition of a Migration. It's
|
||||||
|
// incumbent on the implementor to manually ensure the correctness and
|
||||||
|
// completeness of the mapping. The primary value in defining such a generic
|
||||||
|
// structure is to allow comparably generic logic for migrating concrete
|
||||||
|
// artifacts through schema changes.
|
||||||
|
//
|
||||||
|
// If _to isn't backwards compatible (accretion-only) with _from, then _rel must
|
||||||
|
// explicitly enumerate every field in _from and map it to a field in _to, even
|
||||||
|
// if they're identical. This is laborious for anything outside trivially tiny
|
||||||
|
// schema. We'll want to eventually add helpers for whitelisting or blacklisting
|
||||||
|
// of paths in _from, so that migrations of larger schema can focus narrowly on
|
||||||
|
// the points of actual change.
|
||||||
|
#Migration: {
|
||||||
|
from: {...}
|
||||||
|
to: {...}
|
||||||
|
rel: {...}
|
||||||
|
result: to & rel
|
||||||
|
}
|
22
pkg/cmd/grafana-cli/commands/testdata/public/dashlist/models.cue
vendored
Normal file
22
pkg/cmd/grafana-cli/commands/testdata/public/dashlist/models.cue
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package grafanaschema
|
||||||
|
|
||||||
|
Family: {
|
||||||
|
lineages: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
PanelOptions: {
|
||||||
|
showStarred: bool | *true
|
||||||
|
showRecentlyViewed: bool | *false
|
||||||
|
showSearch: bool | *false
|
||||||
|
showHeadings: bool | *true
|
||||||
|
maxItems: int | *10
|
||||||
|
query: string | *""
|
||||||
|
folderId?: int
|
||||||
|
tags: [...string] | *[]
|
||||||
|
},
|
||||||
|
PanelFieldConfig: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
migrations: []
|
||||||
|
}
|
17
pkg/cmd/grafana-cli/commands/testdata/public/dashlist/plugin.json
vendored
Normal file
17
pkg/cmd/grafana-cli/commands/testdata/public/dashlist/plugin.json
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"type": "panel",
|
||||||
|
"name": "Dashboard list",
|
||||||
|
"id": "dashlist",
|
||||||
|
"skipDataQuery": true,
|
||||||
|
"info": {
|
||||||
|
"description": "List of dynamic links to other dashboards",
|
||||||
|
"author": {
|
||||||
|
"name": "Grafana Labs",
|
||||||
|
"url": "https://grafana.com"
|
||||||
|
},
|
||||||
|
"logos": {
|
||||||
|
"small": "img/icn-dashlist-panel.svg",
|
||||||
|
"large": "img/icn-dashlist-panel.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
214
pkg/cmd/grafana-cli/commands/testdata/valid_scuemata/cue/data/gen.cue
vendored
Normal file
214
pkg/cmd/grafana-cli/commands/testdata/valid_scuemata/cue/data/gen.cue
vendored
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
package grafanaschema
|
||||||
|
|
||||||
|
import "github.com/grafana/grafana/cue/scuemata"
|
||||||
|
|
||||||
|
Family: scuemata.#Family & {
|
||||||
|
lineages: [
|
||||||
|
[
|
||||||
|
{ // 0.0
|
||||||
|
// Unique numeric identifier for the dashboard.
|
||||||
|
// TODO must isolate or remove identifiers local to a Grafana instance...?
|
||||||
|
id?: number
|
||||||
|
// Unique dashboard identifier that can be generated by anyone. string (8-40)
|
||||||
|
uid?: string
|
||||||
|
// Title of dashboard.
|
||||||
|
title?: string
|
||||||
|
// Description of dashboard.
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
gnetId?: string
|
||||||
|
// Tags associated with dashboard.
|
||||||
|
tags?: [...string]
|
||||||
|
// Theme of dashboard.
|
||||||
|
style: *"light" | "dark"
|
||||||
|
// Timezone of dashboard,
|
||||||
|
timezone?: *"browser" | "utc"
|
||||||
|
// Whether a dashboard is editable or not.
|
||||||
|
editable: bool | *true
|
||||||
|
// 0 for no shared crosshair or tooltip (default).
|
||||||
|
// 1 for shared crosshair.
|
||||||
|
// 2 for shared crosshair AND shared tooltip.
|
||||||
|
graphTooltip: >=0 & <=2 | *0
|
||||||
|
// Time range for dashboard, e.g. last 6 hours, last 7 days, etc
|
||||||
|
time?: {
|
||||||
|
from: string | *"now-6h"
|
||||||
|
to: string | *"now"
|
||||||
|
}
|
||||||
|
// Timepicker metadata.
|
||||||
|
timepicker?: {
|
||||||
|
// Whether timepicker is collapsed or not.
|
||||||
|
collapse: bool | *false
|
||||||
|
// Whether timepicker is enabled or not.
|
||||||
|
enable: bool | *true
|
||||||
|
// Whether timepicker is visible or not.
|
||||||
|
hidden: bool | *false
|
||||||
|
// Selectable intervals for auto-refresh.
|
||||||
|
refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
|
||||||
|
}
|
||||||
|
// Templating.
|
||||||
|
templating?: list: [...{...}]
|
||||||
|
// Annotations.
|
||||||
|
annotations?: list: [...{
|
||||||
|
builtIn: number | *0
|
||||||
|
// Datasource to use for annotation.
|
||||||
|
datasource: string
|
||||||
|
// Whether annotation is enabled.
|
||||||
|
enable?: bool | *true
|
||||||
|
// Whether to hide annotation.
|
||||||
|
hide?: bool | *false
|
||||||
|
// Annotation icon color.
|
||||||
|
iconColor?: string
|
||||||
|
// Name of annotation.
|
||||||
|
name?: string
|
||||||
|
type: string | *"dashboard"
|
||||||
|
// Query for annotation data.
|
||||||
|
rawQuery?: string
|
||||||
|
showIn: number | *0
|
||||||
|
}]
|
||||||
|
// Auto-refresh interval.
|
||||||
|
refresh?: string
|
||||||
|
// Version of the JSON schema, incremented each time a Grafana update brings
|
||||||
|
// changes to said schema.
|
||||||
|
schemaVersion: number | *25
|
||||||
|
// Version of the dashboard, incremented each time the dashboard is updated.
|
||||||
|
version?: number
|
||||||
|
panels?: [...#Panel]
|
||||||
|
|
||||||
|
// Dashboard panels. Panels are canonically defined inline
|
||||||
|
// because they share a version timeline with the dashboard
|
||||||
|
// schema; they do not vary independently. We create a separate,
|
||||||
|
// synthetic Family to represent them in Go, for ease of generating
|
||||||
|
// e.g. JSON Schema.
|
||||||
|
#Panel: {
|
||||||
|
...
|
||||||
|
// The panel plugin type id.
|
||||||
|
type: !=""
|
||||||
|
|
||||||
|
// Internal - the exact major and minor versions of the panel plugin
|
||||||
|
// schema. Hidden and therefore not a part of the data model, but
|
||||||
|
// expected to be filled with panel plugin schema versions so that it's
|
||||||
|
// possible to figure out which schema version matched on a successful
|
||||||
|
// unification.
|
||||||
|
// _pv: { maj: int, min: int }
|
||||||
|
// The major and minor versions of the panel plugin for this schema.
|
||||||
|
// TODO 2-tuple list instead of struct?
|
||||||
|
panelSchema?: { maj: number, min: number }
|
||||||
|
|
||||||
|
// Panel title.
|
||||||
|
title?: string
|
||||||
|
// Description.
|
||||||
|
description?: string
|
||||||
|
// Whether to display the panel without a background.
|
||||||
|
transparent: bool | *false
|
||||||
|
// Name of default datasource.
|
||||||
|
datasource?: string
|
||||||
|
// Grid position.
|
||||||
|
gridPos?: {
|
||||||
|
// Panel
|
||||||
|
h: number & >0 | *9
|
||||||
|
// Panel
|
||||||
|
w: number & >0 & <=24 | *12
|
||||||
|
// Panel x
|
||||||
|
x: number & >=0 & <24 | *0
|
||||||
|
// Panel y
|
||||||
|
y: number & >=0 | *0
|
||||||
|
// true if fixed
|
||||||
|
static?: bool
|
||||||
|
}
|
||||||
|
// Panel links.
|
||||||
|
// links?: [..._panelLink]
|
||||||
|
// Name of template variable to repeat for.
|
||||||
|
repeat?: string
|
||||||
|
// Direction to repeat in if 'repeat' is set.
|
||||||
|
// "h" for horizontal, "v" for vertical.
|
||||||
|
repeatDirection: *"h" | "v"
|
||||||
|
// Schema for panel targets is specified by datasource
|
||||||
|
// plugins. We use a placeholder definition, which the Go
|
||||||
|
// schema loader either left open/as-is with the Base
|
||||||
|
// variant of the Dashboard and Panel families, or filled
|
||||||
|
// with types derived from plugins in the Instance variant.
|
||||||
|
// When working directly from CUE, importers can extend this
|
||||||
|
// type directly to achieve the same effect.
|
||||||
|
targets?: [...{...}]
|
||||||
|
|
||||||
|
// The values depend on panel type
|
||||||
|
options: {...}
|
||||||
|
|
||||||
|
fieldConfig: {
|
||||||
|
defaults: {
|
||||||
|
// The display value for this field. This supports template variables blank is auto
|
||||||
|
displayName?: string
|
||||||
|
|
||||||
|
// This can be used by data sources that return and explicit naming structure for values and labels
|
||||||
|
// When this property is configured, this value is used rather than the default naming strategy.
|
||||||
|
displayNameFromDS?: string
|
||||||
|
|
||||||
|
// Human readable field metadata
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
// An explict path to the field in the datasource. When the frame meta includes a path,
|
||||||
|
// This will default to `${frame.meta.path}/${field.name}
|
||||||
|
//
|
||||||
|
// When defined, this value can be used as an identifier within the datasource scope, and
|
||||||
|
// may be used to update the results
|
||||||
|
path?: string
|
||||||
|
|
||||||
|
// True if data source can write a value to the path. Auth/authz are supported separately
|
||||||
|
writeable?: bool
|
||||||
|
|
||||||
|
// True if data source field supports ad-hoc filters
|
||||||
|
filterable?: bool
|
||||||
|
|
||||||
|
// Numeric Options
|
||||||
|
unit?: string
|
||||||
|
|
||||||
|
// Significant digits (for display)
|
||||||
|
decimals?: number
|
||||||
|
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
|
||||||
|
// // Convert input values into a display string
|
||||||
|
// mappings?: ValueMapping[];
|
||||||
|
|
||||||
|
// // Map numeric values to states
|
||||||
|
// thresholds?: ThresholdsConfig;
|
||||||
|
|
||||||
|
// // Map values to a display color
|
||||||
|
// color?: FieldColor;
|
||||||
|
|
||||||
|
// // Used when reducing field values
|
||||||
|
// nullValueMode?: NullValueMode;
|
||||||
|
|
||||||
|
// // The behavior when clicking on a result
|
||||||
|
// links?: DataLink[];
|
||||||
|
|
||||||
|
// Alternative to empty string
|
||||||
|
noValue?: string
|
||||||
|
|
||||||
|
// Can always exist. Valid fields within this are
|
||||||
|
// defined by the panel plugin - that's the
|
||||||
|
// PanelFieldConfig that comes from the plugin.
|
||||||
|
custom?: {...}
|
||||||
|
}
|
||||||
|
overrides: [...{
|
||||||
|
matcher: {
|
||||||
|
id: string | *""
|
||||||
|
options?: _
|
||||||
|
}
|
||||||
|
properties: [...{
|
||||||
|
id: string | *""
|
||||||
|
value?: _
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#Latest: {
|
||||||
|
#Dashboard: Family.latest
|
||||||
|
#Panel: Family.latest._Panel
|
||||||
|
}
|
21
pkg/cmd/grafana-cli/commands/testdata/valid_scuemata/cue/scuemata/panel-plugin.cue
vendored
Normal file
21
pkg/cmd/grafana-cli/commands/testdata/valid_scuemata/cue/scuemata/panel-plugin.cue
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package scuemata
|
||||||
|
|
||||||
|
// Definition of the shape of a panel plugin's schema declarations in its
|
||||||
|
// schema.cue file.
|
||||||
|
//
|
||||||
|
// Note that these keys do not appear directly in any real JSON artifact;
|
||||||
|
// rather, they are composed into panel structures as they are defined within
|
||||||
|
// the larger Dashboard schema.
|
||||||
|
#PanelSchema: {
|
||||||
|
PanelOptions: {...}
|
||||||
|
PanelFieldConfig: {...}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A lineage of panel schema
|
||||||
|
#PanelLineage: [#PanelSchema, ...#PanelSchema]
|
||||||
|
|
||||||
|
// Panel plugin-specific Family
|
||||||
|
#PanelFamily: {
|
||||||
|
lineages: [#PanelLineage, ...#PanelLineage]
|
||||||
|
migrations: [...#Migration]
|
||||||
|
}
|
60
pkg/cmd/grafana-cli/commands/testdata/valid_scuemata/cue/scuemata/scuemata.cue
vendored
Normal file
60
pkg/cmd/grafana-cli/commands/testdata/valid_scuemata/cue/scuemata/scuemata.cue
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package scuemata
|
||||||
|
|
||||||
|
// A family is a collection of schemas that specify a single kind of object,
|
||||||
|
// allowing evolution of the canonical schema for that kind of object over time.
|
||||||
|
//
|
||||||
|
// The schemas are organized into a list of Lineages, which are themselves ordered
|
||||||
|
// lists of schemas where each schema with its predecessor in the lineage.
|
||||||
|
//
|
||||||
|
// If it is desired to define a schema with a breaking schema relative to its
|
||||||
|
// predecessors, a new Lineage must be created, as well as a Migration that defines
|
||||||
|
// a mapping to the new schema from the latest schema in prior Lineage.
|
||||||
|
//
|
||||||
|
// The version number of a schema is not controlled by the schema itself, but by
|
||||||
|
// its position in the list of lineages - e.g., 0.0 corresponds to the first
|
||||||
|
// schema in the first lineage.
|
||||||
|
#Family: {
|
||||||
|
lineages: [#Lineage, ...#Lineage]
|
||||||
|
migrations: [...#Migration]
|
||||||
|
let lseq = lineages[len(lineages)-1]
|
||||||
|
latest: #LastSchema & {_p: lseq}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Lineage is a non-empty list containing an ordered series of schemas that
|
||||||
|
// all describe a single kind of object, where each schema is backwards
|
||||||
|
// compatible with its predecessor.
|
||||||
|
#Lineage: [{...}, ...{...}]
|
||||||
|
|
||||||
|
#LastSchema: {
|
||||||
|
_p: #Lineage
|
||||||
|
_p[len(_p)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Migration defines a relation between two schemas, "_from" and "_to". The
|
||||||
|
// relation expresses any complex mappings that must be performed to
|
||||||
|
// transform an input artifact valid with respect to the _from schema, into
|
||||||
|
// an artifact valid with respect to the _to schema. This is accomplished
|
||||||
|
// in two stages:
|
||||||
|
// 1. A Migration is initially defined by passing in schemas for _from and _to,
|
||||||
|
// and mappings that translate _from to _to are defined in _rel.
|
||||||
|
// 2. A concrete object may then be unified with _to, resulting in its values
|
||||||
|
// being mapped onto "result" by way of _rel.
|
||||||
|
//
|
||||||
|
// This is the absolute simplest possible definition of a Migration. It's
|
||||||
|
// incumbent on the implementor to manually ensure the correctness and
|
||||||
|
// completeness of the mapping. The primary value in defining such a generic
|
||||||
|
// structure is to allow comparably generic logic for migrating concrete
|
||||||
|
// artifacts through schema changes.
|
||||||
|
//
|
||||||
|
// If _to isn't backwards compatible (accretion-only) with _from, then _rel must
|
||||||
|
// explicitly enumerate every field in _from and map it to a field in _to, even
|
||||||
|
// if they're identical. This is laborious for anything outside trivially tiny
|
||||||
|
// schema. We'll want to eventually add helpers for whitelisting or blacklisting
|
||||||
|
// of paths in _from, so that migrations of larger schema can focus narrowly on
|
||||||
|
// the points of actual change.
|
||||||
|
#Migration: {
|
||||||
|
from: {...}
|
||||||
|
to: {...}
|
||||||
|
rel: {...}
|
||||||
|
result: to & rel
|
||||||
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"cuelang.org/go/cue"
|
"cuelang.org/go/cue"
|
||||||
"cuelang.org/go/cue/load"
|
"cuelang.org/go/cue/load"
|
||||||
|
"github.com/grafana/grafana"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rt = &cue.Runtime{}
|
var rt = &cue.Runtime{}
|
||||||
@ -39,6 +40,13 @@ type BaseLoadPaths struct {
|
|||||||
InstanceCueFS fs.FS
|
InstanceCueFS fs.FS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDefaultLoadPaths() BaseLoadPaths {
|
||||||
|
return BaseLoadPaths{
|
||||||
|
BaseCueFS: grafana.CoreSchema,
|
||||||
|
DistPluginCueFS: grafana.PluginSchema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// toOverlay converts all .cue files in the fs.FS into Source entries in an
|
// toOverlay converts all .cue files in the fs.FS into Source entries in an
|
||||||
// overlay map, as expected by load.Config.
|
// overlay map, as expected by load.Config.
|
||||||
//
|
//
|
||||||
|
Loading…
Reference in New Issue
Block a user