The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/registry/apis/provisioning/resources/fileformat.go

112 lines
3.6 KiB

package resources
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
"github.com/grafana/grafana-app-sdk/logging"
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
var (
ErrUnableToReadResourceBytes = errors.New("unable to read bytes as a resource")
ErrUnableToReadPanelsMissing = errors.New("panels property is required")
ErrUnableToReadSchemaVersionMissing = errors.New("schemaVersion property is required")
ErrUnableToReadTagsMissing = errors.New("tags property is required")
ErrClassicResourceIsAlreadyK8sForm = errors.New("classic resource is already structured with apiVersion and kind")
)
// This reads a "classic" file format and will convert it to an unstructured k8s resource
// The file path may determine how the resource is parsed
//
// The context and logger are both only used for logging purposes. They do not control any logic.
func ReadClassicResource(ctx context.Context, info *repository.FileInfo) (*unstructured.Unstructured, *schema.GroupVersionKind, provisioning.ClassicFileType, error) {
var value map[string]any
// Try parsing as JSON
if info.Data[0] == '{' {
err := json.Unmarshal(info.Data, &value)
if err != nil {
return nil, nil, "", err
}
} else {
return nil, nil, "", fmt.Errorf("unable to read file")
}
// regular version headers exist
// TODO: do we intend on this checking Kind or kind? document reasoning.
if value["apiVersion"] != nil {
if value["kind"] != nil {
return nil, nil, "", ErrClassicResourceIsAlreadyK8sForm
}
logging.FromContext(ctx).Debug("TODO... likely a provisioning",
"apiVersion", value["apiVersion"],
"kind", value["Kind"])
gv, err := schema.ParseGroupVersion(value["apiVersion"].(string))
if err != nil {
return nil, nil, "", fmt.Errorf("invalid apiVersion")
}
gvk := gv.WithKind(value["Kind"].(string))
return &unstructured.Unstructured{Object: value}, &gvk, "", nil
}
// If this is a dashboard, convert it
if value["panels"] != nil &&
value["schemaVersion"] != nil &&
value["tags"] != nil {
gvk := &schema.GroupVersionKind{
Group: dashboard.GROUP,
Version: "v0alpha1", // no schema
Kind: "Dashboard"}
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": gvk.GroupVersion().String(),
"kind": gvk.Kind,
"metadata": map[string]any{
"name": value["uid"],
},
"spec": value,
},
}, gvk, provisioning.ClassicDashboard, nil
}
return nil, nil, "", ErrUnableToReadResourceBytes
}
// DecodeYAMLObject reads the input as YAML and outputs its Kubernetes resource, if it is one.
// Note that all JSON is also valid YAML, so this can also be used for JSON data.
func DecodeYAMLObject(input io.Reader) (*unstructured.Unstructured, *schema.GroupVersionKind, error) {
data, err := io.ReadAll(input)
if err != nil {
return nil, nil, err
}
obj, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).
Decode(data, nil, nil)
if err != nil {
return nil, gvk, err
}
// The decoder should put it directly into an unstructured object
val, ok := obj.(*unstructured.Unstructured)
if ok {
return val, gvk, err
}
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, gvk, err
}
return &unstructured.Unstructured{Object: unstructuredMap}, gvk, err
}