Provisioning: Improve sample provisioning (#103508)
This commit is contained in:
+1
-1
@@ -69,7 +69,6 @@
|
||||
|
||||
/pkg/apis/provisioning @grafana/grafana-git-ui-sync-team
|
||||
/public/app/features/provisioning @grafana/grafana-git-ui-sync-team
|
||||
/conf/provisioning/sample @grafana/grafana-git-ui-sync-team
|
||||
/pkg/registry/apis/provisioning @grafana/grafana-git-ui-sync-team
|
||||
|
||||
/apps/alerting/ @grafana/alerting-backend
|
||||
@@ -844,3 +843,4 @@ embed.go @grafana/grafana-as-code
|
||||
/conf/provisioning/dashboards/ @grafana/dashboards-squad
|
||||
/conf/provisioning/datasources/ @grafana/plugins-platform-backend
|
||||
/conf/provisioning/plugins/ @grafana/plugins-platform-backend
|
||||
/conf/provisioning/sample/ @grafana/grafana-git-ui-sync-team
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"target": {
|
||||
"limit": 100,
|
||||
"matchAny": false,
|
||||
"tags": [],
|
||||
"type": "dashboard"
|
||||
},
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": 1348,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "plaintext",
|
||||
"showLineNumbers": false,
|
||||
"showMiniMap": false
|
||||
},
|
||||
"content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod\n\n---\ntext = $Text",
|
||||
"mode": "markdown"
|
||||
},
|
||||
"pluginVersion": "9.2.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"title": "Markdown (with variables)",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 5,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "json",
|
||||
"showLineNumbers": true,
|
||||
"showMiniMap": false
|
||||
},
|
||||
"content": "{\n \"datacenter\": $datacenter,\n \"server\": $server,\n \"pod\": $pod\n \"text\": $Text\n}\n",
|
||||
"mode": "code"
|
||||
},
|
||||
"pluginVersion": "9.2.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"title": "JSON (with variables)",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"id": 6,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "plaintext",
|
||||
"showLineNumbers": false,
|
||||
"showMiniMap": false
|
||||
},
|
||||
"content": "<h3>Data center</h3>\n<p>$datacenter</p>\n\n<h3>server</h3>\n<p>$server</p>\n\n<h3>pod</h3>\n<p>$pod</p>\n\n<h3>Text</h3>\n<p>$Text</p>",
|
||||
"mode": "html"
|
||||
},
|
||||
"pluginVersion": "9.2.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"title": "HTML (with variables)",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 9
|
||||
},
|
||||
"id": 7,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "markdown",
|
||||
"showLineNumbers": true,
|
||||
"showMiniMap": true
|
||||
},
|
||||
"content": "## Data center\n$datacenter\n\n### server\n$server\n\n#### pod = \n$pod\n",
|
||||
"mode": "code"
|
||||
},
|
||||
"pluginVersion": "9.2.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"title": "Markdown (code w/ with variables)",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"refresh": false,
|
||||
"schemaVersion": 37,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": ["All"],
|
||||
"value": ["$__all"]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"definition": "*",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "datacenter",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "*",
|
||||
"refId": "gdev-testdata-datacenter-Variable-Query"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": ["All"],
|
||||
"value": ["$__all"]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"definition": "$datacenter.*",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "server",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "$datacenter.*",
|
||||
"refId": "gdev-testdata-server-Variable-Query"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": ["AAA", "ACB"],
|
||||
"value": ["AAA", "ACB"]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"definition": "$datacenter.$server.*",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "pod",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "$datacenter.$server.*",
|
||||
"refId": "gdev-testdata-pod-Variable-Query"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "temp",
|
||||
"value": "temp"
|
||||
},
|
||||
"hide": 0,
|
||||
"name": "Text",
|
||||
"options": [
|
||||
{
|
||||
"selected": true,
|
||||
"text": "temp",
|
||||
"value": "temp"
|
||||
}
|
||||
],
|
||||
"query": "temp",
|
||||
"skipUrlSync": false,
|
||||
"type": "textbox"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": ["10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
|
||||
},
|
||||
"timezone": "",
|
||||
"title": "Text options",
|
||||
"uid": "WZ7AhQiVz",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"options": {
|
||||
"content": "<div style=\"background:#1cac78; color:#000; padding:50px; height:1000px\"><h1>This dashboard was loaded from provisioning (classic) 🎉🎉🎉🎉🎉🎉</h1></div>",
|
||||
"mode": "html"
|
||||
},
|
||||
"transparent": true,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 37,
|
||||
"tags": [],
|
||||
"title": "Dashboard classic",
|
||||
"uid": "dash-classic"
|
||||
}
|
||||
+5
-9
@@ -1,21 +1,17 @@
|
||||
{
|
||||
"kind": "Dashboard",
|
||||
"apiVersion": "dashboard.grafana.app/v0alpha1",
|
||||
"apiVersion": "dashboard.grafana.app/v1alpha1",
|
||||
"metadata": {
|
||||
"name": "ignored?",
|
||||
"namespace": "default",
|
||||
"uid": "ignored",
|
||||
"resourceVersion": "also ignored",
|
||||
"creationTimestamp": "2024-11-15T19:54:07Z",
|
||||
"name": "sample-dash",
|
||||
"annotations": {
|
||||
"hello": "world:"
|
||||
"hello": "world"
|
||||
},
|
||||
"labels": {
|
||||
"region": "west"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"title": "Green dashboard from provisioning",
|
||||
"title": "Dashboard saved at v1alpha1",
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
@@ -25,7 +21,7 @@
|
||||
"y": 0
|
||||
},
|
||||
"options": {
|
||||
"content": "<div style=\"background:#1cac78; color:#000; padding:50px; height:1000px\"><h1>This dashboard was loaded from provisioning 🎉🎉🎉🎉🎉🎉</h1></div>",
|
||||
"content": "<div style=\"background:#1cac78; color:#000; padding:50px; height:1000px\"><h1>This dashboard was loaded from provisioning (v1) 🎉🎉🎉🎉🎉🎉</h1></div>",
|
||||
"mode": "html"
|
||||
},
|
||||
"transparent": true,
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"kind": "Dashboard",
|
||||
"apiVersion": "dashboard.grafana.app/v1alpha1",
|
||||
"metadata": {
|
||||
"name": "dashboard-nested",
|
||||
"annotations": {
|
||||
"hello": "world"
|
||||
},
|
||||
"labels": {
|
||||
"region": "west"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"title": "Dashboard nested under A/B",
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"options": {
|
||||
"content": "<div style=\"background:#1cac78; color:#000; padding:50px; height:1000px\"><h1>This dashboard was loaded from provisioning (nested) 🎉🎉🎉🎉🎉🎉</h1></div>",
|
||||
"mode": "html"
|
||||
},
|
||||
"transparent": true,
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,7 @@
|
||||
"kind": "Playlist",
|
||||
"apiVersion": "playlist.grafana.app/v0alpha1",
|
||||
"metadata": {
|
||||
"name": "adcmdrw",
|
||||
"namespace": "default",
|
||||
"uid": "8a2b984d-4663-4182-861b-edec2f987dff",
|
||||
"resourceVersion": "1731336353828005",
|
||||
"creationTimestamp": "2024-11-11T14:45:53Z"
|
||||
"name": "sample-palylist"
|
||||
},
|
||||
"spec": {
|
||||
"title": "Playlist from provisioning",
|
||||
|
||||
@@ -24,6 +24,9 @@ var (
|
||||
|
||||
// SupportedProvisioningResources is the list of resources that can fully managed from the UI
|
||||
SupportedProvisioningResources = []schema.GroupVersionResource{FolderResource, DashboardResource}
|
||||
|
||||
// SupportsFolderAnnotation is the list of resources that can be saved in a folder
|
||||
SupportsFolderAnnotation = []schema.GroupResource{FolderResource.GroupResource(), DashboardResource.GroupResource()}
|
||||
)
|
||||
|
||||
// ClientFactory is a factory for creating clients for a given namespace
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// ParserFactory is a factory for creating parsers for a given repository
|
||||
@@ -177,8 +178,12 @@ func (r *parser) Parse(ctx context.Context, info *repository.FileInfo) (parsed *
|
||||
Checksum: info.Hash,
|
||||
})
|
||||
|
||||
if obj.GetName() == "" && obj.GetGenerateName() == "" {
|
||||
return nil, ErrMissingName
|
||||
if obj.GetName() == "" {
|
||||
if obj.GetGenerateName() == "" {
|
||||
return nil, ErrMissingName
|
||||
}
|
||||
// Generate a new UID
|
||||
obj.SetName(obj.GetGenerateName() + util.GenerateShortUID())
|
||||
}
|
||||
|
||||
// Calculate folder identifier from the file path
|
||||
@@ -226,12 +231,6 @@ func (f *ParsedResource) DryRun(ctx context.Context) error {
|
||||
DryRun: []string{"All"},
|
||||
})
|
||||
}
|
||||
|
||||
// When the name is missing (and generateName is configured) use the value from DryRun
|
||||
if f.Obj.GetName() == "" && f.DryRunResponse != nil {
|
||||
f.Obj.SetName(f.DryRunResponse.GetName())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
|
||||
dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
)
|
||||
|
||||
@@ -21,8 +21,8 @@ func TestParser(t *testing.T) {
|
||||
Return(nil, dashboardV1.DashboardResourceInfo.GroupVersionResource(), nil).Maybe()
|
||||
|
||||
parser := &parser{
|
||||
repo: v0alpha1.ResourceRepositoryInfo{
|
||||
Type: v0alpha1.LocalRepositoryType,
|
||||
repo: provisioning.ResourceRepositoryInfo{
|
||||
Type: provisioning.LocalRepositoryType,
|
||||
Namespace: "xxx",
|
||||
Name: "repo",
|
||||
},
|
||||
@@ -63,13 +63,34 @@ spec:
|
||||
`),
|
||||
})
|
||||
require.EqualError(t, err, "name.metadata.name: Required value: missing name in resource")
|
||||
})
|
||||
|
||||
// Read the name from classic grafana format
|
||||
dash, err = parser.Parse(context.Background(), &repository.FileInfo{
|
||||
t.Run("generate name will generate a name", func(t *testing.T) {
|
||||
dash, err := parser.Parse(context.Background(), &repository.FileInfo{
|
||||
Data: []byte(`apiVersion: dashboard.grafana.app/v0alpha1
|
||||
kind: Dashboard
|
||||
metadata:
|
||||
generateName: rand-
|
||||
spec:
|
||||
title: Test dashboard
|
||||
`),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "dashboard.grafana.app", dash.GVK.Group)
|
||||
require.Equal(t, "v0alpha1", dash.GVK.Version)
|
||||
require.True(t, strings.HasPrefix(dash.Obj.GetName(), "rand-"), "set name")
|
||||
})
|
||||
|
||||
t.Run("dashboard classic format", func(t *testing.T) {
|
||||
dash, err := parser.Parse(context.Background(), &repository.FileInfo{
|
||||
Data: []byte(`{ "uid": "test", "schemaVersion": 30, "panels": [], "tags": [] }`),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, v0alpha1.ClassicDashboard, dash.Classic)
|
||||
require.Equal(t, "test", dash.Obj.GetName())
|
||||
require.Equal(t, provisioning.ClassicDashboard, dash.Classic)
|
||||
require.Equal(t, "dashboard.grafana.app", dash.GVK.Group)
|
||||
require.Equal(t, "v0alpha1", dash.GVK.Version)
|
||||
require.Equal(t, "dashboard.grafana.app", dash.GVR.Group)
|
||||
require.Equal(t, "v0alpha1", dash.GVR.Version)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -161,18 +163,25 @@ func (r *ResourcesManager) WriteResourceFromFile(ctx context.Context, path strin
|
||||
}
|
||||
r.resourcesLookup[id] = path
|
||||
|
||||
// Make sure the parent folders exist
|
||||
folder, err := r.folders.EnsureFolderPathExist(ctx, path)
|
||||
if err != nil {
|
||||
return "", parsed.GVK, fmt.Errorf("failed to ensure folder path exists: %w", err)
|
||||
// For resources that exist in folders, set the header annotation
|
||||
if slices.Contains(SupportsFolderAnnotation, parsed.GVR.GroupResource()) {
|
||||
// Make sure the parent folders exist
|
||||
folder, err := r.folders.EnsureFolderPathExist(ctx, path)
|
||||
if err != nil {
|
||||
return "", parsed.GVK, fmt.Errorf("failed to ensure folder path exists: %w", err)
|
||||
}
|
||||
parsed.Meta.SetFolder(folder)
|
||||
}
|
||||
|
||||
parsed.Meta.SetFolder(folder)
|
||||
parsed.Meta.SetUID("") // clear identifiers
|
||||
parsed.Meta.SetResourceVersion("") // clear identifiers
|
||||
// Clear any saved identifiers
|
||||
parsed.Meta.SetUID("")
|
||||
parsed.Meta.SetResourceVersion("")
|
||||
|
||||
// Update will also create (for resources we care about)
|
||||
_, err = parsed.Client.Update(ctx, parsed.Obj, metav1.UpdateOptions{})
|
||||
// Update or Create resource
|
||||
parsed.Upsert, err = parsed.Client.Update(ctx, parsed.Obj, metav1.UpdateOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
parsed.Upsert, err = parsed.Client.Create(ctx, parsed.Obj, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
return parsed.Obj.GetName(), parsed.GVK, err
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export const useGetResourceRepositoryView = ({ name, folderName }: GetResourceRe
|
||||
|
||||
// For nested folders we need to see what the folder thinks
|
||||
const annotatedFolderName = folder?.metadata?.annotations?.[AnnoKeyManagerIdentity];
|
||||
if (annotatedFolderName && name) {
|
||||
if (annotatedFolderName) {
|
||||
repository = items.find((repo) => repo.name === annotatedFolderName);
|
||||
if (repository) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user