Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs

This commit is contained in:
Ryan McKinley
2025-07-10 09:51:51 -07:00
196 changed files with 5867 additions and 14047 deletions
+50 -114
View File
@@ -2,6 +2,7 @@ package dashboards
import (
"context"
"fmt"
"strings"
"testing"
@@ -13,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/infra/slugify"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tests/apis"
@@ -112,74 +114,28 @@ func runDashboardTest(t *testing.T, helper *apis.K8sTestHelper, gvr schema.Group
func TestIntegrationDashboardsAppV0Alpha1(t *testing.T) {
gvr := schema.GroupVersionResource{
Group: dashboardV1.GROUP,
Version: dashboardV1.VERSION,
Group: dashboardV0.GROUP,
Version: dashboardV0.VERSION,
Resource: "dashboards",
}
if testing.Short() {
t.Skip("skipping integration test")
}
t.Run("v0alpha1 with dual writer mode 0", func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: 0,
modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5}
for _, mode := range modes {
t.Run(fmt.Sprintf("v0alpha1 with dual writer mode %d", mode), func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: mode,
},
},
},
})
runDashboardTest(t, helper, gvr)
})
runDashboardTest(t, helper, gvr)
})
t.Run("v0alpha1 with dual writer mode 1", func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: 1,
},
},
})
runDashboardTest(t, helper, gvr)
})
t.Run("v0alpha1 with dual writer mode 2", func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: 2,
},
},
})
runDashboardTest(t, helper, gvr)
})
t.Run("v0alpha1 with dual writer mode 3", func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: 3,
},
},
})
runDashboardTest(t, helper, gvr)
})
t.Run("v0alpha1 with dual writer mode 4", func(t *testing.T) {
t.Skip("skipping test because of authorizer issue")
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: 4,
},
},
})
runDashboardTest(t, helper, gvr)
})
}
}
func TestIntegrationDashboardsAppV1(t *testing.T) {
@@ -192,66 +148,46 @@ func TestIntegrationDashboardsAppV1(t *testing.T) {
t.Skip("skipping integration test")
}
t.Run("v1 with dual writer mode 0", func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: 0,
modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5}
for _, mode := range modes {
t.Run(fmt.Sprintf("v1beta1 with dual writer mode %d", mode), func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: mode,
},
},
},
})
runDashboardTest(t, helper, gvr)
})
runDashboardTest(t, helper, gvr)
})
}
}
t.Run("v1 with dual writer mode 1", func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: 1,
},
},
})
runDashboardTest(t, helper, gvr)
})
func TestIntegrationDashboardsAppV2(t *testing.T) {
gvr := schema.GroupVersionResource{
Group: dashboardV2.GROUP,
Version: dashboardV2.VERSION,
Resource: "dashboards",
}
if testing.Short() {
t.Skip("skipping integration test")
}
t.Run("v1 with dual writer mode 2", func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: 2,
modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5}
for _, mode := range modes {
t.Run(fmt.Sprintf("v1beta1 with dual writer mode %d", mode), func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: mode,
},
},
},
})
runDashboardTest(t, helper, gvr)
})
runDashboardTest(t, helper, gvr)
})
t.Run("v1 with dual writer mode 3", func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: 3,
},
},
})
runDashboardTest(t, helper, gvr)
})
t.Run("v1 with dual writer mode 4", func(t *testing.T) {
t.Skip("skipping test because of authorizer issue")
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: 4,
},
},
})
runDashboardTest(t, helper, gvr)
})
}
}
func TestIntegrationLegacySupport(t *testing.T) {
+5 -1
View File
@@ -18,6 +18,7 @@ import (
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/api/dtos"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -1026,7 +1027,10 @@ func TestIntegrationFoldersGetAPIEndpointK8S(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
t.Skip("not working yet")
if !db.IsTestDbSQLite() {
t.Skip("test only on sqlite for now")
}
type testCase struct {
description string
@@ -2303,6 +2303,7 @@
]
},
"spec": {
"description": "Spec is the spec of the Check",
"default": {},
"allOf": [
{
@@ -2389,6 +2390,33 @@
}
]
},
"com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckReport": {
"type": "object",
"required": [
"count",
"failures"
],
"properties": {
"count": {
"description": "Number of elements analyzed",
"type": "integer",
"format": "int64",
"default": 0
},
"failures": {
"description": "List of failures",
"type": "array",
"items": {
"default": {},
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckReportFailure"
}
]
}
}
}
},
"com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckReportFailure": {
"type": "object",
"required": [
@@ -2479,7 +2507,7 @@
"default": {},
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckV0alpha1StatusReport"
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckReport"
}
]
}
@@ -2510,6 +2538,7 @@
]
},
"spec": {
"description": "Spec is the spec of the CheckType",
"default": {},
"allOf": [
{
@@ -2682,33 +2711,6 @@
}
}
},
"com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckV0alpha1StatusReport": {
"type": "object",
"required": [
"count",
"failures"
],
"properties": {
"count": {
"description": "Number of elements analyzed",
"type": "integer",
"format": "int64",
"default": 0
},
"failures": {
"description": "List of failures",
"type": "array",
"items": {
"default": {},
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckReportFailure"
}
]
}
}
}
},
"com.github.grafana.grafana.apps.advisor.pkg.apis.advisor.v0alpha1.CheckstatusOperatorState": {
"type": "object",
"required": [
@@ -2923,11 +2923,16 @@
"type": "object",
"required": [
"title",
"description",
"version",
"group",
"permissions"
],
"properties": {
"description": {
"type": "string",
"default": ""
},
"group": {
"type": "string",
"default": ""
@@ -3236,11 +3241,16 @@
"type": "object",
"required": [
"title",
"description",
"version",
"group",
"permissions"
],
"properties": {
"description": {
"type": "string",
"default": ""
},
"group": {
"type": "string",
"default": ""
@@ -3723,11 +3733,16 @@
"type": "object",
"required": [
"title",
"description",
"version",
"group",
"permissions"
],
"properties": {
"description": {
"type": "string",
"default": ""
},
"group": {
"type": "string",
"default": ""
+2 -84
View File
@@ -2,10 +2,7 @@ package provisioning
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net/http"
"os"
"path"
"strings"
@@ -13,7 +10,6 @@ import (
"text/template"
"time"
gh "github.com/google/go-github/v70/github"
ghmock "github.com/migueleliasweb/go-github-mock/src/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -224,20 +220,8 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper
}
helper := apis.NewK8sTestHelper(t, opts)
helper.GetEnv().GitHubFactory.Client = ghmock.NewMockedHTTPClient(
ghmock.WithRequestMatchHandler(ghmock.GetUser, ghAlwaysWrite(t, &gh.User{})),
ghmock.WithRequestMatchHandler(ghmock.GetReposHooksByOwnerByRepo, ghAlwaysWrite(t, []*gh.Hook{})),
ghmock.WithRequestMatchHandler(ghmock.PostReposHooksByOwnerByRepo, ghAlwaysWrite(t, &gh.Hook{})),
ghmock.WithRequestMatchHandler(ghmock.GetReposByOwnerByRepo, ghAlwaysWrite(t, &gh.Repository{})),
ghmock.WithRequestMatchHandler(ghmock.GetReposBranchesByOwnerByRepoByBranch, ghAlwaysWrite(t, &gh.Branch{})),
ghmock.WithRequestMatchHandler(ghmock.GetReposGitTreesByOwnerByRepoByTreeSha, ghAlwaysWrite(t, &gh.Tree{})),
ghmock.WithRequestMatchHandler(
ghmock.DeleteReposHooksByOwnerByRepoByHookId,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}),
),
)
// FIXME: keeping this line here to keep the dependency around until we have tests which use this again.
helper.GetEnv().GitHubFactory.Client = ghmock.NewMockedHTTPClient()
repositories := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
@@ -310,33 +294,6 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper
}
}
func ghAlwaysWrite(t *testing.T, body any) http.HandlerFunc {
marshalled := ghmock.MustMarshal(body)
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, err := w.Write(marshalled)
require.NoError(t, err, "failed to write body in mock")
})
}
func ghHandleTree(t *testing.T, refs map[string][]*gh.TreeEntry) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sha := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
require.NotEmpty(t, sha, "sha path parameter was missing?")
entries := refs[sha]
require.NotNil(t, entries, "no entries for sha %s", sha)
tree := &gh.Tree{
SHA: gh.Ptr(sha),
Truncated: gh.Ptr(false),
Entries: entries,
}
_, err := w.Write(ghmock.MustMarshal(tree))
require.NoError(t, err, "failed to write body in mock")
})
}
func mustNestedString(obj map[string]interface{}, fields ...string) string {
v, _, err := unstructured.NestedString(obj, fields...)
if err != nil {
@@ -350,15 +307,6 @@ func asJSON(obj any) []byte {
return jj
}
func treeEntryDir(dirName string, sha string) *gh.TreeEntry {
return &gh.TreeEntry{
SHA: gh.Ptr(sha),
Path: gh.Ptr(dirName),
Type: gh.Ptr("tree"),
Mode: gh.Ptr("040000"),
}
}
func unstructuredToRepository(t *testing.T, obj *unstructured.Unstructured) *provisioning.Repository {
bytes, err := obj.MarshalJSON()
require.NoError(t, err)
@@ -369,33 +317,3 @@ func unstructuredToRepository(t *testing.T, obj *unstructured.Unstructured) *pro
return repo
}
func treeEntry(fpath string, content []byte) *gh.TreeEntry {
sha := sha256.Sum256(content)
return &gh.TreeEntry{
SHA: gh.Ptr(hex.EncodeToString(sha[:])),
Path: gh.Ptr(fpath),
Size: gh.Ptr(len(content)),
Type: gh.Ptr("blob"),
Mode: gh.Ptr("100644"),
Content: gh.Ptr(string(content)),
}
}
func repoContent(fpath string, content []byte) *gh.RepositoryContent {
sha := sha256.Sum256(content)
typ := "blob"
if strings.HasSuffix(fpath, "/") {
typ = "tree"
}
return &gh.RepositoryContent{
SHA: gh.Ptr(hex.EncodeToString(sha[:])),
Name: gh.Ptr(path.Base(fpath)),
Path: &fpath,
Size: gh.Ptr(len(content)),
Type: &typ,
Content: gh.Ptr(string(content)),
}
}
+149 -49
View File
@@ -3,16 +3,14 @@ package provisioning
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
gh "github.com/google/go-github/v70/github"
ghmock "github.com/migueleliasweb/go-github-mock/src/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -173,7 +171,7 @@ func TestIntegrationProvisioning_FailInvalidSchema(t *testing.T) {
}, time.Second*10, time.Millisecond*10, "Expected to be able to start a sync job")
require.EventuallyWithT(t, func(collect *assert.CollectT) {
//helper.TriggerJobProcessing(t)
// helper.TriggerJobProcessing(t)
result, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{},
"jobs", string(jobObj.GetUID()))
@@ -211,50 +209,25 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) {
helper := runGrafana(t)
ctx := context.Background()
helper.GetEnv().GitHubFactory.Client = ghmock.NewMockedHTTPClient(
ghmock.WithRequestMatchHandler(ghmock.GetUser, ghAlwaysWrite(t, &gh.User{Name: gh.Ptr("github-user")})),
ghmock.WithRequestMatchHandler(ghmock.GetReposHooksByOwnerByRepo, ghAlwaysWrite(t, []*gh.Hook{})),
ghmock.WithRequestMatchHandler(ghmock.PostReposHooksByOwnerByRepo, ghAlwaysWrite(t, &gh.Hook{ID: gh.Ptr(int64(123))})),
ghmock.WithRequestMatchHandler(ghmock.GetReposByOwnerByRepo, ghAlwaysWrite(t, &gh.Repository{ID: gh.Ptr(int64(234))})),
ghmock.WithRequestMatchHandler(
ghmock.GetReposBranchesByOwnerByRepoByBranch,
ghAlwaysWrite(t, &gh.Branch{
Name: gh.Ptr("main"),
Commit: &gh.RepositoryCommit{SHA: gh.Ptr("deadbeef")},
}),
),
ghmock.WithRequestMatchHandler(ghmock.GetReposGitTreesByOwnerByRepoByTreeSha,
ghHandleTree(t, map[string][]*gh.TreeEntry{
"deadbeef": {
treeEntryDir("grafana", "subtree"),
},
"subtree": {
treeEntry("dashboard.json", helper.LoadFile("testdata/all-panels.json")),
treeEntryDir("subdir", "subtree2"),
treeEntry("subdir/dashboard2.yaml", helper.LoadFile("testdata/text-options.json")),
},
})),
ghmock.WithRequestMatchHandler(
ghmock.GetReposContentsByOwnerByRepoByPath,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pathRegex := regexp.MustCompile(`/repos/[^/]+/[^/]+/contents/(.*)`)
matches := pathRegex.FindStringSubmatch(r.URL.Path)
require.NotNil(t, matches, "no match for contents?")
path := matches[1]
// FIXME: instead of using an existing GitHub repository, we should create a new one for the tests and a branch
// This was the previous structure
// ghmock.WithRequestMatchHandler(ghmock.GetReposGitTreesByOwnerByRepoByTreeSha,
// ghHandleTree(t, map[string][]*gh.TreeEntry{
// "deadbeef": {
// treeEntryDir("grafana", "subtree"),
// },
// "subtree": {
// treeEntry("dashboard.json", helper.LoadFile("testdata/all-panels.json")),
// treeEntryDir("subdir", "subtree2"),
// treeEntry("subdir/dashboard2.yaml", helper.LoadFile("testdata/text-options.json")),
// },
// })),
var err error
switch path {
case "grafana/dashboard.json":
_, err = w.Write(ghmock.MustMarshal(repoContent(path, helper.LoadFile("testdata/all-panels.json"))))
case "grafana/subdir/dashboard2.yaml":
_, err = w.Write(ghmock.MustMarshal(repoContent(path, helper.LoadFile("testdata/text-options.json"))))
default:
t.Fatalf("got unexpected path: %s", path)
}
require.NoError(t, err)
}),
),
)
// FIXME: uncomment these to implement webhook integration tests.
// helper.GetEnv().GitHubFactory.Client = ghmock.NewMockedHTTPClient(
// ghmock.WithRequestMatchHandler(ghmock.GetReposHooksByOwnerByRepo, ghAlwaysWrite(t, []*gh.Hook{})),
// ghmock.WithRequestMatchHandler(ghmock.PostReposHooksByOwnerByRepo, ghAlwaysWrite(t, &gh.Hook{ID: gh.Ptr(int64(123))})),
// )
const repo = "github-create-test"
_, err := helper.Repositories.Resource.Create(ctx,
@@ -279,8 +252,10 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) {
for _, v := range found.Items {
names = append(names, v.GetName())
}
assert.Contains(t, names, "n1jR8vnnz", "should contain dashboard.json's contents")
assert.Contains(t, names, "WZ7AhQiVz", "should contain dashboard2.yaml's contents")
require.Len(t, names, 3, "should have three dashboards")
assert.Contains(t, names, "adg5vbj", "should contain dashboard.json's contents")
assert.Contains(t, names, "admfz74", "should contain dashboard2.yaml's contents")
assert.Contains(t, names, "adn5mxb", "should contain dashboard2.yaml's contents")
err = helper.Repositories.Resource.Delete(ctx, repo, metav1.DeleteOptions{})
require.NoError(t, err, "should delete values")
@@ -651,3 +626,128 @@ func TestProvisioning_ExportUnifiedToRepository(t *testing.T) {
require.Nil(t, obj["status"], "should not have a status element")
}
}
func TestIntegrationProvisioning_DeleteResources(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
helper := runGrafana(t)
ctx := context.Background()
const repo = "delete-test-repo"
localTmp := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{
"Name": repo,
"SyncEnabled": true,
"SyncTarget": "instance",
})
_, err := helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{})
require.NoError(t, err)
// create the structure:
// dashboard1.json
// folder/
// dashboard2.json
// nested/
// dashboard3.json
dashboard1 := helper.LoadFile("testdata/all-panels.json")
result := helper.AdminREST.Post().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("files", "dashboard1.json").
Body(dashboard1).
SetHeader("Content-Type", "application/json").
Do(ctx)
require.NoError(t, result.Error())
dashboard2 := helper.LoadFile("testdata/text-options.json")
result = helper.AdminREST.Post().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("files", "folder", "dashboard2.json").
Body(dashboard2).
SetHeader("Content-Type", "application/json").
Do(ctx)
require.NoError(t, result.Error())
dashboard3 := helper.LoadFile("testdata/timeline-demo.json")
result = helper.AdminREST.Post().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("files", "folder", "nested", "dashboard3.json").
Body(dashboard3).
SetHeader("Content-Type", "application/json").
Do(ctx)
require.NoError(t, result.Error())
// make sure we don't fail when there is a .keep file in a folder
helper.CopyToProvisioningPath(t, "testdata/.keep", "folder/nested/.keep")
dashboards, err := helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err)
require.Equal(t, 3, len(dashboards.Items))
folders, err := helper.Folders.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err)
require.Equal(t, 2, len(folders.Items))
t.Run("delete individual dashboard file, should delete from repo and grafana", func(t *testing.T) {
result := helper.AdminREST.Delete().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("files", "dashboard1.json").
Do(ctx)
require.NoError(t, result.Error())
_, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "dashboard1.json")
require.Error(t, err)
dashboards, err = helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err)
require.Equal(t, 2, len(dashboards.Items))
})
t.Run("delete folder, should delete from repo and grafana all nested resources too", func(t *testing.T) {
// need to delete directly through the url, because the k8s client doesn't support `/` in a subresource
// but that is needed by gitsync to know that it is a folder
addr := helper.GetEnv().Server.HTTPServer.Listener.Addr().String()
url := fmt.Sprintf("http://admin:admin@%s/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/%s/files/folder/", addr, repo)
req, err := http.NewRequest(http.MethodDelete, url, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
// nolint:errcheck
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
// should be deleted from the repo
_, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "folder")
require.Error(t, err)
_, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "folder", "dashboard2.json")
require.Error(t, err)
_, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "folder", "nested")
require.Error(t, err)
_, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "folder", "nested", "dashboard3.json")
require.Error(t, err)
// all should be deleted from grafana
for _, d := range dashboards.Items {
_, err = helper.DashboardsV1.Resource.Get(ctx, d.GetName(), metav1.GetOptions{})
require.Error(t, err)
}
for _, f := range folders.Items {
_, err = helper.Folders.Resource.Get(ctx, f.GetName(), metav1.GetOptions{})
require.Error(t, err)
}
})
t.Run("deleting a non-existent file should fail", func(t *testing.T) {
result := helper.AdminREST.Delete().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("files", "non-existent.json").
Do(ctx)
require.Error(t, result.Error())
})
}
+1
View File
@@ -0,0 +1 @@
# This file ensures the folder/nested directory is tracked in version control
@@ -9,10 +9,10 @@
"description": "{{ or .Description .Name "Load grafana dashboard from fake repository" }}",
"type": "github",
"github": {
"url": "{{ or .URL "https://github.com/grafana/git-ui-sync-demo" }}",
"branch": "{{ or .Branch "dummy" }}",
"url": "{{ or .URL "https://github.com/grafana/grafana-git-sync-demo" }}",
"branch": "{{ or .Branch "integration-test" }}",
"generateDashboardPreviews": {{ if .GenerateDashboardPreviews }} true {{ else }} false {{ end }},
"token": "{{ or .Token "github_pat_dummy" }}",
"token": "{{ or .Token "" }}",
"path": "{{ or .Path "grafana/" }}"
},
"sync": {
+1
View File
@@ -0,0 +1 @@
../../../../../devenv/dev-dashboards/panel-timeline/timeline-demo.json