Provisioning: Improve sample provisioning (#103508)

This commit is contained in:
Ryan McKinley
2025-04-07 12:37:18 +03:00
committed by GitHub
parent 4db8d8530d
commit 1faa855ff1
11 changed files with 121 additions and 337 deletions
@@ -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
}