provisioning: add integration tests to avoid managed folder permission changes

This commit is contained in:
Gonzalo Trigueros
2026-01-14 09:17:37 +01:00
parent 2804351da1
commit a276557b0a
@@ -0,0 +1,193 @@
package provisioning
import (
"fmt"
"net/http"
"testing"
"time"
foldersV1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/util/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
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/types"
)
// We currently block permission updates for folders managed by provisioning.
func TestIntegrationFolderPermissions_ProvisionedFolders(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := runGrafana(t)
helper.CreateRepo(t, TestRepo{
Name: "test-repo",
Target: "folder",
ExpectedFolders: 1,
})
t.Run("should fail to update permissions for provisioned folder", func(t *testing.T) {
folders, err := helper.Folders.Resource.List(t.Context(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, folders.Items, 1)
managedFolderName := folders.Items[0].GetName()
permissionsPayload := map[string]interface{}{
"items": []map[string]interface{}{
{
"role": "Viewer",
"permission": 1, // View permission
},
},
}
permissionsURL := fmt.Sprintf("/api/folders/%s/permissions", managedFolderName)
permissionsData, code, err := postHelper(t, *helper.K8sTestHelper, permissionsURL, permissionsPayload, helper.Org1.Admin)
require.Error(t, err)
require.Equal(t, http.StatusForbidden, code)
require.NotNil(t, permissionsData)
require.Equal(t, "Cannot update permissions for folders managed by provisioning.", permissionsData["message"])
})
t.Run("should fail to update permissions for nested provisioned folder", func(t *testing.T) {
// Create a new repo with nested folder structure
nestedHelper := runGrafana(t)
nestedHelper.CreateRepo(t, TestRepo{
Name: "nested-folder-repo",
Target: "instance",
Copies: map[string]string{
"testdata/all-panels.json": "folder/subfolder/dashboard.json",
},
SkipResourceAssertions: true,
})
folders, err := nestedHelper.Folders.Resource.List(t.Context(), metav1.ListOptions{})
require.NoError(t, err)
require.GreaterOrEqual(t, len(folders.Items), 2, "should have at least 2 folders (root and nested)")
// Find the nested folder by checking for sourcePath annotation
// The root folder has sourcePath equal to the repo name, nested folders have paths like "folder" or "folder/subfolder"
var nestedFolder *unstructured.Unstructured
repoName := "nested-folder-repo"
for i := range folders.Items {
sourcePath, found, _ := unstructured.NestedString(folders.Items[i].Object, "metadata", "annotations", utils.AnnoKeySourcePath)
if found && sourcePath != "" && sourcePath != repoName {
// This is a nested folder (not the root folder)
nestedFolder = &folders.Items[i]
break
}
}
require.NotNil(t, nestedFolder, "should find a nested folder")
nestedFolderName := nestedFolder.GetName()
require.Contains(t, nestedFolder.GetAnnotations(), utils.AnnoKeyManagerKind, "nested folder should be managed")
require.Contains(t, nestedFolder.GetAnnotations(), utils.AnnoKeyManagerIdentity, "nested folder should be managed")
permissionsPayload := map[string]interface{}{
"items": []map[string]interface{}{
{
"role": "Viewer",
"permission": 1, // View permission
},
},
}
permissionsURL := fmt.Sprintf("/api/folders/%s/permissions", nestedFolderName)
permissionsData, code, err := postHelper(t, *nestedHelper.K8sTestHelper, permissionsURL, permissionsPayload, nestedHelper.Org1.Admin)
require.Error(t, err)
require.Equal(t, http.StatusForbidden, code)
require.NotNil(t, permissionsData)
require.Equal(t, "Cannot update permissions for folders managed by provisioning.", permissionsData["message"])
})
}
func TestIntegrationFolderPermissions_UnprovisionedFolders(t *testing.T) {
const repo = "test-repo"
helper := runGrafana(t)
helper.CreateRepo(t, TestRepo{
Name: repo,
Target: "folder",
ExpectedFolders: 1,
})
t.Run("should update permissions when folder is released", func(t *testing.T) {
folders, err := helper.Folders.Resource.List(t.Context(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, folders.Items, 1)
managedFolderName := folders.Items[0].GetName()
require.Contains(t, folders.Items[0].GetAnnotations(), utils.AnnoKeyManagerKind, "folder should be managed")
require.Contains(t, folders.Items[0].GetAnnotations(), utils.AnnoKeyManagerIdentity, "folder should be managed")
_, err = helper.Repositories.Resource.Patch(t.Context(), repo, types.JSONPatchType, []byte(`[
{
"op": "replace",
"path": "/metadata/finalizers",
"value": ["cleanup", "release-orphan-resources"]
}
]`), metav1.PatchOptions{})
require.NoError(t, err, "should successfully patch finalizers")
require.NoError(t, helper.Repositories.Resource.Delete(t.Context(), repo, metav1.DeleteOptions{}))
require.EventuallyWithT(t, func(collect *assert.CollectT) {
_, err := helper.Repositories.Resource.Get(t.Context(), repo, metav1.GetOptions{})
assert.True(collect, apierrors.IsNotFound(err), "repository should be deleted")
}, time.Second*10, time.Millisecond*50, "repository should be deleted")
require.EventuallyWithT(t, func(collect *assert.CollectT) {
foundFolders, err := helper.Folders.Resource.List(t.Context(), metav1.ListOptions{})
require.NoError(t, err, "can list values")
for _, v := range foundFolders.Items {
assert.NotContains(t, v.GetAnnotations(), utils.AnnoKeyManagerKind)
assert.NotContains(t, v.GetAnnotations(), utils.AnnoKeyManagerIdentity)
assert.NotContains(t, v.GetAnnotations(), utils.AnnoKeySourcePath)
assert.NotContains(t, v.GetAnnotations(), utils.AnnoKeySourceChecksum)
}
}, time.Second*20, time.Millisecond*10, "Expected folders to be released")
permissionsPayload := map[string]interface{}{
"items": []map[string]interface{}{
{
"role": "Viewer",
"permission": 1, // View permission
},
},
}
permissionsURL := fmt.Sprintf("/api/folders/%s/permissions", managedFolderName)
permissionsData, code, err := postHelper(t, *helper.K8sTestHelper, permissionsURL, permissionsPayload, helper.Org1.Admin)
require.NoError(t, err)
require.Equal(t, http.StatusOK, code)
require.NotNil(t, permissionsData)
})
t.Run("should update permissions for unmanaged folder", func(t *testing.T) {
unmanagedFolder := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": foldersV1.FolderResourceInfo.GroupVersion().String(),
"kind": foldersV1.FolderResourceInfo.GroupVersionKind().Kind,
"metadata": map[string]interface{}{
"generateName": "test-folder-",
},
"spec": map[string]interface{}{
"title": "Unmanaged Folder",
},
},
}
createdFolder, err := helper.Folders.Resource.Create(t.Context(), unmanagedFolder, metav1.CreateOptions{})
require.NoError(t, err)
require.NotNil(t, createdFolder)
unmanagedFolderName := createdFolder.GetName()
permissionsPayload := map[string]interface{}{
"items": []map[string]interface{}{
{
"role": "Editor",
"permission": 2, // Edit permission
},
},
}
permissionsURL := fmt.Sprintf("/api/folders/%s/permissions", unmanagedFolderName)
permissionsData, code, err := postHelper(t, *helper.K8sTestHelper, permissionsURL, permissionsPayload, helper.Org1.Admin)
require.NoError(t, err)
require.Equal(t, http.StatusOK, code)
require.NotNil(t, permissionsData)
})
}