Provisioning: Introduce Repository Factory with extras (#110018)

This commit is contained in:
Roberto Jiménez Sánchez
2025-08-26 10:37:47 +02:00
committed by GitHub
parent 6a050ff551
commit e196f13b2b
37 changed files with 1032 additions and 425 deletions
@@ -0,0 +1,83 @@
package github
import (
"context"
"fmt"
"github.com/grafana/grafana-app-sdk/logging"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/git"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/webhooks"
"k8s.io/apimachinery/pkg/runtime"
)
type extra struct {
factory *Factory
decrypter repository.Decrypter
webhookBuilder *webhooks.WebhookExtraBuilder
}
func Extra(decrypter repository.Decrypter, factory *Factory, webhookBuilder *webhooks.WebhookExtraBuilder) repository.Extra {
return &extra{
decrypter: decrypter,
factory: factory,
webhookBuilder: webhookBuilder,
}
}
func (e *extra) Type() provisioning.RepositoryType {
return provisioning.GitHubRepositoryType
}
func (e *extra) Build(ctx context.Context, r *provisioning.Repository) (repository.Repository, error) {
logger := logging.FromContext(ctx).With("url", r.Spec.GitHub.URL, "branch", r.Spec.GitHub.Branch, "path", r.Spec.GitHub.Path)
logger.Info("Instantiating Github repository")
secure := e.decrypter(r)
cfg := r.Spec.GitHub
if cfg == nil {
return nil, fmt.Errorf("github configuration is required")
}
token, err := secure.Token(ctx)
if err != nil {
return nil, fmt.Errorf("unable to decrypt token: %w", err)
}
gitRepo, err := git.NewRepository(ctx, r, git.RepositoryConfig{
URL: cfg.URL,
Branch: cfg.Branch,
Path: cfg.Path,
Token: token,
})
if err != nil {
return nil, fmt.Errorf("error creating git repository: %w", err)
}
ghRepo, err := NewRepository(ctx, r, gitRepo, e.factory, token)
if err != nil {
return nil, fmt.Errorf("error creating github repository: %w", err)
}
if e.webhookBuilder == nil {
return ghRepo, nil
}
webhookURL := e.webhookBuilder.WebhookURL(ctx, r)
if len(webhookURL) == 0 {
logger.Debug("Skipping webhook setup as no webhooks are not configured")
return ghRepo, nil
}
webhookSecret, err := secure.WebhookSecret(ctx)
if err != nil {
return nil, fmt.Errorf("decrypt webhookSecret: %w", err)
}
return NewGithubWebhookRepository(ghRepo, webhookURL, webhookSecret), nil
}
func (e *extra) Mutate(ctx context.Context, obj runtime.Object) error {
return Mutate(ctx, obj)
}
@@ -7,29 +7,26 @@ import (
"k8s.io/apimachinery/pkg/runtime"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/controller"
)
func Mutator() controller.Mutator {
return func(ctx context.Context, obj runtime.Object) error {
repo, ok := obj.(*provisioning.Repository)
if !ok {
return nil
}
if repo.Spec.GitHub == nil {
return nil
}
// Trim trailing ".git" and any trailing slash from the GitHub URL, if present, using the strings package.
if repo.Spec.GitHub.URL != "" {
url := repo.Spec.GitHub.URL
url = strings.TrimRight(url, "/")
url = strings.TrimSuffix(url, ".git")
url = strings.TrimRight(url, "/")
repo.Spec.GitHub.URL = url
}
func Mutate(ctx context.Context, obj runtime.Object) error {
repo, ok := obj.(*provisioning.Repository)
if !ok {
return nil
}
if repo.Spec.GitHub == nil {
return nil
}
// Trim trailing ".git" and any trailing slash from the GitHub URL, if present, using the strings package.
if repo.Spec.GitHub.URL != "" {
url := repo.Spec.GitHub.URL
url = strings.TrimRight(url, "/")
url = strings.TrimSuffix(url, ".git")
url = strings.TrimRight(url, "/")
repo.Spec.GitHub.URL = url
}
return nil
}
@@ -106,9 +106,7 @@ func TestMutator(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mutator := Mutator()
err := mutator(context.Background(), tt.obj)
err := Mutate(context.Background(), tt.obj)
if tt.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
@@ -42,7 +42,7 @@ type GithubRepository interface {
Client() Client
}
func NewGitHub(
func NewRepository(
ctx context.Context,
config *provisioning.Repository,
gitRepo git.GitRepository,
@@ -82,7 +82,7 @@ func TestNewGitHub(t *testing.T) {
gitRepo := git.NewMockGitRepository(t)
// Call the function under test
repo, err := NewGitHub(
repo, err := NewRepository(
context.Background(),
tt.config,
gitRepo,
@@ -0,0 +1,231 @@
{
"action": "created",
"issue": {
"id": 2726065547,
"number": 12,
"state": "open",
"locked": false,
"title": "Webhook test PR",
"author_association": "MEMBER",
"user": {
"login": "ryantxu",
"id": 705951,
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
"html_url": "https://github.com/ryantxu",
"gravatar_id": "",
"type": "User",
"site_admin": false,
"url": "https://api.github.com/users/ryantxu",
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
"followers_url": "https://api.github.com/users/ryantxu/followers",
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
"repos_url": "https://api.github.com/users/ryantxu/repos",
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
},
"comments": 1,
"created_at": "2024-12-09T05:53:14Z",
"updated_at": "2024-12-09T06:03:21Z",
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12",
"html_url": "https://github.com/grafana/git-ui-sync-demo/pull/12",
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/comments",
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/events",
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/labels{/name}",
"repository_url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
"pull_request": {
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12",
"html_url": "https://github.com/grafana/git-ui-sync-demo/pull/12",
"diff_url": "https://github.com/grafana/git-ui-sync-demo/pull/12.diff",
"patch_url": "https://github.com/grafana/git-ui-sync-demo/pull/12.patch"
},
"reactions": {
"total_count": 0,
"+1": 0,
"-1": 0,
"laugh": 0,
"confused": 0,
"heart": 0,
"hooray": 0,
"rocket": 0,
"eyes": 0,
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/reactions"
},
"node_id": "PR_kwDONO4cS86EfDVR",
"draft": false
},
"comment": {
"id": 2527008082,
"node_id": "IC_kwDONO4cS86WnxVS",
"body": "comment in PR",
"user": {
"login": "ryantxu",
"id": 705951,
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
"html_url": "https://github.com/ryantxu",
"gravatar_id": "",
"type": "User",
"site_admin": false,
"url": "https://api.github.com/users/ryantxu",
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
"followers_url": "https://api.github.com/users/ryantxu/followers",
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
"repos_url": "https://api.github.com/users/ryantxu/repos",
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
},
"reactions": {
"total_count": 0,
"+1": 0,
"-1": 0,
"laugh": 0,
"confused": 0,
"heart": 0,
"hooray": 0,
"rocket": 0,
"eyes": 0,
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments/2527008082/reactions"
},
"created_at": "2024-12-09T06:03:19Z",
"updated_at": "2024-12-09T06:03:19Z",
"author_association": "MEMBER",
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments/2527008082",
"html_url": "https://github.com/grafana/git-ui-sync-demo/pull/12#issuecomment-2527008082",
"issue_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12"
},
"repository": {
"id": 888020043,
"node_id": "R_kgDONO4cSw",
"owner": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"html_url": "https://github.com/grafana",
"gravatar_id": "",
"type": "Organization",
"site_admin": false,
"url": "https://api.github.com/users/grafana",
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
"followers_url": "https://api.github.com/users/grafana/followers",
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/grafana/orgs",
"received_events_url": "https://api.github.com/users/grafana/received_events",
"repos_url": "https://api.github.com/users/grafana/repos",
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
},
"name": "git-ui-sync-demo",
"full_name": "grafana/git-ui-sync-demo",
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
"default_branch": "main",
"created_at": "2024-11-13T17:13:33Z",
"pushed_at": "2024-12-09T05:58:00Z",
"updated_at": "2024-12-09T05:58:03Z",
"html_url": "https://github.com/grafana/git-ui-sync-demo",
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
"fork": false,
"forks_count": 0,
"open_issues_count": 9,
"open_issues": 9,
"stargazers_count": 0,
"watchers_count": 0,
"watchers": 0,
"size": 141,
"allow_forking": false,
"web_commit_signoff_required": false,
"archived": false,
"disabled": false,
"private": true,
"has_issues": true,
"has_wiki": true,
"has_pages": false,
"has_projects": true,
"has_downloads": true,
"has_discussions": false,
"is_template": false,
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
"visibility": "internal"
},
"sender": {
"login": "ryantxu",
"id": 705951,
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
"html_url": "https://github.com/ryantxu",
"gravatar_id": "",
"type": "User",
"site_admin": false,
"url": "https://api.github.com/users/ryantxu",
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
"followers_url": "https://api.github.com/users/ryantxu/followers",
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
"repos_url": "https://api.github.com/users/ryantxu/repos",
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
},
"organization": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open \u0026 composable observability platform.",
"url": "https://api.github.com/orgs/grafana",
"events_url": "https://api.github.com/orgs/grafana/events",
"hooks_url": "https://api.github.com/orgs/grafana/hooks",
"issues_url": "https://api.github.com/orgs/grafana/issues",
"members_url": "https://api.github.com/orgs/grafana/members{/member}",
"public_members_url": "https://api.github.com/orgs/grafana/public_members{/member}",
"repos_url": "https://api.github.com/orgs/grafana/repos"
}
}
@@ -0,0 +1,143 @@
{
"zen": "Keep it logically awesome.",
"hook_id": 517704995,
"hook": {
"created_at": "2024-12-09T05:44:20Z",
"updated_at": "2024-12-09T05:44:20Z",
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks/517704995",
"id": 517704995,
"type": "Repository",
"name": "web",
"test_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks/517704995/test",
"ping_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks/517704995/pings",
"last_response": {
"code": null,
"message": null,
"status": "unused"
},
"config": {
"content_type": "form",
"insecure_ssl": "0",
"url": "https://0b71-216-128-0-90.ngrok-free.app/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/github-example-ryan/webhook",
"secret": "********"
},
"events": [
"*"
],
"active": true
},
"repository": {
"id": 888020043,
"node_id": "R_kgDONO4cSw",
"owner": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"html_url": "https://github.com/grafana",
"gravatar_id": "",
"type": "Organization",
"site_admin": false,
"url": "https://api.github.com/users/grafana",
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
"followers_url": "https://api.github.com/users/grafana/followers",
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/grafana/orgs",
"received_events_url": "https://api.github.com/users/grafana/received_events",
"repos_url": "https://api.github.com/users/grafana/repos",
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
},
"name": "git-ui-sync-demo",
"full_name": "grafana/git-ui-sync-demo",
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
"default_branch": "main",
"created_at": "2024-11-13T17:13:33Z",
"pushed_at": "2024-12-08T10:51:24Z",
"updated_at": "2024-11-28T12:53:26Z",
"html_url": "https://github.com/grafana/git-ui-sync-demo",
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
"fork": false,
"forks_count": 0,
"open_issues_count": 8,
"open_issues": 8,
"stargazers_count": 0,
"watchers_count": 0,
"watchers": 0,
"size": 141,
"allow_forking": false,
"web_commit_signoff_required": false,
"archived": false,
"disabled": false,
"private": true,
"has_issues": true,
"has_wiki": true,
"has_pages": false,
"has_projects": true,
"has_downloads": true,
"has_discussions": false,
"is_template": false,
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
"visibility": "internal"
},
"sender": {
"login": "ryantxu",
"id": 705951,
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
"html_url": "https://github.com/ryantxu",
"gravatar_id": "",
"type": "User",
"site_admin": false,
"url": "https://api.github.com/users/ryantxu",
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
"followers_url": "https://api.github.com/users/ryantxu/followers",
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
"repos_url": "https://api.github.com/users/ryantxu/repos",
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
}
}
@@ -0,0 +1,470 @@
{
"action": "opened",
"number": 12,
"pull_request": {
"id": 2222732625,
"number": 12,
"state": "open",
"locked": false,
"title": "Webhook test PR",
"created_at": "2024-12-09T05:53:14Z",
"updated_at": "2024-12-09T05:53:14Z",
"user": {
"login": "ryantxu",
"id": 705951,
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
"html_url": "https://github.com/ryantxu",
"gravatar_id": "",
"type": "User",
"site_admin": false,
"url": "https://api.github.com/users/ryantxu",
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
"followers_url": "https://api.github.com/users/ryantxu/followers",
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
"repos_url": "https://api.github.com/users/ryantxu/repos",
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
},
"draft": false,
"merged": false,
"mergeable_state": "unknown",
"comments": 0,
"commits": 1,
"additions": 1,
"deletions": 0,
"changed_files": 1,
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12",
"html_url": "https://github.com/grafana/git-ui-sync-demo/pull/12",
"issue_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12",
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/ab5446a53df9e5f8bdeed52250f51fad08e822bc",
"diff_url": "https://github.com/grafana/git-ui-sync-demo/pull/12.diff",
"patch_url": "https://github.com/grafana/git-ui-sync-demo/pull/12.patch",
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12/commits",
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/comments",
"review_comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12/comments",
"review_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/comments{/number}",
"review_comments": 0,
"maintainer_can_modify": false,
"author_association": "MEMBER",
"node_id": "PR_kwDONO4cS86EfDVR",
"_links": {
"self": {
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12"
},
"html": {
"href": "https://github.com/grafana/git-ui-sync-demo/pull/12"
},
"issue": {
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12"
},
"comments": {
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/comments"
},
"review_comments": {
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12/comments"
},
"review_comment": {
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/comments{/number}"
},
"commits": {
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12/commits"
},
"statuses": {
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/ab5446a53df9e5f8bdeed52250f51fad08e822bc"
}
},
"head": {
"label": "grafana:dashboard/1733653266690",
"ref": "dashboard/1733653266690",
"sha": "ab5446a53df9e5f8bdeed52250f51fad08e822bc",
"repo": {
"id": 888020043,
"node_id": "R_kgDONO4cSw",
"owner": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"html_url": "https://github.com/grafana",
"gravatar_id": "",
"type": "Organization",
"site_admin": false,
"url": "https://api.github.com/users/grafana",
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
"followers_url": "https://api.github.com/users/grafana/followers",
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/grafana/orgs",
"received_events_url": "https://api.github.com/users/grafana/received_events",
"repos_url": "https://api.github.com/users/grafana/repos",
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
},
"name": "git-ui-sync-demo",
"full_name": "grafana/git-ui-sync-demo",
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
"default_branch": "main",
"created_at": "2024-11-13T17:13:33Z",
"pushed_at": "2024-12-08T10:51:24Z",
"updated_at": "2024-11-28T12:53:26Z",
"html_url": "https://github.com/grafana/git-ui-sync-demo",
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
"fork": false,
"forks_count": 0,
"open_issues_count": 9,
"open_issues": 9,
"stargazers_count": 0,
"watchers_count": 0,
"watchers": 0,
"size": 141,
"allow_rebase_merge": true,
"allow_update_branch": false,
"allow_squash_merge": true,
"allow_merge_commit": true,
"allow_auto_merge": false,
"allow_forking": false,
"web_commit_signoff_required": false,
"delete_branch_on_merge": false,
"use_squash_pr_title_as_default": false,
"squash_merge_commit_title": "COMMIT_OR_PR_TITLE",
"squash_merge_commit_message": "COMMIT_MESSAGES",
"merge_commit_title": "MERGE_MESSAGE",
"merge_commit_message": "PR_TITLE",
"archived": false,
"disabled": false,
"private": true,
"has_issues": true,
"has_wiki": true,
"has_pages": false,
"has_projects": true,
"has_downloads": true,
"has_discussions": false,
"is_template": false,
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
"visibility": "internal"
},
"user": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"html_url": "https://github.com/grafana",
"gravatar_id": "",
"type": "Organization",
"site_admin": false,
"url": "https://api.github.com/users/grafana",
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
"followers_url": "https://api.github.com/users/grafana/followers",
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/grafana/orgs",
"received_events_url": "https://api.github.com/users/grafana/received_events",
"repos_url": "https://api.github.com/users/grafana/repos",
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
}
},
"base": {
"label": "grafana:main",
"ref": "main",
"sha": "6c86a0cdfd220c2fe3518cfaa4a4babf030d9a7a",
"repo": {
"id": 888020043,
"node_id": "R_kgDONO4cSw",
"owner": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"html_url": "https://github.com/grafana",
"gravatar_id": "",
"type": "Organization",
"site_admin": false,
"url": "https://api.github.com/users/grafana",
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
"followers_url": "https://api.github.com/users/grafana/followers",
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/grafana/orgs",
"received_events_url": "https://api.github.com/users/grafana/received_events",
"repos_url": "https://api.github.com/users/grafana/repos",
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
},
"name": "git-ui-sync-demo",
"full_name": "grafana/git-ui-sync-demo",
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
"default_branch": "main",
"created_at": "2024-11-13T17:13:33Z",
"pushed_at": "2024-12-08T10:51:24Z",
"updated_at": "2024-11-28T12:53:26Z",
"html_url": "https://github.com/grafana/git-ui-sync-demo",
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
"fork": false,
"forks_count": 0,
"open_issues_count": 9,
"open_issues": 9,
"stargazers_count": 0,
"watchers_count": 0,
"watchers": 0,
"size": 141,
"allow_rebase_merge": true,
"allow_update_branch": false,
"allow_squash_merge": true,
"allow_merge_commit": true,
"allow_auto_merge": false,
"allow_forking": false,
"web_commit_signoff_required": false,
"delete_branch_on_merge": false,
"use_squash_pr_title_as_default": false,
"squash_merge_commit_title": "COMMIT_OR_PR_TITLE",
"squash_merge_commit_message": "COMMIT_MESSAGES",
"merge_commit_title": "MERGE_MESSAGE",
"merge_commit_message": "PR_TITLE",
"archived": false,
"disabled": false,
"private": true,
"has_issues": true,
"has_wiki": true,
"has_pages": false,
"has_projects": true,
"has_downloads": true,
"has_discussions": false,
"is_template": false,
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
"visibility": "internal"
},
"user": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"html_url": "https://github.com/grafana",
"gravatar_id": "",
"type": "Organization",
"site_admin": false,
"url": "https://api.github.com/users/grafana",
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
"followers_url": "https://api.github.com/users/grafana/followers",
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/grafana/orgs",
"received_events_url": "https://api.github.com/users/grafana/received_events",
"repos_url": "https://api.github.com/users/grafana/repos",
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
}
}
},
"repository": {
"id": 888020043,
"node_id": "R_kgDONO4cSw",
"owner": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"html_url": "https://github.com/grafana",
"gravatar_id": "",
"type": "Organization",
"site_admin": false,
"url": "https://api.github.com/users/grafana",
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
"followers_url": "https://api.github.com/users/grafana/followers",
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/grafana/orgs",
"received_events_url": "https://api.github.com/users/grafana/received_events",
"repos_url": "https://api.github.com/users/grafana/repos",
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
},
"name": "git-ui-sync-demo",
"full_name": "grafana/git-ui-sync-demo",
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
"default_branch": "main",
"created_at": "2024-11-13T17:13:33Z",
"pushed_at": "2024-12-08T10:51:24Z",
"updated_at": "2024-11-28T12:53:26Z",
"html_url": "https://github.com/grafana/git-ui-sync-demo",
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
"fork": false,
"forks_count": 0,
"open_issues_count": 9,
"open_issues": 9,
"stargazers_count": 0,
"watchers_count": 0,
"watchers": 0,
"size": 141,
"allow_forking": false,
"web_commit_signoff_required": false,
"archived": false,
"disabled": false,
"private": true,
"has_issues": true,
"has_wiki": true,
"has_pages": false,
"has_projects": true,
"has_downloads": true,
"has_discussions": false,
"is_template": false,
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
"visibility": "internal"
},
"sender": {
"login": "ryantxu",
"id": 705951,
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
"html_url": "https://github.com/ryantxu",
"gravatar_id": "",
"type": "User",
"site_admin": false,
"url": "https://api.github.com/users/ryantxu",
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
"followers_url": "https://api.github.com/users/ryantxu/followers",
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
"repos_url": "https://api.github.com/users/ryantxu/repos",
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
},
"organization": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open \u0026 composable observability platform.",
"url": "https://api.github.com/orgs/grafana",
"events_url": "https://api.github.com/orgs/grafana/events",
"hooks_url": "https://api.github.com/orgs/grafana/hooks",
"issues_url": "https://api.github.com/orgs/grafana/issues",
"members_url": "https://api.github.com/orgs/grafana/members{/member}",
"public_members_url": "https://api.github.com/orgs/grafana/public_members{/member}",
"repos_url": "https://api.github.com/orgs/grafana/repos"
}
}
@@ -0,0 +1,148 @@
{
"ref": "refs/heads/not-main",
"commits": [
{
"message": "Update README.md\n\ntest message",
"author": {
"name": "Ryan McKinley",
"email": "ryantxu@gmail.com",
"username": "ryantxu"
},
"url": "https://github.com/grafana/git-ui-sync-demo/commit/72096e3adc646c5a5b8a91744f962b12bac06045",
"distinct": true,
"id": "72096e3adc646c5a5b8a91744f962b12bac06045",
"tree_id": "03ff034c54bcefae2f96041f3fb8172f2fe93df3",
"timestamp": "2024-12-09T08:58:00+03:00",
"committer": {
"name": "GitHub",
"email": "noreply@github.com",
"username": "web-flow"
},
"modified": [
"README.md"
]
}
],
"before": "6c86a0cdfd220c2fe3518cfaa4a4babf030d9a7a",
"after": "72096e3adc646c5a5b8a91744f962b12bac06045",
"created": false,
"deleted": false,
"forced": false,
"compare": "https://github.com/grafana/git-ui-sync-demo/compare/6c86a0cdfd22...72096e3adc64",
"repository": {
"id": 888020043,
"node_id": "R_kgDONO4cSw",
"name": "git-ui-sync-demo",
"full_name": "grafana/git-ui-sync-demo",
"owner": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"html_url": "https://github.com/grafana",
"gravatar_id": "",
"name": "grafana",
"email": "hello@grafana.com",
"type": "Organization",
"site_admin": false,
"url": "https://api.github.com/users/grafana",
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
"followers_url": "https://api.github.com/users/grafana/followers",
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/grafana/orgs",
"received_events_url": "https://api.github.com/users/grafana/received_events",
"repos_url": "https://api.github.com/users/grafana/repos",
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
},
"private": true,
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
"fork": false,
"created_at": "2024-11-13T20:13:33+03:00",
"pushed_at": "2024-12-09T08:58:00+03:00",
"updated_at": "2024-11-28T12:53:26Z",
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
"size": 141,
"stargazers_count": 0,
"watchers_count": 0,
"has_issues": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 0,
"archived": false,
"disabled": false,
"open_issues_count": 9,
"default_branch": "main",
"master_branch": "main",
"organization": "grafana",
"url": "https://github.com/grafana/git-ui-sync-demo",
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
"html_url": "https://github.com/grafana/git-ui-sync-demo",
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
"svn_url": "https://github.com/grafana/git-ui-sync-demo"
},
"head_commit": {
"message": "Update README.md\n\ntest message",
"author": {
"name": "Ryan McKinley",
"email": "ryantxu@gmail.com",
"username": "ryantxu"
},
"url": "https://github.com/grafana/git-ui-sync-demo/commit/72096e3adc646c5a5b8a91744f962b12bac06045",
"distinct": true,
"id": "72096e3adc646c5a5b8a91744f962b12bac06045",
"tree_id": "03ff034c54bcefae2f96041f3fb8172f2fe93df3",
"timestamp": "2024-12-09T08:58:00+03:00",
"committer": {
"name": "GitHub",
"email": "noreply@github.com",
"username": "web-flow"
},
"modified": [
"README.md"
]
},
"pusher": {
"name": "ryantxu",
"email": "ryantxu@gmail.com"
},
"sender": {
"login": "ryantxu",
"id": 705951,
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
"html_url": "https://github.com/ryantxu",
"gravatar_id": "",
"type": "User",
"site_admin": false,
"url": "https://api.github.com/users/ryantxu",
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
"followers_url": "https://api.github.com/users/ryantxu/followers",
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
"repos_url": "https://api.github.com/users/ryantxu/repos",
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
},
"organization": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open \u0026 composable observability platform.",
"url": "https://api.github.com/orgs/grafana",
"events_url": "https://api.github.com/orgs/grafana/events",
"hooks_url": "https://api.github.com/orgs/grafana/hooks",
"issues_url": "https://api.github.com/orgs/grafana/issues",
"members_url": "https://api.github.com/orgs/grafana/members{/member}",
"public_members_url": "https://api.github.com/orgs/grafana/public_members{/member}",
"repos_url": "https://api.github.com/orgs/grafana/repos"
}
}
@@ -0,0 +1,234 @@
{
"ref": "refs/heads/main",
"before": "72096e3adc646c5a5b8a91744f962b12bac06045",
"after": "5c816f9812e391c62b0c5555d0b473b296d9179c",
"repository": {
"id": 888020043,
"node_id": "R_kgDONO4cSw",
"name": "git-ui-sync-demo",
"full_name": "grafana/git-ui-sync-demo",
"private": true,
"owner": {
"name": "grafana",
"email": "hello@grafana.com",
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/grafana",
"html_url": "https://github.com/grafana",
"followers_url": "https://api.github.com/users/grafana/followers",
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions",
"organizations_url": "https://api.github.com/users/grafana/orgs",
"repos_url": "https://api.github.com/users/grafana/repos",
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
"received_events_url": "https://api.github.com/users/grafana/received_events",
"type": "Organization",
"user_view_type": "public",
"site_admin": false
},
"html_url": "https://github.com/grafana/git-ui-sync-demo",
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
"fork": false,
"url": "https://github.com/grafana/git-ui-sync-demo",
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
"created_at": 1731518013,
"updated_at": "2024-12-09T05:58:03Z",
"pushed_at": 1733731254,
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
"homepage": null,
"size": 142,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"has_discussions": false,
"forks_count": 0,
"mirror_url": null,
"archived": false,
"disabled": false,
"open_issues_count": 9,
"license": null,
"allow_forking": false,
"is_template": false,
"web_commit_signoff_required": false,
"topics": [
],
"visibility": "internal",
"forks": 0,
"open_issues": 9,
"watchers": 0,
"default_branch": "main",
"stargazers": 0,
"master_branch": "main",
"organization": "grafana",
"custom_properties": {
}
},
"pusher": {
"name": "ryantxu",
"email": "ryantxu@gmail.com"
},
"organization": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"url": "https://api.github.com/orgs/grafana",
"repos_url": "https://api.github.com/orgs/grafana/repos",
"events_url": "https://api.github.com/orgs/grafana/events",
"hooks_url": "https://api.github.com/orgs/grafana/hooks",
"issues_url": "https://api.github.com/orgs/grafana/issues",
"members_url": "https://api.github.com/orgs/grafana/members{/member}",
"public_members_url": "https://api.github.com/orgs/grafana/public_members{/member}",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open & composable observability platform."
},
"enterprise": {
"id": 129980,
"slug": "grafana",
"name": "Grafana Labs Enterprise",
"node_id": "E_kgDOAAH7vA",
"avatar_url": "https://avatars.githubusercontent.com/b/129980?v=4",
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open & composable observability platform.",
"website_url": "https://grafana.com",
"html_url": "https://github.com/enterprises/grafana",
"created_at": "2024-02-29T23:01:47Z",
"updated_at": "2024-10-21T08:45:28Z"
},
"sender": {
"login": "ryantxu",
"id": 705951,
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/ryantxu",
"html_url": "https://github.com/ryantxu",
"followers_url": "https://api.github.com/users/ryantxu/followers",
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions",
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
"repos_url": "https://api.github.com/users/ryantxu/repos",
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
"type": "User",
"user_view_type": "public",
"site_admin": false
},
"created": false,
"deleted": false,
"forced": false,
"base_ref": null,
"compare": "https://github.com/grafana/git-ui-sync-demo/compare/72096e3adc64...5c816f9812e3",
"commits": [
{
"id": "5c816f9812e391c62b0c5555d0b473b296d9179c",
"tree_id": "97c7ba756b07b6a2b7fd130e59f75deed53fe027",
"distinct": true,
"message": "nested folders",
"timestamp": "2024-12-09T11:00:48+03:00",
"url": "https://github.com/grafana/git-ui-sync-demo/commit/5c816f9812e391c62b0c5555d0b473b296d9179c",
"author": {
"name": "Ryan McKinley",
"email": "ryantxu@gmail.com",
"username": "ryantxu"
},
"committer": {
"name": "Ryan McKinley",
"email": "ryantxu@gmail.com",
"username": "ryantxu"
},
"added": [
"nested-1/README.md",
"nested-1/dash-1.json",
"nested-1/nested-2/README.md",
"nested-1/nested-2/dash-2.json"
],
"removed": [
],
"modified": [
"first-dashboard.json"
]
}
],
"head_commit": {
"id": "5c816f9812e391c62b0c5555d0b473b296d9179c",
"tree_id": "97c7ba756b07b6a2b7fd130e59f75deed53fe027",
"distinct": true,
"message": "nested folders",
"timestamp": "2024-12-09T11:00:48+03:00",
"url": "https://github.com/grafana/git-ui-sync-demo/commit/5c816f9812e391c62b0c5555d0b473b296d9179c",
"author": {
"name": "Ryan McKinley",
"email": "ryantxu@gmail.com",
"username": "ryantxu"
},
"committer": {
"name": "Ryan McKinley",
"email": "ryantxu@gmail.com",
"username": "ryantxu"
},
"added": [
"nested-1/README.md",
"nested-1/dash-1.json",
"nested-1/nested-2/README.md",
"nested-1/nested-2/dash-2.json"
],
"removed": [
],
"modified": [
"first-dashboard.json"
]
}
}
@@ -0,0 +1,148 @@
{
"ref": "refs/heads/main",
"commits": [
{
"message": "Update README.md\n\ntest message",
"author": {
"name": "Ryan McKinley",
"email": "ryantxu@gmail.com",
"username": "ryantxu"
},
"url": "https://github.com/grafana/git-ui-sync-demo/commit/72096e3adc646c5a5b8a91744f962b12bac06045",
"distinct": true,
"id": "72096e3adc646c5a5b8a91744f962b12bac06045",
"tree_id": "03ff034c54bcefae2f96041f3fb8172f2fe93df3",
"timestamp": "2024-12-09T08:58:00+03:00",
"committer": {
"name": "GitHub",
"email": "noreply@github.com",
"username": "web-flow"
},
"modified": [
"README.md"
]
}
],
"before": "6c86a0cdfd220c2fe3518cfaa4a4babf030d9a7a",
"after": "72096e3adc646c5a5b8a91744f962b12bac06045",
"created": false,
"deleted": false,
"forced": false,
"compare": "https://github.com/grafana/git-ui-sync-demo/compare/6c86a0cdfd22...72096e3adc64",
"repository": {
"id": 888020043,
"node_id": "R_kgDONO4cSw",
"name": "git-ui-sync-demo",
"full_name": "grafana/git-ui-sync-demo",
"owner": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"html_url": "https://github.com/grafana",
"gravatar_id": "",
"name": "grafana",
"email": "hello@grafana.com",
"type": "Organization",
"site_admin": false,
"url": "https://api.github.com/users/grafana",
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
"followers_url": "https://api.github.com/users/grafana/followers",
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/grafana/orgs",
"received_events_url": "https://api.github.com/users/grafana/received_events",
"repos_url": "https://api.github.com/users/grafana/repos",
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
},
"private": true,
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
"fork": false,
"created_at": "2024-11-13T20:13:33+03:00",
"pushed_at": "2024-12-09T08:58:00+03:00",
"updated_at": "2024-11-28T12:53:26Z",
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
"size": 141,
"stargazers_count": 0,
"watchers_count": 0,
"has_issues": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 0,
"archived": false,
"disabled": false,
"open_issues_count": 9,
"default_branch": "main",
"master_branch": "main",
"organization": "grafana",
"url": "https://github.com/grafana/git-ui-sync-demo",
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
"html_url": "https://github.com/grafana/git-ui-sync-demo",
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
"svn_url": "https://github.com/grafana/git-ui-sync-demo"
},
"head_commit": {
"message": "Update README.md\n\ntest message",
"author": {
"name": "Ryan McKinley",
"email": "ryantxu@gmail.com",
"username": "ryantxu"
},
"url": "https://github.com/grafana/git-ui-sync-demo/commit/72096e3adc646c5a5b8a91744f962b12bac06045",
"distinct": true,
"id": "72096e3adc646c5a5b8a91744f962b12bac06045",
"tree_id": "03ff034c54bcefae2f96041f3fb8172f2fe93df3",
"timestamp": "2024-12-09T08:58:00+03:00",
"committer": {
"name": "GitHub",
"email": "noreply@github.com",
"username": "web-flow"
},
"modified": [
"README.md"
]
},
"pusher": {
"name": "ryantxu",
"email": "ryantxu@gmail.com"
},
"sender": {
"login": "ryantxu",
"id": 705951,
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
"html_url": "https://github.com/ryantxu",
"gravatar_id": "",
"type": "User",
"site_admin": false,
"url": "https://api.github.com/users/ryantxu",
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
"followers_url": "https://api.github.com/users/ryantxu/followers",
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
"repos_url": "https://api.github.com/users/ryantxu/repos",
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
},
"organization": {
"login": "grafana",
"id": 7195757,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open \u0026 composable observability platform.",
"url": "https://api.github.com/orgs/grafana",
"events_url": "https://api.github.com/orgs/grafana/events",
"hooks_url": "https://api.github.com/orgs/grafana/hooks",
"issues_url": "https://api.github.com/orgs/grafana/issues",
"members_url": "https://api.github.com/orgs/grafana/members{/member}",
"public_members_url": "https://api.github.com/orgs/grafana/public_members{/member}",
"repos_url": "https://api.github.com/orgs/grafana/repos"
}
}
@@ -0,0 +1,368 @@
package github
import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"slices"
"github.com/google/go-github/v70/github"
"github.com/google/uuid"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"github.com/grafana/grafana-app-sdk/logging"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
var subscribedEvents = []string{"pull_request", "push"} // same order as slices.Sort()
type WebhookRepository interface {
Webhook(ctx context.Context, req *http.Request) (*provisioning.WebhookResponse, error)
}
type GithubWebhookRepository interface {
GithubRepository
repository.Hooks
WebhookRepository
}
type githubWebhookRepository struct {
GithubRepository
config *provisioning.Repository
owner string
repo string
secret common.RawSecureValue
gh Client
webhookURL string
}
func NewGithubWebhookRepository(
basic GithubRepository,
webhookURL string,
secret common.RawSecureValue,
) GithubWebhookRepository {
return &githubWebhookRepository{
GithubRepository: basic,
config: basic.Config(),
owner: basic.Owner(),
repo: basic.Repo(),
gh: basic.Client(),
webhookURL: webhookURL,
secret: secret,
}
}
// Webhook implements Repository.
func (r *githubWebhookRepository) Webhook(ctx context.Context, req *http.Request) (*provisioning.WebhookResponse, error) {
if r.config.Status.Webhook == nil {
return nil, fmt.Errorf("unexpected webhook request")
}
if r.secret.IsZero() {
return nil, fmt.Errorf("missing webhook secret")
}
payload, err := github.ValidatePayload(req, []byte(r.secret))
if err != nil {
return nil, apierrors.NewUnauthorized("invalid signature")
}
return r.parseWebhook(github.WebHookType(req), payload)
}
// This method does not include context because it does delegate any more requests
func (r *githubWebhookRepository) parseWebhook(messageType string, payload []byte) (*provisioning.WebhookResponse, error) {
event, err := github.ParseWebHook(messageType, payload)
if err != nil {
return nil, apierrors.NewBadRequest("invalid payload")
}
switch event := event.(type) {
case *github.PushEvent:
return r.parsePushEvent(event)
case *github.PullRequestEvent:
return r.parsePullRequestEvent(event)
case *github.PingEvent:
return &provisioning.WebhookResponse{
Code: http.StatusOK,
Message: "ping received",
}, nil
default:
return &provisioning.WebhookResponse{
Code: http.StatusNotImplemented,
Message: fmt.Sprintf("unsupported messageType: %s", messageType),
}, nil
}
}
func (r *githubWebhookRepository) parsePushEvent(event *github.PushEvent) (*provisioning.WebhookResponse, error) {
if event.GetRepo() == nil {
return nil, fmt.Errorf("missing repository in push event")
}
if event.GetRepo().GetFullName() != fmt.Sprintf("%s/%s", r.owner, r.repo) {
return nil, fmt.Errorf("repository mismatch")
}
// No need to sync if not enabled
if !r.config.Spec.Sync.Enabled {
return &provisioning.WebhookResponse{Code: http.StatusOK}, nil
}
// Skip silently if the event is not for the main/master branch
// as we cannot configure the webhook to only publish events for the main branch
if event.GetRef() != fmt.Sprintf("refs/heads/%s", r.config.Spec.GitHub.Branch) {
return &provisioning.WebhookResponse{Code: http.StatusOK}, nil
}
return &provisioning.WebhookResponse{
Code: http.StatusAccepted,
Job: &provisioning.JobSpec{
Repository: r.config.GetName(),
Action: provisioning.JobActionPull,
Pull: &provisioning.SyncJobOptions{
Incremental: true,
},
},
}, nil
}
func (r *githubWebhookRepository) parsePullRequestEvent(event *github.PullRequestEvent) (*provisioning.WebhookResponse, error) {
if event.GetRepo() == nil {
return nil, fmt.Errorf("missing repository in pull request event")
}
cfg := r.config.Spec.GitHub
if cfg == nil {
return nil, fmt.Errorf("missing GitHub config")
}
if event.GetRepo().GetFullName() != fmt.Sprintf("%s/%s", r.owner, r.repo) {
return nil, fmt.Errorf("repository mismatch")
}
pr := event.GetPullRequest()
if pr == nil {
return nil, fmt.Errorf("expected PR in event")
}
if pr.GetBase().GetRef() != r.config.Spec.GitHub.Branch {
return &provisioning.WebhookResponse{
Code: http.StatusOK,
Message: fmt.Sprintf("ignoring pull request event as %s is not the configured branch", pr.GetBase().GetRef()),
}, nil
}
action := event.GetAction()
if action != "opened" && action != "reopened" && action != "synchronize" {
return &provisioning.WebhookResponse{
Code: http.StatusOK, // Nothing needed
Message: fmt.Sprintf("ignore pull request event: %s", action),
}, nil
}
// Queue an async job that will parse files
return &provisioning.WebhookResponse{
Code: http.StatusAccepted, // Nothing needed
Message: fmt.Sprintf("pull request: %s", action),
Job: &provisioning.JobSpec{
Repository: r.config.GetName(),
Action: provisioning.JobActionPullRequest,
PullRequest: &provisioning.PullRequestJobOptions{
URL: pr.GetHTMLURL(),
PR: pr.GetNumber(),
Ref: pr.GetHead().GetRef(),
Hash: pr.GetHead().GetSHA(),
},
},
}, nil
}
// CommentPullRequest adds a comment to a pull request.
func (r *githubWebhookRepository) CommentPullRequest(ctx context.Context, prNumber int, comment string) error {
ctx, _ = r.logger(ctx, "")
return r.gh.CreatePullRequestComment(ctx, r.owner, r.repo, prNumber, comment)
}
func (r *githubWebhookRepository) createWebhook(ctx context.Context) (WebhookConfig, error) {
secret, err := uuid.NewRandom()
if err != nil {
return WebhookConfig{}, fmt.Errorf("could not generate secret: %w", err)
}
cfg := WebhookConfig{
URL: r.webhookURL,
Secret: secret.String(),
ContentType: "json",
Events: subscribedEvents,
Active: true,
}
hook, err := r.gh.CreateWebhook(ctx, r.owner, r.repo, cfg)
if err != nil {
return WebhookConfig{}, err
}
// HACK: GitHub does not return the secret, so we need to update it manually
hook.Secret = cfg.Secret
logging.FromContext(ctx).Info("webhook created", "url", cfg.URL, "id", hook.ID)
return hook, nil
}
// updateWebhook checks if the webhook needs to be updated and updates it if necessary.
// if the webhook does not exist, it will create it.
func (r *githubWebhookRepository) updateWebhook(ctx context.Context) (WebhookConfig, bool, error) {
if r.config.Status.Webhook == nil || r.config.Status.Webhook.ID == 0 {
hook, err := r.createWebhook(ctx)
if err != nil {
return WebhookConfig{}, false, err
}
return hook, true, nil
}
hook, err := r.gh.GetWebhook(ctx, r.owner, r.repo, r.config.Status.Webhook.ID)
switch {
case errors.Is(err, ErrResourceNotFound):
hook, err := r.createWebhook(ctx)
if err != nil {
return WebhookConfig{}, false, err
}
return hook, true, nil
case err != nil:
return WebhookConfig{}, false, fmt.Errorf("get webhook: %w", err)
}
var mustUpdate bool
if hook.URL != r.webhookURL {
mustUpdate = true
hook.URL = r.webhookURL
}
slices.Sort(hook.Events) // consistent order for comparison
if !slices.Equal(hook.Events, subscribedEvents) {
mustUpdate = true
hook.Events = subscribedEvents
}
if !mustUpdate {
return hook, false, nil
}
// Something has changed in the webhook. Let's rotate the secret as well, so as to ensure we end up with a 100% correct webhook.
secret, err := uuid.NewRandom()
if err != nil {
return WebhookConfig{}, false, fmt.Errorf("could not generate secret: %w", err)
}
hook.Secret = secret.String()
if err := r.gh.EditWebhook(ctx, r.owner, r.repo, hook); err != nil {
return WebhookConfig{}, false, fmt.Errorf("edit webhook: %w", err)
}
return hook, true, nil
}
func (r *githubWebhookRepository) deleteWebhook(ctx context.Context) error {
logger := logging.FromContext(ctx)
if r.config.Status.Webhook == nil {
return fmt.Errorf("webhook not found")
}
id := r.config.Status.Webhook.ID
if err := r.gh.DeleteWebhook(ctx, r.owner, r.repo, id); err != nil {
return fmt.Errorf("delete webhook: %w", err)
}
logger.Info("webhook deleted", "url", r.config.Status.Webhook.URL, "id", id)
return nil
}
func (r *githubWebhookRepository) OnCreate(ctx context.Context) ([]map[string]interface{}, error) {
if len(r.webhookURL) == 0 {
return nil, nil
}
ctx, _ = r.logger(ctx, "")
hook, err := r.createWebhook(ctx)
if err != nil {
return nil, err
}
return []map[string]interface{}{
{
"op": "replace",
"path": "/status/webhook",
"value": &provisioning.WebhookStatus{
ID: hook.ID,
URL: hook.URL,
SubscribedEvents: hook.Events,
},
},
{
"op": "replace",
"path": "/secure/webhookSecret",
"value": map[string]string{
"create": hook.Secret,
},
},
}, nil
}
func (r *githubWebhookRepository) OnUpdate(ctx context.Context) ([]map[string]interface{}, error) {
if len(r.webhookURL) == 0 {
return nil, nil
}
ctx, _ = r.logger(ctx, "")
hook, changed, err := r.updateWebhook(ctx)
if err != nil || !changed {
return nil, err
}
// update the webhook and secret
return []map[string]any{{
"op": "replace",
"path": "/status/webhook",
"value": &provisioning.WebhookStatus{
ID: hook.ID,
URL: hook.URL,
SubscribedEvents: hook.Events,
},
}, {
"op": "replace",
"path": "/secure/webhookSecret",
"value": map[string]string{
"create": hook.Secret,
},
}}, nil
}
func (r *githubWebhookRepository) OnDelete(ctx context.Context) error {
if r.config.Status.Webhook == nil {
return nil
}
return r.deleteWebhook(ctx)
}
func (r *githubWebhookRepository) logger(ctx context.Context, ref string) (context.Context, logging.Logger) {
logger := logging.FromContext(ctx)
type containsGh int
var containsGhKey containsGh
if ctx.Value(containsGhKey) != nil {
return ctx, logging.FromContext(ctx)
}
if ref == "" {
ref = r.config.Spec.GitHub.Branch
}
logger = logger.With(slog.Group("github_repository", "owner", r.owner, "name", r.repo, "ref", ref))
ctx = logging.Context(ctx, logger)
// We want to ensure we don't add multiple github_repository keys. With doesn't deduplicate the keys...
ctx = context.WithValue(ctx, containsGhKey, true)
return ctx, logger
}
File diff suppressed because it is too large Load Diff