Provisioning: Use Nanogit for basic git operations in Github repository type (#107889)

This commit is contained in:
Roberto Jiménez Sánchez
2025-07-10 09:46:38 -07:00
committed by GitHub
parent 9df15d120d
commit 7e0848294e
75 changed files with 2426 additions and 12362 deletions
+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)),
}
}
@@ -7,13 +7,10 @@ import (
"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"
@@ -174,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()))
@@ -212,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,
@@ -280,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")
@@ -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": {