Provisioning: Move repository package to provisioning app (#110228)
* Move repository package to apps
* Move operators to grafana/grafana
* Go mod tidy
* Own package by git sync team for now
* Merged
* Do not use settings in local extra
* Remove dependency on webhook extra
* Hack to work around issue with secure contracts
* Sync Go modules
* Revert "Move operators to grafana/grafana"
This reverts commit 9f19b30a2e.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
// The github package exists to provide a client for the GH API, which can also be faked with a mock.
|
||||
// In most cases, we want the real client, but testing should mock it, lest we get blocked from their API, or have to configure auth for simple tests.
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
// API errors that we need to convey after parsing real GH errors (or faking them).
|
||||
var (
|
||||
ErrResourceNotFound = errors.New("the resource does not exist")
|
||||
//lint:ignore ST1005 this is not punctuation
|
||||
ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
|
||||
ErrTooManyItems = errors.New("maximum number of items exceeded")
|
||||
)
|
||||
|
||||
//go:generate mockery --name Client --structname MockClient --inpackage --filename mock_client.go --with-expecter
|
||||
type Client interface {
|
||||
// Commits
|
||||
Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error)
|
||||
|
||||
// Webhooks
|
||||
ListWebhooks(ctx context.Context, owner, repository string) ([]WebhookConfig, error)
|
||||
CreateWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) (WebhookConfig, error)
|
||||
GetWebhook(ctx context.Context, owner, repository string, webhookID int64) (WebhookConfig, error)
|
||||
DeleteWebhook(ctx context.Context, owner, repository string, webhookID int64) error
|
||||
EditWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) error
|
||||
|
||||
// Pull requests
|
||||
ListPullRequestFiles(ctx context.Context, owner, repository string, number int) ([]CommitFile, error)
|
||||
CreatePullRequestComment(ctx context.Context, owner, repository string, number int, body string) error
|
||||
}
|
||||
|
||||
type CommitAuthor struct {
|
||||
Name string
|
||||
Username string
|
||||
AvatarURL string
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
Ref string
|
||||
Message string
|
||||
Author *CommitAuthor
|
||||
Committer *CommitAuthor
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
//go:generate mockery --name CommitFile --structname MockCommitFile --inpackage --filename mock_commit_file.go --with-expecter
|
||||
type CommitFile interface {
|
||||
GetSHA() string
|
||||
GetFilename() string
|
||||
GetPreviousFilename() string
|
||||
GetStatus() string
|
||||
}
|
||||
|
||||
type WebhookConfig struct {
|
||||
// The ID of the webhook.
|
||||
// Can be 0 on creation.
|
||||
ID int64
|
||||
// The events which this webhook shall contact the URL for.
|
||||
Events []string
|
||||
// Is the webhook enabled?
|
||||
Active bool
|
||||
// The URL GitHub should contact on events.
|
||||
URL string
|
||||
// The content type GitHub should send to the URL.
|
||||
// If not specified, this is "form".
|
||||
ContentType string
|
||||
// The secret to use when sending events to the URL.
|
||||
// If fetched from GitHub, this is empty as it contains no useful information.
|
||||
Secret string
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
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/apps/provisioning/pkg/repository"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/repository/git"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
type WebhookURLBuilder interface {
|
||||
WebhookURL(ctx context.Context, r *provisioning.Repository) string
|
||||
}
|
||||
|
||||
type extra struct {
|
||||
factory *Factory
|
||||
decrypter repository.Decrypter
|
||||
webhookBuilder WebhookURLBuilder
|
||||
}
|
||||
|
||||
func Extra(decrypter repository.Decrypter, factory *Factory, webhookBuilder WebhookURLBuilder) 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)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/go-github/v70/github"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
)
|
||||
|
||||
// Factory creates new GitHub clients.
|
||||
// It exists only for the ability to test the code easily.
|
||||
type Factory struct {
|
||||
// Client allows overriding the client to use in the GH client returned. It exists primarily for testing.
|
||||
// FIXME: we should replace in this way. We should add some options pattern for the factory.
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
func ProvideFactory() *Factory {
|
||||
return &Factory{}
|
||||
}
|
||||
|
||||
func (r *Factory) New(ctx context.Context, ghToken common.RawSecureValue) Client {
|
||||
if r.Client != nil {
|
||||
return NewClient(github.NewClient(r.Client))
|
||||
}
|
||||
|
||||
if !ghToken.IsZero() {
|
||||
tokenSrc := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: string(ghToken)},
|
||||
)
|
||||
tokenClient := oauth2.NewClient(ctx, tokenSrc)
|
||||
return NewClient(github.NewClient(tokenClient))
|
||||
}
|
||||
|
||||
return NewClient(github.NewClient(&http.Client{}))
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,333 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v70/github"
|
||||
)
|
||||
|
||||
type githubClient struct {
|
||||
gh *github.Client
|
||||
}
|
||||
|
||||
func NewClient(client *github.Client) Client {
|
||||
return &githubClient{client}
|
||||
}
|
||||
|
||||
const (
|
||||
maxCommits = 1000 // Maximum number of commits to fetch
|
||||
maxWebhooks = 100 // Maximum number of webhooks allowed per repository
|
||||
maxPRFiles = 1000 // Maximum number of files allowed in a pull request
|
||||
)
|
||||
|
||||
// Commits returns a list of commits for a given repository and branch.
|
||||
func (r *githubClient) Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error) {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.RepositoryCommit, *github.Response, error) {
|
||||
return r.gh.Repositories.ListCommits(ctx, owner, repository, &github.CommitsListOptions{
|
||||
Path: path,
|
||||
SHA: branch,
|
||||
ListOptions: *opts,
|
||||
})
|
||||
}
|
||||
|
||||
commits, err := paginatedList(
|
||||
ctx,
|
||||
listFn,
|
||||
defaultListOptions(maxCommits),
|
||||
)
|
||||
if errors.Is(err, ErrTooManyItems) {
|
||||
return nil, fmt.Errorf("too many commits to fetch (more than %d)", maxCommits)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]Commit, 0, len(commits))
|
||||
for _, c := range commits {
|
||||
// FIXME: This code is a mess. I am pretty sure that we have issue in
|
||||
// some situations
|
||||
var createdAt time.Time
|
||||
var author *CommitAuthor
|
||||
if c.GetCommit().GetAuthor() != nil {
|
||||
author = &CommitAuthor{
|
||||
Name: c.GetCommit().GetAuthor().GetName(),
|
||||
Username: c.GetAuthor().GetLogin(),
|
||||
AvatarURL: c.GetAuthor().GetAvatarURL(),
|
||||
}
|
||||
|
||||
createdAt = c.GetCommit().GetAuthor().GetDate().Time
|
||||
}
|
||||
|
||||
var committer *CommitAuthor
|
||||
if c.GetCommitter() != nil {
|
||||
committer = &CommitAuthor{
|
||||
Name: c.GetCommit().GetCommitter().GetName(),
|
||||
Username: c.GetCommitter().GetLogin(),
|
||||
AvatarURL: c.GetCommitter().GetAvatarURL(),
|
||||
}
|
||||
}
|
||||
|
||||
ret = append(ret, Commit{
|
||||
Ref: c.GetSHA(),
|
||||
Message: c.GetCommit().GetMessage(),
|
||||
Author: author,
|
||||
Committer: committer,
|
||||
CreatedAt: createdAt,
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) ListWebhooks(ctx context.Context, owner, repository string) ([]WebhookConfig, error) {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
|
||||
return r.gh.Repositories.ListHooks(ctx, owner, repository, opts)
|
||||
}
|
||||
|
||||
hooks, err := paginatedList(
|
||||
ctx,
|
||||
listFn,
|
||||
defaultListOptions(maxWebhooks),
|
||||
)
|
||||
if errors.Is(err, ErrTooManyItems) {
|
||||
return nil, fmt.Errorf("too many webhooks configured (more than %d)", maxWebhooks)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pre-allocate the result slice
|
||||
ret := make([]WebhookConfig, 0, len(hooks))
|
||||
for _, h := range hooks {
|
||||
contentType := h.GetConfig().GetContentType()
|
||||
if contentType == "" {
|
||||
contentType = "form"
|
||||
}
|
||||
|
||||
ret = append(ret, WebhookConfig{
|
||||
ID: h.GetID(),
|
||||
Events: h.Events,
|
||||
Active: h.GetActive(),
|
||||
URL: h.GetConfig().GetURL(),
|
||||
ContentType: contentType,
|
||||
// Intentionally not setting Secret.
|
||||
})
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CreateWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) (WebhookConfig, error) {
|
||||
if cfg.ContentType == "" {
|
||||
cfg.ContentType = "form"
|
||||
}
|
||||
|
||||
hook := &github.Hook{
|
||||
URL: &cfg.URL,
|
||||
Events: cfg.Events,
|
||||
Active: &cfg.Active,
|
||||
Config: &github.HookConfig{
|
||||
ContentType: &cfg.ContentType,
|
||||
Secret: &cfg.Secret,
|
||||
URL: &cfg.URL,
|
||||
},
|
||||
}
|
||||
|
||||
createdHook, _, err := r.gh.Repositories.CreateHook(ctx, owner, repository, hook)
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return WebhookConfig{}, ErrServiceUnavailable
|
||||
}
|
||||
if err != nil {
|
||||
return WebhookConfig{}, err
|
||||
}
|
||||
|
||||
return WebhookConfig{
|
||||
ID: createdHook.GetID(),
|
||||
// events is not returned by GitHub.
|
||||
Events: cfg.Events,
|
||||
Active: createdHook.GetActive(),
|
||||
URL: createdHook.GetConfig().GetURL(),
|
||||
ContentType: createdHook.GetConfig().GetContentType(),
|
||||
// Secret is not returned by GitHub.
|
||||
Secret: cfg.Secret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) GetWebhook(ctx context.Context, owner, repository string, webhookID int64) (WebhookConfig, error) {
|
||||
hook, _, err := r.gh.Repositories.GetHook(ctx, owner, repository, webhookID)
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return WebhookConfig{}, ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return WebhookConfig{}, ErrResourceNotFound
|
||||
}
|
||||
return WebhookConfig{}, err
|
||||
}
|
||||
|
||||
contentType := hook.GetConfig().GetContentType()
|
||||
if contentType == "" {
|
||||
// FIXME: Not sure about the value of the contentType
|
||||
// we default to form in the other ones but to JSON here
|
||||
contentType = "json"
|
||||
}
|
||||
|
||||
return WebhookConfig{
|
||||
ID: hook.GetID(),
|
||||
Events: hook.Events,
|
||||
Active: hook.GetActive(),
|
||||
URL: hook.GetConfig().GetURL(),
|
||||
ContentType: contentType,
|
||||
// Intentionally not setting Secret.
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) DeleteWebhook(ctx context.Context, owner, repository string, webhookID int64) error {
|
||||
_, err := r.gh.Repositories.DeleteHook(ctx, owner, repository, webhookID)
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *githubClient) EditWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) error {
|
||||
if cfg.ContentType == "" {
|
||||
cfg.ContentType = "form"
|
||||
}
|
||||
|
||||
hook := &github.Hook{
|
||||
URL: &cfg.URL,
|
||||
Events: cfg.Events,
|
||||
Active: &cfg.Active,
|
||||
Config: &github.HookConfig{
|
||||
ContentType: &cfg.ContentType,
|
||||
Secret: &cfg.Secret,
|
||||
URL: &cfg.URL,
|
||||
},
|
||||
}
|
||||
_, _, err := r.gh.Repositories.EditHook(ctx, owner, repository, cfg.ID, hook)
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *githubClient) ListPullRequestFiles(ctx context.Context, owner, repository string, number int) ([]CommitFile, error) {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.CommitFile, *github.Response, error) {
|
||||
return r.gh.PullRequests.ListFiles(ctx, owner, repository, number, opts)
|
||||
}
|
||||
|
||||
files, err := paginatedList(
|
||||
ctx,
|
||||
listFn,
|
||||
defaultListOptions(maxPRFiles),
|
||||
)
|
||||
if errors.Is(err, ErrTooManyItems) {
|
||||
return nil, fmt.Errorf("pull request contains too many files (more than %d)", maxPRFiles)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to the interface type
|
||||
ret := make([]CommitFile, 0, len(files))
|
||||
for _, f := range files {
|
||||
ret = append(ret, f)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CreatePullRequestComment(ctx context.Context, owner, repository string, number int, body string) error {
|
||||
comment := &github.IssueComment{
|
||||
Body: &body,
|
||||
}
|
||||
|
||||
if _, _, err := r.gh.Issues.CreateComment(ctx, owner, repository, number, comment); err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// listOptions represents pagination parameters for list operations
|
||||
type listOptions struct {
|
||||
github.ListOptions
|
||||
MaxItems int
|
||||
}
|
||||
|
||||
// defaultListOptions returns a ListOptions with sensible defaults
|
||||
func defaultListOptions(maxItems int) listOptions {
|
||||
return listOptions{
|
||||
ListOptions: github.ListOptions{
|
||||
Page: 1,
|
||||
PerPage: 100,
|
||||
},
|
||||
MaxItems: maxItems,
|
||||
}
|
||||
}
|
||||
|
||||
// paginatedList is a generic function to handle GitHub API pagination
|
||||
func paginatedList[T any](
|
||||
ctx context.Context,
|
||||
listFn func(context.Context, *github.ListOptions) ([]T, *github.Response, error),
|
||||
opts listOptions,
|
||||
) ([]T, error) {
|
||||
var allItems []T
|
||||
|
||||
for {
|
||||
items, resp, err := listFn(ctx, &opts.ListOptions)
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return nil, err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return nil, ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return nil, ErrResourceNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pre-allocate the slice if this is the first page
|
||||
if allItems == nil {
|
||||
allItems = make([]T, 0, len(items)*2) // Estimate double the first page size
|
||||
}
|
||||
|
||||
allItems = append(allItems, items...)
|
||||
|
||||
// Check if we've exceeded the maximum allowed items
|
||||
if len(allItems) > opts.MaxItems {
|
||||
return nil, ErrTooManyItems
|
||||
}
|
||||
|
||||
// If there are no more pages, break
|
||||
if resp.NextPage == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Set up next page
|
||||
opts.Page = resp.NextPage
|
||||
}
|
||||
|
||||
return allItems, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,485 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockClient is an autogenerated mock type for the Client type
|
||||
type MockClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockClient_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockClient) EXPECT() *MockClient_Expecter {
|
||||
return &MockClient_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Commits provides a mock function with given fields: ctx, owner, repository, path, branch
|
||||
func (_m *MockClient) Commits(ctx context.Context, owner string, repository string, path string, branch string) ([]Commit, error) {
|
||||
ret := _m.Called(ctx, owner, repository, path, branch)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Commits")
|
||||
}
|
||||
|
||||
var r0 []Commit
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) ([]Commit, error)); ok {
|
||||
return rf(ctx, owner, repository, path, branch)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) []Commit); ok {
|
||||
r0 = rf(ctx, owner, repository, path, branch)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]Commit)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) error); ok {
|
||||
r1 = rf(ctx, owner, repository, path, branch)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_Commits_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commits'
|
||||
type MockClient_Commits_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Commits is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - path string
|
||||
// - branch string
|
||||
func (_e *MockClient_Expecter) Commits(ctx interface{}, owner interface{}, repository interface{}, path interface{}, branch interface{}) *MockClient_Commits_Call {
|
||||
return &MockClient_Commits_Call{Call: _e.mock.On("Commits", ctx, owner, repository, path, branch)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_Commits_Call) Run(run func(ctx context.Context, owner string, repository string, path string, branch string)) *MockClient_Commits_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_Commits_Call) Return(_a0 []Commit, _a1 error) *MockClient_Commits_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_Commits_Call) RunAndReturn(run func(context.Context, string, string, string, string) ([]Commit, error)) *MockClient_Commits_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CreatePullRequestComment provides a mock function with given fields: ctx, owner, repository, number, body
|
||||
func (_m *MockClient) CreatePullRequestComment(ctx context.Context, owner string, repository string, number int, body string) error {
|
||||
ret := _m.Called(ctx, owner, repository, number, body)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreatePullRequestComment")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int, string) error); ok {
|
||||
r0 = rf(ctx, owner, repository, number, body)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClient_CreatePullRequestComment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePullRequestComment'
|
||||
type MockClient_CreatePullRequestComment_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CreatePullRequestComment is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - number int
|
||||
// - body string
|
||||
func (_e *MockClient_Expecter) CreatePullRequestComment(ctx interface{}, owner interface{}, repository interface{}, number interface{}, body interface{}) *MockClient_CreatePullRequestComment_Call {
|
||||
return &MockClient_CreatePullRequestComment_Call{Call: _e.mock.On("CreatePullRequestComment", ctx, owner, repository, number, body)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreatePullRequestComment_Call) Run(run func(ctx context.Context, owner string, repository string, number int, body string)) *MockClient_CreatePullRequestComment_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int), args[4].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreatePullRequestComment_Call) Return(_a0 error) *MockClient_CreatePullRequestComment_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreatePullRequestComment_Call) RunAndReturn(run func(context.Context, string, string, int, string) error) *MockClient_CreatePullRequestComment_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CreateWebhook provides a mock function with given fields: ctx, owner, repository, cfg
|
||||
func (_m *MockClient) CreateWebhook(ctx context.Context, owner string, repository string, cfg WebhookConfig) (WebhookConfig, error) {
|
||||
ret := _m.Called(ctx, owner, repository, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreateWebhook")
|
||||
}
|
||||
|
||||
var r0 WebhookConfig
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, WebhookConfig) (WebhookConfig, error)); ok {
|
||||
return rf(ctx, owner, repository, cfg)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, WebhookConfig) WebhookConfig); ok {
|
||||
r0 = rf(ctx, owner, repository, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(WebhookConfig)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, WebhookConfig) error); ok {
|
||||
r1 = rf(ctx, owner, repository, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_CreateWebhook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateWebhook'
|
||||
type MockClient_CreateWebhook_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CreateWebhook is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - cfg WebhookConfig
|
||||
func (_e *MockClient_Expecter) CreateWebhook(ctx interface{}, owner interface{}, repository interface{}, cfg interface{}) *MockClient_CreateWebhook_Call {
|
||||
return &MockClient_CreateWebhook_Call{Call: _e.mock.On("CreateWebhook", ctx, owner, repository, cfg)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreateWebhook_Call) Run(run func(ctx context.Context, owner string, repository string, cfg WebhookConfig)) *MockClient_CreateWebhook_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(WebhookConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreateWebhook_Call) Return(_a0 WebhookConfig, _a1 error) *MockClient_CreateWebhook_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_CreateWebhook_Call) RunAndReturn(run func(context.Context, string, string, WebhookConfig) (WebhookConfig, error)) *MockClient_CreateWebhook_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteWebhook provides a mock function with given fields: ctx, owner, repository, webhookID
|
||||
func (_m *MockClient) DeleteWebhook(ctx context.Context, owner string, repository string, webhookID int64) error {
|
||||
ret := _m.Called(ctx, owner, repository, webhookID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteWebhook")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) error); ok {
|
||||
r0 = rf(ctx, owner, repository, webhookID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClient_DeleteWebhook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteWebhook'
|
||||
type MockClient_DeleteWebhook_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteWebhook is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - webhookID int64
|
||||
func (_e *MockClient_Expecter) DeleteWebhook(ctx interface{}, owner interface{}, repository interface{}, webhookID interface{}) *MockClient_DeleteWebhook_Call {
|
||||
return &MockClient_DeleteWebhook_Call{Call: _e.mock.On("DeleteWebhook", ctx, owner, repository, webhookID)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_DeleteWebhook_Call) Run(run func(ctx context.Context, owner string, repository string, webhookID int64)) *MockClient_DeleteWebhook_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int64))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_DeleteWebhook_Call) Return(_a0 error) *MockClient_DeleteWebhook_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_DeleteWebhook_Call) RunAndReturn(run func(context.Context, string, string, int64) error) *MockClient_DeleteWebhook_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// EditWebhook provides a mock function with given fields: ctx, owner, repository, cfg
|
||||
func (_m *MockClient) EditWebhook(ctx context.Context, owner string, repository string, cfg WebhookConfig) error {
|
||||
ret := _m.Called(ctx, owner, repository, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for EditWebhook")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, WebhookConfig) error); ok {
|
||||
r0 = rf(ctx, owner, repository, cfg)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockClient_EditWebhook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EditWebhook'
|
||||
type MockClient_EditWebhook_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// EditWebhook is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - cfg WebhookConfig
|
||||
func (_e *MockClient_Expecter) EditWebhook(ctx interface{}, owner interface{}, repository interface{}, cfg interface{}) *MockClient_EditWebhook_Call {
|
||||
return &MockClient_EditWebhook_Call{Call: _e.mock.On("EditWebhook", ctx, owner, repository, cfg)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_EditWebhook_Call) Run(run func(ctx context.Context, owner string, repository string, cfg WebhookConfig)) *MockClient_EditWebhook_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(WebhookConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_EditWebhook_Call) Return(_a0 error) *MockClient_EditWebhook_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_EditWebhook_Call) RunAndReturn(run func(context.Context, string, string, WebhookConfig) error) *MockClient_EditWebhook_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetWebhook provides a mock function with given fields: ctx, owner, repository, webhookID
|
||||
func (_m *MockClient) GetWebhook(ctx context.Context, owner string, repository string, webhookID int64) (WebhookConfig, error) {
|
||||
ret := _m.Called(ctx, owner, repository, webhookID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetWebhook")
|
||||
}
|
||||
|
||||
var r0 WebhookConfig
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (WebhookConfig, error)); ok {
|
||||
return rf(ctx, owner, repository, webhookID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) WebhookConfig); ok {
|
||||
r0 = rf(ctx, owner, repository, webhookID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(WebhookConfig)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) error); ok {
|
||||
r1 = rf(ctx, owner, repository, webhookID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_GetWebhook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWebhook'
|
||||
type MockClient_GetWebhook_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetWebhook is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - webhookID int64
|
||||
func (_e *MockClient_Expecter) GetWebhook(ctx interface{}, owner interface{}, repository interface{}, webhookID interface{}) *MockClient_GetWebhook_Call {
|
||||
return &MockClient_GetWebhook_Call{Call: _e.mock.On("GetWebhook", ctx, owner, repository, webhookID)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetWebhook_Call) Run(run func(ctx context.Context, owner string, repository string, webhookID int64)) *MockClient_GetWebhook_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int64))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetWebhook_Call) Return(_a0 WebhookConfig, _a1 error) *MockClient_GetWebhook_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetWebhook_Call) RunAndReturn(run func(context.Context, string, string, int64) (WebhookConfig, error)) *MockClient_GetWebhook_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListPullRequestFiles provides a mock function with given fields: ctx, owner, repository, number
|
||||
func (_m *MockClient) ListPullRequestFiles(ctx context.Context, owner string, repository string, number int) ([]CommitFile, error) {
|
||||
ret := _m.Called(ctx, owner, repository, number)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListPullRequestFiles")
|
||||
}
|
||||
|
||||
var r0 []CommitFile
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int) ([]CommitFile, error)); ok {
|
||||
return rf(ctx, owner, repository, number)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int) []CommitFile); ok {
|
||||
r0 = rf(ctx, owner, repository, number)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]CommitFile)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, int) error); ok {
|
||||
r1 = rf(ctx, owner, repository, number)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_ListPullRequestFiles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPullRequestFiles'
|
||||
type MockClient_ListPullRequestFiles_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListPullRequestFiles is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
// - number int
|
||||
func (_e *MockClient_Expecter) ListPullRequestFiles(ctx interface{}, owner interface{}, repository interface{}, number interface{}) *MockClient_ListPullRequestFiles_Call {
|
||||
return &MockClient_ListPullRequestFiles_Call{Call: _e.mock.On("ListPullRequestFiles", ctx, owner, repository, number)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_ListPullRequestFiles_Call) Run(run func(ctx context.Context, owner string, repository string, number int)) *MockClient_ListPullRequestFiles_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_ListPullRequestFiles_Call) Return(_a0 []CommitFile, _a1 error) *MockClient_ListPullRequestFiles_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_ListPullRequestFiles_Call) RunAndReturn(run func(context.Context, string, string, int) ([]CommitFile, error)) *MockClient_ListPullRequestFiles_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListWebhooks provides a mock function with given fields: ctx, owner, repository
|
||||
func (_m *MockClient) ListWebhooks(ctx context.Context, owner string, repository string) ([]WebhookConfig, error) {
|
||||
ret := _m.Called(ctx, owner, repository)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListWebhooks")
|
||||
}
|
||||
|
||||
var r0 []WebhookConfig
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]WebhookConfig, error)); ok {
|
||||
return rf(ctx, owner, repository)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []WebhookConfig); ok {
|
||||
r0 = rf(ctx, owner, repository)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]WebhookConfig)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, owner, repository)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_ListWebhooks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListWebhooks'
|
||||
type MockClient_ListWebhooks_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListWebhooks is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - owner string
|
||||
// - repository string
|
||||
func (_e *MockClient_Expecter) ListWebhooks(ctx interface{}, owner interface{}, repository interface{}) *MockClient_ListWebhooks_Call {
|
||||
return &MockClient_ListWebhooks_Call{Call: _e.mock.On("ListWebhooks", ctx, owner, repository)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_ListWebhooks_Call) Run(run func(ctx context.Context, owner string, repository string)) *MockClient_ListWebhooks_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_ListWebhooks_Call) Return(_a0 []WebhookConfig, _a1 error) *MockClient_ListWebhooks_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_ListWebhooks_Call) RunAndReturn(run func(context.Context, string, string) ([]WebhookConfig, error)) *MockClient_ListWebhooks_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockClient(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockClient {
|
||||
mock := &MockClient{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package github
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
|
||||
// MockCommitFile is an autogenerated mock type for the CommitFile type
|
||||
type MockCommitFile struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockCommitFile_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockCommitFile) EXPECT() *MockCommitFile_Expecter {
|
||||
return &MockCommitFile_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetFilename provides a mock function with no fields
|
||||
func (_m *MockCommitFile) GetFilename() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetFilename")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCommitFile_GetFilename_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFilename'
|
||||
type MockCommitFile_GetFilename_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetFilename is a helper method to define mock.On call
|
||||
func (_e *MockCommitFile_Expecter) GetFilename() *MockCommitFile_GetFilename_Call {
|
||||
return &MockCommitFile_GetFilename_Call{Call: _e.mock.On("GetFilename")}
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetFilename_Call) Run(run func()) *MockCommitFile_GetFilename_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetFilename_Call) Return(_a0 string) *MockCommitFile_GetFilename_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetFilename_Call) RunAndReturn(run func() string) *MockCommitFile_GetFilename_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetPreviousFilename provides a mock function with no fields
|
||||
func (_m *MockCommitFile) GetPreviousFilename() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPreviousFilename")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCommitFile_GetPreviousFilename_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPreviousFilename'
|
||||
type MockCommitFile_GetPreviousFilename_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetPreviousFilename is a helper method to define mock.On call
|
||||
func (_e *MockCommitFile_Expecter) GetPreviousFilename() *MockCommitFile_GetPreviousFilename_Call {
|
||||
return &MockCommitFile_GetPreviousFilename_Call{Call: _e.mock.On("GetPreviousFilename")}
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetPreviousFilename_Call) Run(run func()) *MockCommitFile_GetPreviousFilename_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetPreviousFilename_Call) Return(_a0 string) *MockCommitFile_GetPreviousFilename_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetPreviousFilename_Call) RunAndReturn(run func() string) *MockCommitFile_GetPreviousFilename_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetSHA provides a mock function with no fields
|
||||
func (_m *MockCommitFile) GetSHA() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSHA")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCommitFile_GetSHA_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSHA'
|
||||
type MockCommitFile_GetSHA_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetSHA is a helper method to define mock.On call
|
||||
func (_e *MockCommitFile_Expecter) GetSHA() *MockCommitFile_GetSHA_Call {
|
||||
return &MockCommitFile_GetSHA_Call{Call: _e.mock.On("GetSHA")}
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetSHA_Call) Run(run func()) *MockCommitFile_GetSHA_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetSHA_Call) Return(_a0 string) *MockCommitFile_GetSHA_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetSHA_Call) RunAndReturn(run func() string) *MockCommitFile_GetSHA_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetStatus provides a mock function with no fields
|
||||
func (_m *MockCommitFile) GetStatus() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetStatus")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCommitFile_GetStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetStatus'
|
||||
type MockCommitFile_GetStatus_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetStatus is a helper method to define mock.On call
|
||||
func (_e *MockCommitFile_Expecter) GetStatus() *MockCommitFile_GetStatus_Call {
|
||||
return &MockCommitFile_GetStatus_Call{Call: _e.mock.On("GetStatus")}
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetStatus_Call) Run(run func()) *MockCommitFile_GetStatus_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetStatus_Call) Return(_a0 string) *MockCommitFile_GetStatus_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCommitFile_GetStatus_Call) RunAndReturn(run func() string) *MockCommitFile_GetStatus_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockCommitFile creates a new instance of MockCommitFile. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockCommitFile(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockCommitFile {
|
||||
mock := &MockCommitFile{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
func TestMutator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Object
|
||||
token string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "trims trailing .git and slash from GitHub URL",
|
||||
obj: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "repo1",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
URL: "https://github.com/org/repo.git/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "trims only trailing slash from GitHub URL",
|
||||
obj: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "repo2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
URL: "https://github.com/org/repo/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "trims only trailing .git from GitHub URL",
|
||||
obj: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "repo3",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
URL: "https://github.com/org/repo.git",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "does not trim if no .git or slash",
|
||||
obj: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "repo4",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
URL: "https://github.com/org/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no github spec",
|
||||
obj: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty token",
|
||||
obj: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-repository object",
|
||||
obj: &runtime.Unknown{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := Mutate(context.Background(), tt.obj)
|
||||
if tt.expectedError != "" {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedError)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/repository"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/repository/git"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/safepath"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
)
|
||||
|
||||
// Make sure all public functions of this struct call the (*githubRepository).logger function, to ensure the GH repo details are included.
|
||||
type githubRepository struct {
|
||||
git.GitRepository
|
||||
config *provisioning.Repository
|
||||
gh Client // assumes github.com base URL
|
||||
|
||||
owner string
|
||||
repo string
|
||||
}
|
||||
|
||||
// GithubRepository is an interface that combines all repository capabilities
|
||||
// needed for GitHub repositories.
|
||||
|
||||
//go:generate mockery --name GithubRepository --structname MockGithubRepository --inpackage --filename github_repository_mock.go --with-expecter
|
||||
type GithubRepository interface {
|
||||
repository.Repository
|
||||
repository.Versioned
|
||||
repository.Writer
|
||||
repository.Reader
|
||||
repository.RepositoryWithURLs
|
||||
repository.StageableRepository
|
||||
Owner() string
|
||||
Repo() string
|
||||
Client() Client
|
||||
}
|
||||
|
||||
func NewRepository(
|
||||
ctx context.Context,
|
||||
config *provisioning.Repository,
|
||||
gitRepo git.GitRepository,
|
||||
factory *Factory,
|
||||
token common.RawSecureValue,
|
||||
) (GithubRepository, error) {
|
||||
owner, repo, err := ParseOwnerRepoGithub(config.Spec.GitHub.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse owner and repo: %w", err)
|
||||
}
|
||||
|
||||
return &githubRepository{
|
||||
config: config,
|
||||
GitRepository: gitRepo,
|
||||
gh: factory.New(ctx, token), // TODO, baseURL from config
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *githubRepository) Owner() string {
|
||||
return r.owner
|
||||
}
|
||||
|
||||
func (r *githubRepository) Repo() string {
|
||||
return r.repo
|
||||
}
|
||||
|
||||
func (r *githubRepository) Client() Client {
|
||||
return r.gh
|
||||
}
|
||||
|
||||
// Validate implements provisioning.Repository.
|
||||
func (r *githubRepository) Validate() (list field.ErrorList) {
|
||||
cfg := r.Config()
|
||||
gh := cfg.Spec.GitHub
|
||||
if gh == nil {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github"), "a github config is required"))
|
||||
return list
|
||||
}
|
||||
if gh.URL == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github", "url"), "a github url is required"))
|
||||
} else {
|
||||
_, _, err := ParseOwnerRepoGithub(gh.URL)
|
||||
if err != nil {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, err.Error()))
|
||||
} else if !strings.HasPrefix(gh.URL, "https://github.com/") {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "github", "url"), gh.URL, "URL must start with https://github.com/"))
|
||||
}
|
||||
}
|
||||
|
||||
if len(list) > 0 {
|
||||
return list
|
||||
}
|
||||
|
||||
return r.GitRepository.Validate()
|
||||
}
|
||||
|
||||
func ParseOwnerRepoGithub(giturl string) (owner string, repo string, err error) {
|
||||
giturl = strings.TrimSuffix(giturl, ".git")
|
||||
giturl = strings.TrimSuffix(giturl, "/")
|
||||
|
||||
parsed, e := url.Parse(giturl)
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
parts := strings.Split(parsed.Path, "/")
|
||||
if len(parts) < 3 {
|
||||
err = fmt.Errorf("unable to parse repo+owner from url")
|
||||
return
|
||||
}
|
||||
return parts[1], parts[2], nil
|
||||
}
|
||||
|
||||
// Test implements provisioning.Repository.
|
||||
func (r *githubRepository) Test(ctx context.Context) (*provisioning.TestResults, error) {
|
||||
url := r.config.Spec.GitHub.URL
|
||||
_, _, err := ParseOwnerRepoGithub(url)
|
||||
if err != nil {
|
||||
return repository.FromFieldError(field.Invalid(
|
||||
field.NewPath("spec", "github", "url"), url, err.Error())), nil
|
||||
}
|
||||
|
||||
return r.GitRepository.Test(ctx)
|
||||
}
|
||||
|
||||
func (r *githubRepository) History(ctx context.Context, path, ref string) ([]provisioning.HistoryItem, error) {
|
||||
if ref == "" {
|
||||
ref = r.config.Spec.GitHub.Branch
|
||||
}
|
||||
|
||||
finalPath := safepath.Join(r.config.Spec.GitHub.Path, path)
|
||||
commits, err := r.gh.Commits(ctx, r.owner, r.repo, finalPath, ref)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrResourceNotFound) {
|
||||
return nil, repository.ErrFileNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("get commits: %w", err)
|
||||
}
|
||||
|
||||
ret := make([]provisioning.HistoryItem, 0, len(commits))
|
||||
for _, commit := range commits {
|
||||
authors := make([]provisioning.Author, 0)
|
||||
if commit.Author != nil {
|
||||
authors = append(authors, provisioning.Author{
|
||||
Name: commit.Author.Name,
|
||||
Username: commit.Author.Username,
|
||||
AvatarURL: commit.Author.AvatarURL,
|
||||
})
|
||||
}
|
||||
|
||||
if commit.Committer != nil && commit.Author != nil && commit.Author.Name != commit.Committer.Name {
|
||||
authors = append(authors, provisioning.Author{
|
||||
Name: commit.Committer.Name,
|
||||
Username: commit.Committer.Username,
|
||||
AvatarURL: commit.Committer.AvatarURL,
|
||||
})
|
||||
}
|
||||
|
||||
ret = append(ret, provisioning.HistoryItem{
|
||||
Ref: commit.Ref,
|
||||
Message: commit.Message,
|
||||
Authors: authors,
|
||||
CreatedAt: commit.CreatedAt.UnixMilli(),
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// ListRefs list refs from the git repository and add the ref URL to the ref item
|
||||
func (r *githubRepository) ListRefs(ctx context.Context) ([]provisioning.RefItem, error) {
|
||||
refs, err := r.GitRepository.ListRefs(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list refs: %w", err)
|
||||
}
|
||||
|
||||
for i := range refs {
|
||||
refs[i].RefURL = fmt.Sprintf("%s/tree/%s", r.config.Spec.GitHub.URL, refs[i].Name)
|
||||
}
|
||||
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
// ResourceURLs implements RepositoryWithURLs.
|
||||
func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*provisioning.RepositoryURLs, error) {
|
||||
cfg := r.config.Spec.GitHub
|
||||
if file.Path == "" || cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ref := file.Ref
|
||||
if ref == "" {
|
||||
ref = cfg.Branch
|
||||
}
|
||||
|
||||
urls := &provisioning.RepositoryURLs{
|
||||
RepositoryURL: cfg.URL,
|
||||
SourceURL: fmt.Sprintf("%s/blob/%s/%s", cfg.URL, ref, file.Path),
|
||||
}
|
||||
|
||||
if ref != cfg.Branch {
|
||||
urls.CompareURL = fmt.Sprintf("%s/compare/%s...%s", cfg.URL, cfg.Branch, ref)
|
||||
|
||||
// Create a new pull request
|
||||
urls.NewPullRequestURL = fmt.Sprintf("%s?quick_pull=1&labels=grafana", urls.CompareURL)
|
||||
}
|
||||
|
||||
return urls, nil
|
||||
}
|
||||
|
||||
// RefURLs implements RepositoryWithURLs.
|
||||
func (r *githubRepository) RefURLs(ctx context.Context, ref string) (*provisioning.RepositoryURLs, error) {
|
||||
cfg := r.config.Spec.GitHub
|
||||
if cfg == nil || ref == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
urls := &provisioning.RepositoryURLs{
|
||||
SourceURL: fmt.Sprintf("%s/tree/%s", cfg.URL, ref),
|
||||
}
|
||||
|
||||
if ref != cfg.Branch {
|
||||
urls.CompareURL = fmt.Sprintf("%s/compare/%s...%s", cfg.URL, cfg.Branch, ref)
|
||||
urls.NewPullRequestURL = fmt.Sprintf("%s?quick_pull=1&labels=grafana", urls.CompareURL)
|
||||
}
|
||||
|
||||
return urls, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+231
@@ -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"
|
||||
}
|
||||
}
|
||||
+470
@@ -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"
|
||||
}
|
||||
}
|
||||
+148
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
+148
@@ -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"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/repository"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
)
|
||||
|
||||
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
Reference in New Issue
Block a user