From fda4d575a5bdb5f0a4fcf93a010fb27aab5f013f Mon Sep 17 00:00:00 2001 From: Mariell Hoversholm Date: Mon, 24 Feb 2025 12:05:40 +0100 Subject: [PATCH] Provisioning: Fix tests (#101197) * Provisioning: Refactor tests to be multiple functions * Provisioning: Fix tests * fix: make github-example sync --- .../apis/provisioning/provisioning_test.go | 610 +++++++++--------- .../provisioning/testdata/github-example.json | 4 +- 2 files changed, 322 insertions(+), 292 deletions(-) diff --git a/pkg/tests/apis/provisioning/provisioning_test.go b/pkg/tests/apis/provisioning/provisioning_test.go index ea2694a1e19..fd225e22d95 100644 --- a/pkg/tests/apis/provisioning/provisioning_test.go +++ b/pkg/tests/apis/provisioning/provisioning_test.go @@ -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)), } } diff --git a/pkg/tests/apis/provisioning/testdata/github-example.json b/pkg/tests/apis/provisioning/testdata/github-example.json index 7e4c11493c5..990e6afa2db 100644 --- a/pkg/tests/apis/provisioning/testdata/github-example.json +++ b/pkg/tests/apis/provisioning/testdata/github-example.json @@ -15,8 +15,8 @@ "token": "github_pat_dummy" }, "sync": { - "enabled": false, - "target": "", + "enabled": true, + "target": "folder", "intervalSeconds": 60 }, "workflows": ["branch", "write"]