Provisioning: Fix tests (#101197)

* Provisioning: Refactor tests to be multiple functions

* Provisioning: Fix tests

* fix: make github-example sync
This commit is contained in:
Mariell Hoversholm
2025-02-24 12:05:40 +01:00
committed by GitHub
parent a3d909d564
commit fda4d575a5
2 changed files with 322 additions and 292 deletions
+320 -290
View File
@@ -8,6 +8,7 @@ import (
"net/http"
"os"
"path"
"regexp"
"strings"
"testing"
"time"
@@ -20,11 +21,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v1alpha1"
folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apiserver/rest"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tests/apis"
@@ -36,14 +38,30 @@ func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestIntegrationProvisioning(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
type provisioningTestHelper struct {
*apis.K8sTestHelper
ProvisioningPath string
Repositories *apis.K8sResourceClient
Jobs *apis.K8sResourceClient
Folders *apis.K8sResourceClient
Dashboards *apis.K8sResourceClient
REST *rest.RESTClient
}
type grafanaOption func(opts *testinfra.GrafanaOpts)
// Useful for debugging a test in development.
//
//lint:ignore U1000 This is used when needed while debugging.
//nolint:golint,unused
func withLogs(opts *testinfra.GrafanaOpts) {
opts.EnableLog = true
}
func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper {
provisioningPath := t.TempDir()
ctx := context.Background()
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
opts := testinfra.GrafanaOpts{
AppModeProduction: false, // required for experimental APIs
EnableFeatureToggles: []string{
featuremgmt.FlagProvisioning,
@@ -53,34 +71,50 @@ func TestIntegrationProvisioning(t *testing.T) {
},
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: rest.Mode5,
DualWriterMode: grafanarest.Mode5,
},
"folders.folder.grafana.app": {
DualWriterMode: rest.Mode5,
DualWriterMode: grafanarest.Mode5,
},
},
PermittedProvisioningPaths: ".|" + provisioningPath,
})
}
for _, o := range options {
o(&opts)
}
helper := apis.NewK8sTestHelper(t, opts)
client := helper.GetResourceClient(apis.ResourceClientArgs{
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)
}),
),
)
repositories := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: "default", // actually org1
GVR: provisioning.RepositoryResourceInfo.GroupVersionResource(),
})
jobClient := helper.GetResourceClient(apis.ResourceClientArgs{
jobs := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: "default", // actually org1
GVR: provisioning.JobResourceInfo.GroupVersionResource(),
})
folderClient := helper.GetResourceClient(apis.ResourceClientArgs{
folders := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: "default", // actually org1
GVR: folder.FolderResourceInfo.GroupVersionResource(),
})
dashboardClient := helper.GetResourceClient(apis.ResourceClientArgs{
dashboards := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: "default", // actually org1
GVR: dashboard.DashboardResourceInfo.GroupVersionResource(),
@@ -90,299 +124,296 @@ func TestIntegrationProvisioning(t *testing.T) {
restClient := helper.Org1.Admin.RESTClient(t, &schema.GroupVersion{
Group: "provisioning.grafana.app", Version: "v0alpha1",
})
_ = restClient
cleanSlate := func(t *testing.T) {
helper.GetEnv().GitHubFactory.Client = ghmock.NewMockedHTTPClient(
ghmock.WithRequestMatchHandler(
ghmock.GetUser,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, err := w.Write(ghmock.MustMarshal(&gh.User{}))
require.NoError(t, err)
}),
),
ghmock.WithRequestMatchHandler(
ghmock.GetReposHooksByOwnerByRepo,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, err := w.Write(ghmock.MustMarshal([]*gh.Hook{}))
require.NoError(t, err)
}),
),
ghmock.WithRequestMatchHandler(
ghmock.PostReposHooksByOwnerByRepo,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, err := w.Write(ghmock.MustMarshal(&gh.Hook{}))
require.NoError(t, err)
}),
),
ghmock.WithRequestMatchHandler(
ghmock.GetReposByOwnerByRepo,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, err := w.Write(ghmock.MustMarshal(&gh.Repository{}))
require.NoError(t, err)
}),
),
ghmock.WithRequestMatchHandler(
ghmock.GetReposBranchesByOwnerByRepoByBranch,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, err := w.Write(ghmock.MustMarshal(&gh.Branch{}))
require.NoError(t, err)
}),
),
ghmock.WithRequestMatchHandler(
ghmock.GetReposGitTreesByOwnerByRepoByTreeSha,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, err := w.Write(ghmock.MustMarshal(&gh.Tree{}))
require.NoError(t, err)
}),
),
ghmock.WithRequestMatchHandler(
ghmock.DeleteReposHooksByOwnerByRepoByHookId,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}),
),
)
deleteAll := func(client *apis.K8sResourceClient) error {
list, err := client.Resource.List(ctx, metav1.ListOptions{})
if err != nil {
deleteAll := func(client *apis.K8sResourceClient) error {
ctx := context.Background()
list, err := client.Resource.List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
for _, resource := range list.Items {
if err := client.Resource.Delete(ctx, resource.GetName(), metav1.DeleteOptions{}); err != nil {
return err
}
for _, resource := range list.Items {
if err := client.Resource.Delete(ctx, resource.GetName(), metav1.DeleteOptions{}); err != nil {
return err
}
}
return nil
}
require.NoError(t, deleteAll(dashboardClient), "deleting all dashboards")
require.NoError(t, deleteAll(folderClient), "deleting all folders")
require.NoError(t, deleteAll(client), "deleting all repositories")
return nil
}
cleanSlate(t)
t.Run("Creating and getting repositories", func(t *testing.T) {
cleanSlate(t)
require.NoError(t, deleteAll(dashboards), "deleting all dashboards")
require.NoError(t, deleteAll(folders), "deleting all folders")
require.NoError(t, deleteAll(repositories), "deleting all repositories")
createOptions := metav1.CreateOptions{FieldValidation: "Strict"}
return &provisioningTestHelper{
ProvisioningPath: provisioningPath,
K8sTestHelper: helper,
for _, inputFilePath := range []string{
"testdata/github-example.json",
"testdata/local-conf-provisioning-sample.json",
"testdata/local-devenv.json",
"testdata/local-tmp.json",
"testdata/local-xxx.json",
} {
t.Run(inputFilePath, func(t *testing.T) {
input := helper.LoadYAMLOrJSONFile(inputFilePath)
Repositories: repositories,
REST: restClient,
Jobs: jobs,
Folders: folders,
Dashboards: dashboards,
}
}
_, err := client.Resource.Create(ctx, input, createOptions)
require.NoError(t, err, "failed to create resource")
output, err := client.Resource.Get(ctx, mustNestedString(input.Object, "metadata", "name"), metav1.GetOptions{})
require.NoError(t, err, "failed to read back resource")
// Move encrypted token mutation
token, found, err := unstructured.NestedString(output.Object, "spec", "github", "encryptedToken")
require.NoError(t, err, "encryptedToken is not a string")
if found {
unstructured.RemoveNestedField(input.Object, "spec", "github", "token")
err = unstructured.SetNestedField(input.Object, token, "spec", "github", "encryptedToken")
require.NoError(t, err, "unable to copy encrypted token")
}
expectedOutput, err := json.MarshalIndent(input.Object["spec"], "", " ")
require.NoError(t, err, "failed to marshal JSON from input spec")
outputJSON, err := json.MarshalIndent(output.Object["spec"], "", " ")
require.NoError(t, err, "failed to marshal JSON from read back resource")
require.JSONEq(t, string(expectedOutput), string(outputJSON))
})
}
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")
})
}
t.Run("creating GitHub repository syncs from branch selected", func(t *testing.T) {
cleanSlate(t)
func TestIntegrationProvisioning_CreatingAndGetting(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
helper.GetEnv().GitHubFactory.Client = ghmock.NewMockedHTTPClient(
ghmock.WithRequestMatch(ghmock.GetUser, gh.User{Name: gh.Ptr("github-user")}),
ghmock.WithRequestMatch(ghmock.GetReposHooksByOwnerByRepo, []*gh.Hook{}),
ghmock.WithRequestMatch(ghmock.PostReposHooksByOwnerByRepo, gh.Hook{ID: gh.Ptr(int64(123))}),
ghmock.WithRequestMatch(ghmock.GetReposByOwnerByRepo, gh.Repository{ID: gh.Ptr(int64(234))}),
ghmock.WithRequestMatch(ghmock.GetReposBranchesByOwnerByRepoByBranch, gh.Branch{}),
ghmock.WithRequestMatch(ghmock.GetReposGitTreesByOwnerByRepoByTreeSha, gh.Tree{
SHA: gh.Ptr("deadbeef"),
Truncated: gh.Ptr(false),
Entries: []*gh.TreeEntry{
treeEntry("README.md", []byte("# Hello, World!")),
treeEntry("dashboard.json", helper.LoadFile("testdata/all-panels.json")),
treeEntry("subdir/dashboard2.yaml", helper.LoadFile("testdata/text-options.json")),
},
helper := runGrafana(t)
createOptions := metav1.CreateOptions{FieldValidation: "Strict"}
ctx := context.Background()
for _, inputFilePath := range []string{
"testdata/github-example.json",
"testdata/local-conf-provisioning-sample.json",
"testdata/local-devenv.json",
"testdata/local-tmp.json",
"testdata/local-xxx.json",
} {
t.Run(inputFilePath, func(t *testing.T) {
input := helper.LoadYAMLOrJSONFile(inputFilePath)
_, err := helper.Repositories.Resource.Create(ctx, input, createOptions)
require.NoError(t, err, "failed to create resource")
output, err := helper.Repositories.Resource.Get(ctx, mustNestedString(input.Object, "metadata", "name"), metav1.GetOptions{})
require.NoError(t, err, "failed to read back resource")
// Move encrypted token mutation
token, found, err := unstructured.NestedString(output.Object, "spec", "github", "encryptedToken")
require.NoError(t, err, "encryptedToken is not a string")
if found {
unstructured.RemoveNestedField(input.Object, "spec", "github", "token")
err = unstructured.SetNestedField(input.Object, token, "spec", "github", "encryptedToken")
require.NoError(t, err, "unable to copy encrypted token")
}
expectedOutput, err := json.MarshalIndent(input.Object["spec"], "", " ")
require.NoError(t, err, "failed to marshal JSON from input spec")
outputJSON, err := json.MarshalIndent(output.Object["spec"], "", " ")
require.NoError(t, err, "failed to marshal JSON from read back resource")
require.JSONEq(t, string(expectedOutput), string(outputJSON))
})
}
}
func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
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.GetReposContentsByOwnerByRepoByPath,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
switch r.PathValue("path") {
case "README.md":
_, err = w.Write(ghmock.MustMarshal(repoContent("README.md", []byte("# Hello, World"))))
case "dashboard.json":
_, err = w.Write(ghmock.MustMarshal(repoContent("dashboard.json", helper.LoadFile("testdata/all-panels.json"))))
case "subdir/dashboard2.yaml":
_, err = w.Write(ghmock.MustMarshal(repoContent("subdir/dashboard2.yaml", helper.LoadFile("testdata/text-options.json"))))
}
require.NoError(t, err)
}),
),
)
),
ghmock.WithRequestMatchHandler(ghmock.GetReposGitTreesByOwnerByRepoByTreeSha, ghAlwaysWrite(t, &gh.Tree{
SHA: gh.Ptr("deadbeef"),
Truncated: gh.Ptr(false),
Entries: []*gh.TreeEntry{
treeEntry("README.md", []byte("# Hello, World!")),
treeEntry("dashboard.json", helper.LoadFile("testdata/all-panels.json")),
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]
_, err := client.Resource.Update(ctx,
helper.LoadYAMLOrJSONFile("testdata/github-example.json"),
metav1.UpdateOptions{},
)
require.NoError(t, err)
var err error
switch path {
case "README.md":
_, err = w.Write(ghmock.MustMarshal(repoContent("README.md", []byte("# Hello, World"))))
case "dashboard.json":
_, err = w.Write(ghmock.MustMarshal(repoContent("dashboard.json", helper.LoadFile("testdata/all-panels.json"))))
case "subdir/dashboard2.yaml":
_, err = w.Write(ghmock.MustMarshal(repoContent("subdir/dashboard2.yaml", helper.LoadFile("testdata/text-options.json"))))
default:
t.Fatalf("got unexpected path: %s", path)
}
require.NoError(t, err)
}),
),
)
require.EventuallyWithT(t, func(collect *assert.CollectT) {
list, err := jobClient.Resource.List(ctx, metav1.ListOptions{})
if assert.NoError(collect, err) {
for _, elem := range list.Items {
state := mustNestedString(elem.Object, "status", "state")
if elem.GetLabels()["repository"] == "github-example" {
if state == string(provisioning.JobStateSuccess) {
continue // doesn't matter
}
require.NotEqual(t, provisioning.JobStateError, state, "no jobs may error, but %s did", elem.GetName())
collect.Errorf("there are still remaining github-example jobs: %v", elem)
return
_, err := helper.Repositories.Resource.Update(ctx,
helper.LoadYAMLOrJSONFile("testdata/github-example.json"),
metav1.UpdateOptions{},
)
require.NoError(t, err)
require.EventuallyWithT(t, func(collect *assert.CollectT) {
list, err := helper.Jobs.Resource.List(ctx, metav1.ListOptions{})
if assert.NoError(collect, err) {
for _, elem := range list.Items {
state := mustNestedString(elem.Object, "status", "state")
if elem.GetLabels()["repository"] == "github-example" {
if state == string(provisioning.JobStateSuccess) {
continue // doesn't matter
}
require.NotEqual(t, provisioning.JobStateError, state, "no jobs may error, but %s did", elem.GetName())
collect.Errorf("there are still remaining github-example jobs: %v", elem)
return
}
}
repo, err := client.Resource.Get(ctx, "github-example", metav1.GetOptions{})
if assert.NoError(collect, err) {
assert.Equal(collect, true, mustNested(repo.Object, "status", "health", "healthy"))
assert.Equal(collect, "success", mustNestedString(repo.Object, "status", "sync", "state"))
}
}, time.Second*5, time.Millisecond*20)
// By now, we should have synced, meaning we have data to read in the local Grafana instance!
found, err := dashboardClient.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err, "can list values")
names := []string{}
for _, v := range found.Items {
names = append(names, v.GetName())
}
require.Contains(t, names, "dashboard-R-GC4gTF44qh", "should contain dashboard.json's contents")
require.Contains(t, names, "dashboard2-1jw3H-Mqm75v", "should contain dashboard2.yaml's contents")
})
t.Run("safe path usages", func(t *testing.T) {
cleanSlate(t)
repo, err := helper.Repositories.Resource.Get(ctx, "github-example", metav1.GetOptions{})
if assert.NoError(collect, err) {
assert.Equal(collect, true, mustNested(repo.Object, "status", "health", "healthy"))
assert.Equal(collect, "success", mustNestedString(repo.Object, "status", "sync", "state"))
}
}, time.Second*5, time.Millisecond*20)
_, err := client.Resource.Update(ctx,
helper.LoadYAMLOrJSONFile("testdata/local-devenv.json"),
metav1.UpdateOptions{},
)
// By now, we should have synced, meaning we have data to read in the local Grafana instance!
found, err := helper.Dashboards.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err, "can list values")
names := []string{}
for _, v := range found.Items {
names = append(names, v.GetName())
}
require.Contains(t, names, "n1jR8vnnz", "should contain dashboard.json's contents")
require.Contains(t, names, "WZ7AhQiVz", "should contain dashboard2.yaml's contents")
}
func TestIntegrationProvisioning_SafePathUsages(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
helper := runGrafana(t)
ctx := context.Background()
_, err := helper.Repositories.Resource.Update(ctx,
helper.LoadYAMLOrJSONFile("testdata/local-devenv.json"),
metav1.UpdateOptions{},
)
require.NoError(t, err)
const repo = "local-devenv"
result := helper.REST.Post().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("files", "all-panels.json").
Body(helper.LoadFile("testdata/all-panels.json")).
Do(ctx)
require.NoError(t, result.Error(), "expecting to be able to create file")
result = helper.REST.Post().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("files", "test", "..", "..", "all-panels.json").
Body(helper.LoadFile("testdata/all-panels.json")).
Do(ctx)
require.Error(t, result.Error(), "invalid path should return error")
_, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "all-panels.json")
require.NoError(t, err, "valid path should be fine")
_, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "../../all-panels.json")
require.Error(t, err, "invalid path should not be fine")
}
func TestIntegrationProvisioning_ImportAllPanelsFromLocalRepository(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
helper := runGrafana(t)
ctx := context.Background()
const repo = "local-tmp"
// Create the repository.
repoPath := path.Join(helper.ProvisioningPath, repo, randomAsciiStr(10))
err := os.MkdirAll(repoPath, 0700)
require.NoError(t, err, "should be able to create repo path")
localTmp := helper.LoadYAMLOrJSONFile("testdata/local-tmp.json")
require.NoError(t, unstructured.SetNestedField(localTmp.Object, repoPath, "spec", "local", "path"))
_, err = helper.Repositories.Resource.Create(ctx, localTmp, metav1.CreateOptions{})
require.NoError(t, err)
err = os.WriteFile(path.Join(repoPath, "all-panels.json"), helper.LoadFile("testdata/all-panels.json"), 0600)
require.NoError(t, err, "expecting to be able to create file")
// Make sure the repo can see the file
_, err = helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "all-panels.json")
require.NoError(t, err, "valid path should be fine")
// But the dashboard shouldn't exist yet
const allPanels = "n1jR8vnnz"
_, err = helper.Dashboards.Resource.Get(ctx, allPanels, metav1.GetOptions{})
require.Error(t, err, "no all-panels dashboard should exist")
// Now, we import it, such that it may exist
result := helper.REST.Post().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("sync").
Body(asJSON(provisioning.SyncJobOptions{
Incremental: false,
})).
Do(ctx)
obj, err := result.Get()
require.NoError(t, err, "expecting to be able to sync repository")
obj2, ok := obj.(*unstructured.Unstructured)
if !ok {
require.Fail(t, "expected unstructured response, %T", obj)
}
job := obj2.GetName()
require.NotEmpty(t, job)
// Wait for the async job to finish
for i := 0; i < 10; i++ {
time.Sleep(time.Millisecond * 250)
job, err := helper.Jobs.Resource.Get(ctx, job, metav1.GetOptions{})
require.NoError(t, err)
const repo = "local-devenv"
result := restClient.Post().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("files", "all-panels.json").
Body(helper.LoadFile("testdata/all-panels.json")).
Do(ctx)
require.NoError(t, result.Error(), "expecting to be able to create file")
result = restClient.Post().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("files", "test", "..", "..", "all-panels.json").
Body(helper.LoadFile("testdata/all-panels.json")).
Do(ctx)
require.Error(t, result.Error(), "invalid path should return error")
_, err = client.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "all-panels.json")
require.NoError(t, err, "valid path should be fine")
_, err = client.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "../../all-panels.json")
require.Error(t, err, "invalid path should not be fine")
})
t.Run("import all-panels from local-repository", func(t *testing.T) {
cleanSlate(t)
const repo = "local-tmp"
// Create the repository.
repoPath := path.Join(provisioningPath, repo, randomAsciiStr(10))
err := os.MkdirAll(repoPath, 0700)
require.NoError(t, err, "should be able to create repo path")
localTmp := helper.LoadYAMLOrJSONFile("testdata/local-tmp.json")
require.NoError(t, unstructured.SetNestedField(localTmp.Object, repoPath, "spec", "local", "path"))
_, err = client.Resource.Create(ctx, localTmp, metav1.CreateOptions{})
state, _, err := unstructured.NestedString(job.Object, "status", "state")
require.NoError(t, err)
err = os.WriteFile(path.Join(repoPath, "all-panels.json"), helper.LoadFile("testdata/all-panels.json"), 0600)
require.NoError(t, err, "expecting to be able to create file")
// Make sure the repo can see the file
_, err = client.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "all-panels.json")
require.NoError(t, err, "valid path should be fine")
// But the dashboard shouldn't exist yet
const allPanels = "all-panels-hallaxjov44rbumtikbi1sbzroco9"
_, err = dashboardClient.Resource.Get(ctx, allPanels, metav1.GetOptions{})
require.Error(t, err, "no all-panels dashboard should exist")
// Now, we import it, such that it may exist
result := restClient.Post().
Namespace("default").
Resource("repositories").
Name(repo).
SubResource("sync").
Body(asJSON(provisioning.SyncJobOptions{
Incremental: false,
})).
Do(ctx)
obj, err := result.Get()
require.NoError(t, err, "expecting to be able to sync repository")
obj2, ok := obj.(*unstructured.Unstructured)
if !ok {
require.Fail(t, "expected unstructured response, %T", obj)
if provisioning.JobState(state).Finished() {
break
}
job := obj2.GetName()
require.NotEmpty(t, job)
}
// Wait for the async job to finish
for i := 0; i < 10; i++ {
time.Sleep(time.Millisecond * 250)
job, err := jobClient.Resource.Get(ctx, job, metav1.GetOptions{})
require.NoError(t, err)
found, err := helper.Dashboards.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err, "can list values")
state, _, err := unstructured.NestedString(job.Object, "status", "state")
require.NoError(t, err)
if provisioning.JobState(state).Finished() {
break
}
}
found, err := dashboardClient.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err, "can list values")
names := []string{}
for _, v := range found.Items {
names = append(names, v.GetName())
}
require.Contains(t, names, allPanels, "all-panels dashboard should now exist")
})
names := []string{}
for _, v := range found.Items {
names = append(names, v.GetName())
}
require.Contains(t, names, allPanels, "all-panels dashboard should now exist")
}
func mustNestedString(obj map[string]interface{}, fields ...string) string {
@@ -438,12 +469,11 @@ func repoContent(fpath string, content []byte) *gh.RepositoryContent {
}
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)),
Encoding: gh.Ptr("UTF-8"),
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)),
}
}
+2 -2
View File
@@ -15,8 +15,8 @@
"token": "github_pat_dummy"
},
"sync": {
"enabled": false,
"target": "",
"enabled": true,
"target": "folder",
"intervalSeconds": 60
},
"workflows": ["branch", "write"]