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

This commit is contained in:
Roberto Jiménez Sánchez
2025-07-10 09:46:38 -07:00
committed by GitHub
parent 9df15d120d
commit 7e0848294e
75 changed files with 2426 additions and 12362 deletions
@@ -7,127 +7,34 @@ import (
"errors"
"time"
"github.com/google/go-github/v70/github"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
// API errors that we need to convey after parsing real GH errors (or faking them).
var (
ErrResourceAlreadyExists = errors.New("the resource already exists")
ErrResourceNotFound = errors.New("the resource does not exist")
ErrMismatchedHash = errors.New("the update cannot be applied because the expected and actual hashes are unequal")
ErrNoSecret = errors.New("new webhooks must have a secret")
ErrResourceNotFound = errors.New("the resource does not exist")
//lint:ignore ST1005 this is not punctuation
ErrPathTraversalDisallowed = errors.New("the path contained ..") //nolint:staticcheck
ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
ErrFileTooLarge = errors.New("file exceeds maximum allowed size")
ErrTooManyItems = errors.New("maximum number of items exceeded")
ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
ErrTooManyItems = errors.New("maximum number of items exceeded")
)
// MaxFileSize maximum file size limit (10MB)
const MaxFileSize = 10 * 1024 * 1024 // 10MB in bytes
type ErrRateLimited = github.RateLimitError
//go:generate mockery --name Client --structname MockClient --inpackage --filename mock_client.go --with-expecter
type Client interface {
// IsAuthenticated checks if the client is authenticated.
IsAuthenticated(ctx context.Context) error
// GetContents returns the metadata and content of a file or directory.
// When a file is checked, the first returned value will have a value. For a directory, the second will. The other value is always nil.
// If an error occurs, the returned values may or may not be nil.
//
// If ".." appears in the "path", this method will return an error.
GetContents(ctx context.Context, owner, repository, path, ref string) (fileContents RepositoryContent, dirContents []RepositoryContent, err error)
// GetTree returns the Git tree in the repository.
// When recursive is given, subtrees are mapped into the returned array.
// When basePath is given, only trees under it are given. The results do not include this path in their names.
//
// The truncated bool will be set to true if the tree is larger than 7 MB or 100 000 entries.
// When truncated is true, you may wish to read each subtree manually instead.
GetTree(ctx context.Context, owner, repository, basePath, ref string, recursive bool) (entries []RepositoryContent, truncated bool, err error)
// CreateFile creates a new file in the repository under the given path.
// The file is created on the branch given.
// The message given is the commit message. If none is given, an appropriate default is used.
// The content is what the file should contain. An empty slice is valid, though often not very useful.
//
// If ".." appears in the "path", this method will return an error.
CreateFile(ctx context.Context, owner, repository, path, branch, message string, content []byte) error
// UpdateFile updates a file in the repository under the given path.
// The file is updated on the branch given.
// The message given is the commit message. If none is given, an appropriate default is used.
// The content is what the file should contain. An empty slice is valid, though often not very useful.
// If the path does not exist, an error is returned.
// The hash given must be the SHA hash of the file contents. Calling GetContents in advance is an easy way of handling this.
//
// If ".." appears in the "path", this method will return an error.
UpdateFile(ctx context.Context, owner, repository, path, branch, message, hash string, content []byte) error
// DeleteFile deletes a file in the repository under the given path.
// The file is deleted from the branch given.
// The message given is the commit message. If none is given, an appropriate default is used.
// If the path does not exist, an error is returned.
// The hash given must be the SHA hash of the file contents. Calling GetContents in advance is an easy way of handling this.
//
// If ".." appears in the "path", this method will return an error.
DeleteFile(ctx context.Context, owner, repository, path, branch, message, hash string) error
// Commits returns the commits for the given path
// Commits
Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error)
// CompareCommits returns the changes between two commits.
CompareCommits(ctx context.Context, owner, repository, base, head string) ([]CommitFile, error)
// RepoExists checks if a repository exists.
RepoExists(ctx context.Context, owner, repository string) (bool, error)
// CreateBranch creates a new branch in the repository.
CreateBranch(ctx context.Context, owner, repository, sourceBranch, branchName string) error
// BranchExists checks if a branch exists in the repository.
BranchExists(ctx context.Context, owner, repository, branchName string) (bool, error)
// GetBranch returns the branch of the repository.
GetBranch(ctx context.Context, owner, repository, branchName string) (Branch, 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
}
//go:generate mockery --name RepositoryContent --structname MockRepositoryContent --inpackage --filename mock_repository_content.go --with-expecter
type RepositoryContent interface {
// Returns true if this is a directory, false if it is a file.
IsDirectory() bool
// Returns the contents of the file. Decoding happens if necessary.
// Returns an error if the content represents a directory.
GetFileContent() (string, error)
// Returns true if this is a symlink.
// If true, GetPath returns the path where this symlink leads.
IsSymlink() bool
// Returns the full path from the root of the repository.
// This has no leading or trailing slashes.
// The path only uses '/' for directories. You can use the 'path' package to interact with these.
GetPath() string
// Get the SHA hash. This is usually a SHA-256, but may also be SHA-512.
// Directories have SHA hashes, too (TODO: how is this calculated?).
GetSHA() string
// The size of the file. Not necessarily non-zero, even if the file is supposed to be non-zero.
GetSize() int64
}
type Branch struct {
Name string
Sha string
}
type CommitAuthor struct {
Name string
Username string
@@ -150,20 +57,6 @@ type CommitFile interface {
GetStatus() string
}
type FileComment struct {
Content string
Path string
Position int
Ref string
}
type CreateFileOptions struct {
// The message of the commit. May be empty, in which case a default value is entered.
Message string
// The content of the file to write, unencoded.
Content []byte
}
type WebhookConfig struct {
// The ID of the webhook.
// Can be 0 on creation.
@@ -27,6 +27,11 @@ func (r *Factory) New(ctx context.Context, ghToken string) Client {
tokenSrc := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: ghToken},
)
tokenClient := oauth2.NewClient(ctx, tokenSrc)
return NewClient(github.NewClient(tokenClient))
if len(ghToken) == 0 {
tokenClient := oauth2.NewClient(ctx, tokenSrc)
return NewClient(github.NewClient(tokenClient))
}
return NewClient(github.NewClient(&http.Client{}))
}
@@ -0,0 +1,942 @@
// Code generated by mockery v2.52.4. DO NOT EDIT.
package github
import (
context "context"
mock "github.com/stretchr/testify/mock"
field "k8s.io/apimachinery/pkg/util/validation/field"
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
)
// MockGithubRepository is an autogenerated mock type for the GithubRepository type
type MockGithubRepository struct {
mock.Mock
}
type MockGithubRepository_Expecter struct {
mock *mock.Mock
}
func (_m *MockGithubRepository) EXPECT() *MockGithubRepository_Expecter {
return &MockGithubRepository_Expecter{mock: &_m.Mock}
}
// Client provides a mock function with no fields
func (_m *MockGithubRepository) Client() Client {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Client")
}
var r0 Client
if rf, ok := ret.Get(0).(func() Client); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(Client)
}
}
return r0
}
// MockGithubRepository_Client_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Client'
type MockGithubRepository_Client_Call struct {
*mock.Call
}
// Client is a helper method to define mock.On call
func (_e *MockGithubRepository_Expecter) Client() *MockGithubRepository_Client_Call {
return &MockGithubRepository_Client_Call{Call: _e.mock.On("Client")}
}
func (_c *MockGithubRepository_Client_Call) Run(run func()) *MockGithubRepository_Client_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockGithubRepository_Client_Call) Return(_a0 Client) *MockGithubRepository_Client_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockGithubRepository_Client_Call) RunAndReturn(run func() Client) *MockGithubRepository_Client_Call {
_c.Call.Return(run)
return _c
}
// CompareFiles provides a mock function with given fields: ctx, base, ref
func (_m *MockGithubRepository) CompareFiles(ctx context.Context, base string, ref string) ([]repository.VersionedFileChange, error) {
ret := _m.Called(ctx, base, ref)
if len(ret) == 0 {
panic("no return value specified for CompareFiles")
}
var r0 []repository.VersionedFileChange
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]repository.VersionedFileChange, error)); ok {
return rf(ctx, base, ref)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) []repository.VersionedFileChange); ok {
r0 = rf(ctx, base, ref)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]repository.VersionedFileChange)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, base, ref)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockGithubRepository_CompareFiles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CompareFiles'
type MockGithubRepository_CompareFiles_Call struct {
*mock.Call
}
// CompareFiles is a helper method to define mock.On call
// - ctx context.Context
// - base string
// - ref string
func (_e *MockGithubRepository_Expecter) CompareFiles(ctx interface{}, base interface{}, ref interface{}) *MockGithubRepository_CompareFiles_Call {
return &MockGithubRepository_CompareFiles_Call{Call: _e.mock.On("CompareFiles", ctx, base, ref)}
}
func (_c *MockGithubRepository_CompareFiles_Call) Run(run func(ctx context.Context, base string, ref string)) *MockGithubRepository_CompareFiles_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string))
})
return _c
}
func (_c *MockGithubRepository_CompareFiles_Call) Return(_a0 []repository.VersionedFileChange, _a1 error) *MockGithubRepository_CompareFiles_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockGithubRepository_CompareFiles_Call) RunAndReturn(run func(context.Context, string, string) ([]repository.VersionedFileChange, error)) *MockGithubRepository_CompareFiles_Call {
_c.Call.Return(run)
return _c
}
// Config provides a mock function with no fields
func (_m *MockGithubRepository) Config() *v0alpha1.Repository {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Config")
}
var r0 *v0alpha1.Repository
if rf, ok := ret.Get(0).(func() *v0alpha1.Repository); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v0alpha1.Repository)
}
}
return r0
}
// MockGithubRepository_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config'
type MockGithubRepository_Config_Call struct {
*mock.Call
}
// Config is a helper method to define mock.On call
func (_e *MockGithubRepository_Expecter) Config() *MockGithubRepository_Config_Call {
return &MockGithubRepository_Config_Call{Call: _e.mock.On("Config")}
}
func (_c *MockGithubRepository_Config_Call) Run(run func()) *MockGithubRepository_Config_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockGithubRepository_Config_Call) Return(_a0 *v0alpha1.Repository) *MockGithubRepository_Config_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockGithubRepository_Config_Call) RunAndReturn(run func() *v0alpha1.Repository) *MockGithubRepository_Config_Call {
_c.Call.Return(run)
return _c
}
// Create provides a mock function with given fields: ctx, path, ref, data, message
func (_m *MockGithubRepository) Create(ctx context.Context, path string, ref string, data []byte, message string) error {
ret := _m.Called(ctx, path, ref, data, message)
if len(ret) == 0 {
panic("no return value specified for Create")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok {
r0 = rf(ctx, path, ref, data, message)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockGithubRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
type MockGithubRepository_Create_Call struct {
*mock.Call
}
// Create is a helper method to define mock.On call
// - ctx context.Context
// - path string
// - ref string
// - data []byte
// - message string
func (_e *MockGithubRepository_Expecter) Create(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockGithubRepository_Create_Call {
return &MockGithubRepository_Create_Call{Call: _e.mock.On("Create", ctx, path, ref, data, message)}
}
func (_c *MockGithubRepository_Create_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockGithubRepository_Create_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
})
return _c
}
func (_c *MockGithubRepository_Create_Call) Return(_a0 error) *MockGithubRepository_Create_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockGithubRepository_Create_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockGithubRepository_Create_Call {
_c.Call.Return(run)
return _c
}
// Delete provides a mock function with given fields: ctx, path, ref, message
func (_m *MockGithubRepository) Delete(ctx context.Context, path string, ref string, message string) error {
ret := _m.Called(ctx, path, ref, message)
if len(ret) == 0 {
panic("no return value specified for Delete")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
r0 = rf(ctx, path, ref, message)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockGithubRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
type MockGithubRepository_Delete_Call struct {
*mock.Call
}
// Delete is a helper method to define mock.On call
// - ctx context.Context
// - path string
// - ref string
// - message string
func (_e *MockGithubRepository_Expecter) Delete(ctx interface{}, path interface{}, ref interface{}, message interface{}) *MockGithubRepository_Delete_Call {
return &MockGithubRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, path, ref, message)}
}
func (_c *MockGithubRepository_Delete_Call) Run(run func(ctx context.Context, path string, ref string, message string)) *MockGithubRepository_Delete_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string))
})
return _c
}
func (_c *MockGithubRepository_Delete_Call) Return(_a0 error) *MockGithubRepository_Delete_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockGithubRepository_Delete_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockGithubRepository_Delete_Call {
_c.Call.Return(run)
return _c
}
// History provides a mock function with given fields: ctx, path, ref
func (_m *MockGithubRepository) History(ctx context.Context, path string, ref string) ([]v0alpha1.HistoryItem, error) {
ret := _m.Called(ctx, path, ref)
if len(ret) == 0 {
panic("no return value specified for History")
}
var r0 []v0alpha1.HistoryItem
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]v0alpha1.HistoryItem, error)); ok {
return rf(ctx, path, ref)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) []v0alpha1.HistoryItem); ok {
r0 = rf(ctx, path, ref)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]v0alpha1.HistoryItem)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, path, ref)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockGithubRepository_History_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'History'
type MockGithubRepository_History_Call struct {
*mock.Call
}
// History is a helper method to define mock.On call
// - ctx context.Context
// - path string
// - ref string
func (_e *MockGithubRepository_Expecter) History(ctx interface{}, path interface{}, ref interface{}) *MockGithubRepository_History_Call {
return &MockGithubRepository_History_Call{Call: _e.mock.On("History", ctx, path, ref)}
}
func (_c *MockGithubRepository_History_Call) Run(run func(ctx context.Context, path string, ref string)) *MockGithubRepository_History_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string))
})
return _c
}
func (_c *MockGithubRepository_History_Call) Return(_a0 []v0alpha1.HistoryItem, _a1 error) *MockGithubRepository_History_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockGithubRepository_History_Call) RunAndReturn(run func(context.Context, string, string) ([]v0alpha1.HistoryItem, error)) *MockGithubRepository_History_Call {
_c.Call.Return(run)
return _c
}
// LatestRef provides a mock function with given fields: ctx
func (_m *MockGithubRepository) LatestRef(ctx context.Context) (string, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for LatestRef")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) string); ok {
r0 = rf(ctx)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockGithubRepository_LatestRef_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestRef'
type MockGithubRepository_LatestRef_Call struct {
*mock.Call
}
// LatestRef is a helper method to define mock.On call
// - ctx context.Context
func (_e *MockGithubRepository_Expecter) LatestRef(ctx interface{}) *MockGithubRepository_LatestRef_Call {
return &MockGithubRepository_LatestRef_Call{Call: _e.mock.On("LatestRef", ctx)}
}
func (_c *MockGithubRepository_LatestRef_Call) Run(run func(ctx context.Context)) *MockGithubRepository_LatestRef_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *MockGithubRepository_LatestRef_Call) Return(_a0 string, _a1 error) *MockGithubRepository_LatestRef_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockGithubRepository_LatestRef_Call) RunAndReturn(run func(context.Context) (string, error)) *MockGithubRepository_LatestRef_Call {
_c.Call.Return(run)
return _c
}
// Owner provides a mock function with no fields
func (_m *MockGithubRepository) Owner() string {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Owner")
}
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// MockGithubRepository_Owner_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Owner'
type MockGithubRepository_Owner_Call struct {
*mock.Call
}
// Owner is a helper method to define mock.On call
func (_e *MockGithubRepository_Expecter) Owner() *MockGithubRepository_Owner_Call {
return &MockGithubRepository_Owner_Call{Call: _e.mock.On("Owner")}
}
func (_c *MockGithubRepository_Owner_Call) Run(run func()) *MockGithubRepository_Owner_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockGithubRepository_Owner_Call) Return(_a0 string) *MockGithubRepository_Owner_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockGithubRepository_Owner_Call) RunAndReturn(run func() string) *MockGithubRepository_Owner_Call {
_c.Call.Return(run)
return _c
}
// Read provides a mock function with given fields: ctx, path, ref
func (_m *MockGithubRepository) Read(ctx context.Context, path string, ref string) (*repository.FileInfo, error) {
ret := _m.Called(ctx, path, ref)
if len(ret) == 0 {
panic("no return value specified for Read")
}
var r0 *repository.FileInfo
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*repository.FileInfo, error)); ok {
return rf(ctx, path, ref)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) *repository.FileInfo); ok {
r0 = rf(ctx, path, ref)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*repository.FileInfo)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, path, ref)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockGithubRepository_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
type MockGithubRepository_Read_Call struct {
*mock.Call
}
// Read is a helper method to define mock.On call
// - ctx context.Context
// - path string
// - ref string
func (_e *MockGithubRepository_Expecter) Read(ctx interface{}, path interface{}, ref interface{}) *MockGithubRepository_Read_Call {
return &MockGithubRepository_Read_Call{Call: _e.mock.On("Read", ctx, path, ref)}
}
func (_c *MockGithubRepository_Read_Call) Run(run func(ctx context.Context, path string, ref string)) *MockGithubRepository_Read_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string))
})
return _c
}
func (_c *MockGithubRepository_Read_Call) Return(_a0 *repository.FileInfo, _a1 error) *MockGithubRepository_Read_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockGithubRepository_Read_Call) RunAndReturn(run func(context.Context, string, string) (*repository.FileInfo, error)) *MockGithubRepository_Read_Call {
_c.Call.Return(run)
return _c
}
// ReadTree provides a mock function with given fields: ctx, ref
func (_m *MockGithubRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
ret := _m.Called(ctx, ref)
if len(ret) == 0 {
panic("no return value specified for ReadTree")
}
var r0 []repository.FileTreeEntry
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) ([]repository.FileTreeEntry, error)); ok {
return rf(ctx, ref)
}
if rf, ok := ret.Get(0).(func(context.Context, string) []repository.FileTreeEntry); ok {
r0 = rf(ctx, ref)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]repository.FileTreeEntry)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, ref)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockGithubRepository_ReadTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadTree'
type MockGithubRepository_ReadTree_Call struct {
*mock.Call
}
// ReadTree is a helper method to define mock.On call
// - ctx context.Context
// - ref string
func (_e *MockGithubRepository_Expecter) ReadTree(ctx interface{}, ref interface{}) *MockGithubRepository_ReadTree_Call {
return &MockGithubRepository_ReadTree_Call{Call: _e.mock.On("ReadTree", ctx, ref)}
}
func (_c *MockGithubRepository_ReadTree_Call) Run(run func(ctx context.Context, ref string)) *MockGithubRepository_ReadTree_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *MockGithubRepository_ReadTree_Call) Return(_a0 []repository.FileTreeEntry, _a1 error) *MockGithubRepository_ReadTree_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockGithubRepository_ReadTree_Call) RunAndReturn(run func(context.Context, string) ([]repository.FileTreeEntry, error)) *MockGithubRepository_ReadTree_Call {
_c.Call.Return(run)
return _c
}
// Repo provides a mock function with no fields
func (_m *MockGithubRepository) Repo() string {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Repo")
}
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// MockGithubRepository_Repo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Repo'
type MockGithubRepository_Repo_Call struct {
*mock.Call
}
// Repo is a helper method to define mock.On call
func (_e *MockGithubRepository_Expecter) Repo() *MockGithubRepository_Repo_Call {
return &MockGithubRepository_Repo_Call{Call: _e.mock.On("Repo")}
}
func (_c *MockGithubRepository_Repo_Call) Run(run func()) *MockGithubRepository_Repo_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockGithubRepository_Repo_Call) Return(_a0 string) *MockGithubRepository_Repo_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockGithubRepository_Repo_Call) RunAndReturn(run func() string) *MockGithubRepository_Repo_Call {
_c.Call.Return(run)
return _c
}
// ResourceURLs provides a mock function with given fields: ctx, file
func (_m *MockGithubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*v0alpha1.ResourceURLs, error) {
ret := _m.Called(ctx, file)
if len(ret) == 0 {
panic("no return value specified for ResourceURLs")
}
var r0 *v0alpha1.ResourceURLs
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *repository.FileInfo) (*v0alpha1.ResourceURLs, error)); ok {
return rf(ctx, file)
}
if rf, ok := ret.Get(0).(func(context.Context, *repository.FileInfo) *v0alpha1.ResourceURLs); ok {
r0 = rf(ctx, file)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v0alpha1.ResourceURLs)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *repository.FileInfo) error); ok {
r1 = rf(ctx, file)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockGithubRepository_ResourceURLs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResourceURLs'
type MockGithubRepository_ResourceURLs_Call struct {
*mock.Call
}
// ResourceURLs is a helper method to define mock.On call
// - ctx context.Context
// - file *repository.FileInfo
func (_e *MockGithubRepository_Expecter) ResourceURLs(ctx interface{}, file interface{}) *MockGithubRepository_ResourceURLs_Call {
return &MockGithubRepository_ResourceURLs_Call{Call: _e.mock.On("ResourceURLs", ctx, file)}
}
func (_c *MockGithubRepository_ResourceURLs_Call) Run(run func(ctx context.Context, file *repository.FileInfo)) *MockGithubRepository_ResourceURLs_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*repository.FileInfo))
})
return _c
}
func (_c *MockGithubRepository_ResourceURLs_Call) Return(_a0 *v0alpha1.ResourceURLs, _a1 error) *MockGithubRepository_ResourceURLs_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockGithubRepository_ResourceURLs_Call) RunAndReturn(run func(context.Context, *repository.FileInfo) (*v0alpha1.ResourceURLs, error)) *MockGithubRepository_ResourceURLs_Call {
_c.Call.Return(run)
return _c
}
// Stage provides a mock function with given fields: ctx, opts
func (_m *MockGithubRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) {
ret := _m.Called(ctx, opts)
if len(ret) == 0 {
panic("no return value specified for Stage")
}
var r0 repository.StagedRepository
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, repository.StageOptions) (repository.StagedRepository, error)); ok {
return rf(ctx, opts)
}
if rf, ok := ret.Get(0).(func(context.Context, repository.StageOptions) repository.StagedRepository); ok {
r0 = rf(ctx, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(repository.StagedRepository)
}
}
if rf, ok := ret.Get(1).(func(context.Context, repository.StageOptions) error); ok {
r1 = rf(ctx, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockGithubRepository_Stage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stage'
type MockGithubRepository_Stage_Call struct {
*mock.Call
}
// Stage is a helper method to define mock.On call
// - ctx context.Context
// - opts repository.StageOptions
func (_e *MockGithubRepository_Expecter) Stage(ctx interface{}, opts interface{}) *MockGithubRepository_Stage_Call {
return &MockGithubRepository_Stage_Call{Call: _e.mock.On("Stage", ctx, opts)}
}
func (_c *MockGithubRepository_Stage_Call) Run(run func(ctx context.Context, opts repository.StageOptions)) *MockGithubRepository_Stage_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(repository.StageOptions))
})
return _c
}
func (_c *MockGithubRepository_Stage_Call) Return(_a0 repository.StagedRepository, _a1 error) *MockGithubRepository_Stage_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockGithubRepository_Stage_Call) RunAndReturn(run func(context.Context, repository.StageOptions) (repository.StagedRepository, error)) *MockGithubRepository_Stage_Call {
_c.Call.Return(run)
return _c
}
// Test provides a mock function with given fields: ctx
func (_m *MockGithubRepository) Test(ctx context.Context) (*v0alpha1.TestResults, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for Test")
}
var r0 *v0alpha1.TestResults
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (*v0alpha1.TestResults, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) *v0alpha1.TestResults); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v0alpha1.TestResults)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockGithubRepository_Test_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Test'
type MockGithubRepository_Test_Call struct {
*mock.Call
}
// Test is a helper method to define mock.On call
// - ctx context.Context
func (_e *MockGithubRepository_Expecter) Test(ctx interface{}) *MockGithubRepository_Test_Call {
return &MockGithubRepository_Test_Call{Call: _e.mock.On("Test", ctx)}
}
func (_c *MockGithubRepository_Test_Call) Run(run func(ctx context.Context)) *MockGithubRepository_Test_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *MockGithubRepository_Test_Call) Return(_a0 *v0alpha1.TestResults, _a1 error) *MockGithubRepository_Test_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockGithubRepository_Test_Call) RunAndReturn(run func(context.Context) (*v0alpha1.TestResults, error)) *MockGithubRepository_Test_Call {
_c.Call.Return(run)
return _c
}
// Update provides a mock function with given fields: ctx, path, ref, data, message
func (_m *MockGithubRepository) Update(ctx context.Context, path string, ref string, data []byte, message string) error {
ret := _m.Called(ctx, path, ref, data, message)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok {
r0 = rf(ctx, path, ref, data, message)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockGithubRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
type MockGithubRepository_Update_Call struct {
*mock.Call
}
// Update is a helper method to define mock.On call
// - ctx context.Context
// - path string
// - ref string
// - data []byte
// - message string
func (_e *MockGithubRepository_Expecter) Update(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockGithubRepository_Update_Call {
return &MockGithubRepository_Update_Call{Call: _e.mock.On("Update", ctx, path, ref, data, message)}
}
func (_c *MockGithubRepository_Update_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockGithubRepository_Update_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
})
return _c
}
func (_c *MockGithubRepository_Update_Call) Return(_a0 error) *MockGithubRepository_Update_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockGithubRepository_Update_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockGithubRepository_Update_Call {
_c.Call.Return(run)
return _c
}
// Validate provides a mock function with no fields
func (_m *MockGithubRepository) Validate() field.ErrorList {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Validate")
}
var r0 field.ErrorList
if rf, ok := ret.Get(0).(func() field.ErrorList); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(field.ErrorList)
}
}
return r0
}
// MockGithubRepository_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
type MockGithubRepository_Validate_Call struct {
*mock.Call
}
// Validate is a helper method to define mock.On call
func (_e *MockGithubRepository_Expecter) Validate() *MockGithubRepository_Validate_Call {
return &MockGithubRepository_Validate_Call{Call: _e.mock.On("Validate")}
}
func (_c *MockGithubRepository_Validate_Call) Run(run func()) *MockGithubRepository_Validate_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockGithubRepository_Validate_Call) Return(_a0 field.ErrorList) *MockGithubRepository_Validate_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockGithubRepository_Validate_Call) RunAndReturn(run func() field.ErrorList) *MockGithubRepository_Validate_Call {
_c.Call.Return(run)
return _c
}
// Write provides a mock function with given fields: ctx, path, ref, data, message
func (_m *MockGithubRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
ret := _m.Called(ctx, path, ref, data, message)
if len(ret) == 0 {
panic("no return value specified for Write")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok {
r0 = rf(ctx, path, ref, data, message)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockGithubRepository_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
type MockGithubRepository_Write_Call struct {
*mock.Call
}
// Write is a helper method to define mock.On call
// - ctx context.Context
// - path string
// - ref string
// - data []byte
// - message string
func (_e *MockGithubRepository_Expecter) Write(ctx interface{}, path interface{}, ref interface{}, data interface{}, message interface{}) *MockGithubRepository_Write_Call {
return &MockGithubRepository_Write_Call{Call: _e.mock.On("Write", ctx, path, ref, data, message)}
}
func (_c *MockGithubRepository_Write_Call) Run(run func(ctx context.Context, path string, ref string, data []byte, message string)) *MockGithubRepository_Write_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string))
})
return _c
}
func (_c *MockGithubRepository_Write_Call) Return(_a0 error) *MockGithubRepository_Write_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockGithubRepository_Write_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) error) *MockGithubRepository_Write_Call {
_c.Call.Return(run)
return _c
}
// NewMockGithubRepository creates a new instance of MockGithubRepository. 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 NewMockGithubRepository(t interface {
mock.TestingT
Cleanup(func())
}) *MockGithubRepository {
mock := &MockGithubRepository{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
@@ -8,10 +8,6 @@ import (
"time"
"github.com/google/go-github/v70/github"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
)
type githubClient struct {
@@ -22,268 +18,12 @@ func NewClient(client *github.Client) Client {
return &githubClient{client}
}
func (r *githubClient) IsAuthenticated(ctx context.Context) error {
if _, _, err := r.gh.Users.Get(ctx, ""); err != nil {
var ghErr *github.ErrorResponse
if errors.As(err, &ghErr) {
switch ghErr.Response.StatusCode {
case http.StatusUnauthorized:
return apierrors.NewUnauthorized("token is invalid or expired")
case http.StatusForbidden:
return &apierrors.StatusError{
ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusUnauthorized,
Reason: metav1.StatusReasonUnauthorized,
Message: "token is revoked or has insufficient permissions",
},
}
case http.StatusServiceUnavailable:
return ErrServiceUnavailable
}
}
return err
}
return nil
}
func (r *githubClient) RepoExists(ctx context.Context, owner, repository string) (bool, error) {
_, resp, err := r.gh.Repositories.Get(ctx, owner, repository)
if err == nil {
return true, nil
}
if resp.StatusCode == http.StatusNotFound {
return false, nil
}
return false, err
}
const (
maxDirectoryItems = 1000 // Maximum number of items allowed in a directory
maxTreeItems = 10000 // Maximum number of items allowed in a tree
maxCommits = 1000 // Maximum number of commits to fetch
maxCompareFiles = 1000 // Maximum number of files to compare between commits
maxWebhooks = 100 // Maximum number of webhooks allowed per repository
maxPRFiles = 1000 // Maximum number of files allowed in a pull request
maxPullRequestsFileComments = 1000 // Maximum number of comments allowed in a pull request
maxFileSize = 10 * 1024 * 1024 // 10MB in bytes
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
)
func (r *githubClient) GetContents(ctx context.Context, owner, repository, path, ref string) (fileContents RepositoryContent, dirContents []RepositoryContent, err error) {
// First try to get repository contents
opts := &github.RepositoryContentGetOptions{
Ref: ref,
}
fc, dc, _, err := r.gh.Repositories.GetContents(ctx, owner, repository, path, opts)
if err != nil {
var ghErr *github.ErrorResponse
if !errors.As(err, &ghErr) {
return nil, nil, err
}
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
return nil, nil, ErrServiceUnavailable
}
if ghErr.Response.StatusCode == http.StatusNotFound {
return nil, nil, ErrResourceNotFound
}
return nil, nil, err
}
if fc != nil {
// Check file size before returning content
if fc.GetSize() > maxFileSize {
return nil, nil, ErrFileTooLarge
}
return realRepositoryContent{fc}, nil, nil
}
// For directories, check size limits
if len(dc) > maxDirectoryItems {
return nil, nil, fmt.Errorf("directory contains too many items (more than %d)", maxDirectoryItems)
}
// Convert directory contents
allContents := make([]RepositoryContent, 0, len(dc))
for _, original := range dc {
allContents = append(allContents, realRepositoryContent{original})
}
return nil, allContents, nil
}
func (r *githubClient) GetTree(ctx context.Context, owner, repository, basePath, ref string, recursive bool) ([]RepositoryContent, bool, error) {
var tree *github.Tree
var err error
subPaths := safepath.Split(basePath)
currentRef := ref
for {
// If subPaths is empty, we can read recursively, as we're reading the tree from the "base" of the repository. Otherwise, always read only the direct children.
recursive := recursive && len(subPaths) == 0
tree, _, err = r.gh.Git.GetTree(ctx, owner, repository, currentRef, recursive)
if err != nil {
var ghErr *github.ErrorResponse
if !errors.As(err, &ghErr) {
return nil, false, err
}
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
return nil, false, ErrServiceUnavailable
}
if ghErr.Response.StatusCode == http.StatusNotFound {
if currentRef != ref {
// We're operating with a subpath which doesn't exist yet.
// Pretend as if there is simply no files.
// FIXME: why should we pretend this?
return nil, false, nil
}
// currentRef == ref
// This indicates the repository or commitish reference doesn't exist. This should always return an error.
return nil, false, ErrResourceNotFound
}
return nil, false, err
}
// Check if we've exceeded the maximum allowed items
if len(tree.Entries) > maxTreeItems {
return nil, false, fmt.Errorf("tree contains too many items (more than %d)", maxTreeItems)
}
// Prep for next iteration.
if len(subPaths) == 0 {
// We're done: we've discovered the tree we want.
break
}
// the ref must be equal the SHA of the entry corresponding to subPaths[0]
currentRef = ""
for _, e := range tree.Entries {
if e.GetPath() == subPaths[0] {
currentRef = e.GetSHA()
break
}
}
subPaths = subPaths[1:]
if currentRef == "" {
// We couldn't find the folder in the tree...
return nil, false, nil
}
}
// If the tree is truncated and we're in recursive mode, return an error
if tree.GetTruncated() && recursive {
return nil, true, fmt.Errorf("tree is too large to fetch recursively (more than %d items)", maxTreeItems)
}
entries := make([]RepositoryContent, 0, len(tree.Entries))
for _, te := range tree.Entries {
rrc := &realRepositoryContent{
real: &github.RepositoryContent{
Path: te.Path,
Size: te.Size,
SHA: te.SHA,
},
}
if te.GetType() == "tree" {
rrc.real.Type = github.Ptr("dir")
} else {
rrc.real.Type = te.Type
}
entries = append(entries, rrc)
}
return entries, tree.GetTruncated(), nil
}
func (r *githubClient) CreateFile(ctx context.Context, owner, repository, path, branch, message string, content []byte) error {
if message == "" {
message = fmt.Sprintf("Create %s", path)
}
_, _, err := r.gh.Repositories.CreateFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
Branch: &branch,
Message: &message,
Content: content,
})
if err == nil {
return nil
}
var ghErr *github.ErrorResponse
if !errors.As(err, &ghErr) {
return err
}
if ghErr.Response.StatusCode == http.StatusUnprocessableEntity {
return ErrResourceAlreadyExists
}
return err
}
func (r *githubClient) UpdateFile(ctx context.Context, owner, repository, path, branch, message, hash string, content []byte) error {
if message == "" {
message = fmt.Sprintf("Update %s", path)
}
_, _, err := r.gh.Repositories.UpdateFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
Branch: &branch,
Message: &message,
Content: content,
SHA: &hash,
})
if err == nil {
return nil
}
var ghErr *github.ErrorResponse
if !errors.As(err, &ghErr) {
return err
}
if ghErr.Response.StatusCode == http.StatusNotFound {
return ErrResourceNotFound
}
if ghErr.Response.StatusCode == http.StatusConflict {
return ErrMismatchedHash
}
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
return ErrServiceUnavailable
}
return err
}
func (r *githubClient) DeleteFile(ctx context.Context, owner, repository, path, branch, message, hash string) error {
if message == "" {
message = fmt.Sprintf("Delete %s", path)
}
_, _, err := r.gh.Repositories.DeleteFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
Branch: &branch,
Message: &message,
SHA: &hash,
})
if err == nil {
return nil
}
var ghErr *github.ErrorResponse
if !errors.As(err, &ghErr) {
return err
}
if ghErr.Response.StatusCode == http.StatusNotFound {
return ErrResourceNotFound
}
if ghErr.Response.StatusCode == http.StatusConflict {
return ErrMismatchedHash
}
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
return ErrServiceUnavailable
}
return err
}
// 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) {
@@ -343,105 +83,6 @@ func (r *githubClient) Commits(ctx context.Context, owner, repository, path, bra
return ret, nil
}
func (r *githubClient) CompareCommits(ctx context.Context, owner, repository, base, head string) ([]CommitFile, error) {
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.CommitFile, *github.Response, error) {
compare, resp, err := r.gh.Repositories.CompareCommits(ctx, owner, repository, base, head, opts)
if err != nil {
return nil, resp, err
}
return compare.Files, resp, nil
}
files, err := paginatedList(
ctx,
listFn,
defaultListOptions(maxCompareFiles),
)
if errors.Is(err, ErrTooManyItems) {
return nil, fmt.Errorf("too many files changed between commits (more than %d)", maxCompareFiles)
}
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) GetBranch(ctx context.Context, owner, repository, branchName string) (Branch, error) {
branch, resp, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0)
if err != nil {
// For some reason, GitHub client handles this case differently by failing with a wrapped error
if resp != nil && resp.StatusCode == http.StatusNotFound {
return Branch{}, ErrResourceNotFound
}
if resp != nil && resp.StatusCode == http.StatusServiceUnavailable {
return Branch{}, ErrServiceUnavailable
}
var ghErr *github.ErrorResponse
if !errors.As(err, &ghErr) {
return Branch{}, err
}
// Leaving these just in case
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
return Branch{}, ErrServiceUnavailable
}
if ghErr.Response.StatusCode == http.StatusNotFound {
return Branch{}, ErrResourceNotFound
}
return Branch{}, err
}
return Branch{
Name: branch.GetName(),
Sha: branch.GetCommit().GetSHA(),
}, nil
}
func (r *githubClient) CreateBranch(ctx context.Context, owner, repository, sourceBranch, branchName string) error {
// Fail if the branch already exists
if _, _, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0); err == nil {
return ErrResourceAlreadyExists
}
// Branch out based on the repository branch
baseRef, _, err := r.gh.Repositories.GetBranch(ctx, owner, repository, sourceBranch, 0)
if err != nil {
return fmt.Errorf("get base branch: %w", err)
}
if _, _, err := r.gh.Git.CreateRef(ctx, owner, repository, &github.Reference{
Ref: github.Ptr(fmt.Sprintf("refs/heads/%s", branchName)),
Object: &github.GitObject{
SHA: baseRef.Commit.SHA,
},
}); err != nil {
return fmt.Errorf("create branch ref: %w", err)
}
return nil
}
func (r *githubClient) BranchExists(ctx context.Context, owner, repository, branchName string) (bool, error) {
_, resp, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0)
if err == nil {
return true, nil
}
if resp.StatusCode == http.StatusNotFound {
return false, nil
}
return false, err
}
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)
@@ -626,44 +267,6 @@ func (r *githubClient) CreatePullRequestComment(ctx context.Context, owner, repo
return nil
}
type realRepositoryContent struct {
real *github.RepositoryContent
}
var _ RepositoryContent = realRepositoryContent{}
func (c realRepositoryContent) IsDirectory() bool {
return c.real.GetType() == "dir"
}
func (c realRepositoryContent) GetFileContent() (string, error) {
return c.real.GetContent()
}
func (c realRepositoryContent) IsSymlink() bool {
return c.real.Target != nil
}
func (c realRepositoryContent) GetPath() string {
return c.real.GetPath()
}
func (c realRepositoryContent) GetSHA() string {
return c.real.GetSHA()
}
func (c realRepositoryContent) GetSize() int64 {
if c.real.Size != nil {
return int64(*c.real.Size)
}
if c.real.Content != nil {
if c, err := c.real.GetContent(); err == nil {
return int64(len(c))
}
}
return 0
}
// listOptions represents pagination parameters for list operations
type listOptions struct {
github.ListOptions
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,4 @@
// Code generated by mockery v2.53.4. DO NOT EDIT.
// Code generated by mockery v2.52.4. DO NOT EDIT.
package github
@@ -21,65 +21,6 @@ func (_m *MockClient) EXPECT() *MockClient_Expecter {
return &MockClient_Expecter{mock: &_m.Mock}
}
// BranchExists provides a mock function with given fields: ctx, owner, repository, branchName
func (_m *MockClient) BranchExists(ctx context.Context, owner string, repository string, branchName string) (bool, error) {
ret := _m.Called(ctx, owner, repository, branchName)
if len(ret) == 0 {
panic("no return value specified for BranchExists")
}
var r0 bool
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (bool, error)); ok {
return rf(ctx, owner, repository, branchName)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) bool); ok {
r0 = rf(ctx, owner, repository, branchName)
} else {
r0 = ret.Get(0).(bool)
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
r1 = rf(ctx, owner, repository, branchName)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockClient_BranchExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BranchExists'
type MockClient_BranchExists_Call struct {
*mock.Call
}
// BranchExists is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repository string
// - branchName string
func (_e *MockClient_Expecter) BranchExists(ctx interface{}, owner interface{}, repository interface{}, branchName interface{}) *MockClient_BranchExists_Call {
return &MockClient_BranchExists_Call{Call: _e.mock.On("BranchExists", ctx, owner, repository, branchName)}
}
func (_c *MockClient_BranchExists_Call) Run(run func(ctx context.Context, owner string, repository string, branchName string)) *MockClient_BranchExists_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string))
})
return _c
}
func (_c *MockClient_BranchExists_Call) Return(_a0 bool, _a1 error) *MockClient_BranchExists_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockClient_BranchExists_Call) RunAndReturn(run func(context.Context, string, string, string) (bool, error)) *MockClient_BranchExists_Call {
_c.Call.Return(run)
return _c
}
// 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)
@@ -142,170 +83,6 @@ func (_c *MockClient_Commits_Call) RunAndReturn(run func(context.Context, string
return _c
}
// CompareCommits provides a mock function with given fields: ctx, owner, repository, base, head
func (_m *MockClient) CompareCommits(ctx context.Context, owner string, repository string, base string, head string) ([]CommitFile, error) {
ret := _m.Called(ctx, owner, repository, base, head)
if len(ret) == 0 {
panic("no return value specified for CompareCommits")
}
var r0 []CommitFile
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) ([]CommitFile, error)); ok {
return rf(ctx, owner, repository, base, head)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) []CommitFile); ok {
r0 = rf(ctx, owner, repository, base, head)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]CommitFile)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) error); ok {
r1 = rf(ctx, owner, repository, base, head)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockClient_CompareCommits_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CompareCommits'
type MockClient_CompareCommits_Call struct {
*mock.Call
}
// CompareCommits is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repository string
// - base string
// - head string
func (_e *MockClient_Expecter) CompareCommits(ctx interface{}, owner interface{}, repository interface{}, base interface{}, head interface{}) *MockClient_CompareCommits_Call {
return &MockClient_CompareCommits_Call{Call: _e.mock.On("CompareCommits", ctx, owner, repository, base, head)}
}
func (_c *MockClient_CompareCommits_Call) Run(run func(ctx context.Context, owner string, repository string, base string, head string)) *MockClient_CompareCommits_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_CompareCommits_Call) Return(_a0 []CommitFile, _a1 error) *MockClient_CompareCommits_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockClient_CompareCommits_Call) RunAndReturn(run func(context.Context, string, string, string, string) ([]CommitFile, error)) *MockClient_CompareCommits_Call {
_c.Call.Return(run)
return _c
}
// CreateBranch provides a mock function with given fields: ctx, owner, repository, sourceBranch, branchName
func (_m *MockClient) CreateBranch(ctx context.Context, owner string, repository string, sourceBranch string, branchName string) error {
ret := _m.Called(ctx, owner, repository, sourceBranch, branchName)
if len(ret) == 0 {
panic("no return value specified for CreateBranch")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok {
r0 = rf(ctx, owner, repository, sourceBranch, branchName)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockClient_CreateBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateBranch'
type MockClient_CreateBranch_Call struct {
*mock.Call
}
// CreateBranch is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repository string
// - sourceBranch string
// - branchName string
func (_e *MockClient_Expecter) CreateBranch(ctx interface{}, owner interface{}, repository interface{}, sourceBranch interface{}, branchName interface{}) *MockClient_CreateBranch_Call {
return &MockClient_CreateBranch_Call{Call: _e.mock.On("CreateBranch", ctx, owner, repository, sourceBranch, branchName)}
}
func (_c *MockClient_CreateBranch_Call) Run(run func(ctx context.Context, owner string, repository string, sourceBranch string, branchName string)) *MockClient_CreateBranch_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_CreateBranch_Call) Return(_a0 error) *MockClient_CreateBranch_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockClient_CreateBranch_Call) RunAndReturn(run func(context.Context, string, string, string, string) error) *MockClient_CreateBranch_Call {
_c.Call.Return(run)
return _c
}
// CreateFile provides a mock function with given fields: ctx, owner, repository, path, branch, message, content
func (_m *MockClient) CreateFile(ctx context.Context, owner string, repository string, path string, branch string, message string, content []byte) error {
ret := _m.Called(ctx, owner, repository, path, branch, message, content)
if len(ret) == 0 {
panic("no return value specified for CreateFile")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, []byte) error); ok {
r0 = rf(ctx, owner, repository, path, branch, message, content)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockClient_CreateFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateFile'
type MockClient_CreateFile_Call struct {
*mock.Call
}
// CreateFile is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repository string
// - path string
// - branch string
// - message string
// - content []byte
func (_e *MockClient_Expecter) CreateFile(ctx interface{}, owner interface{}, repository interface{}, path interface{}, branch interface{}, message interface{}, content interface{}) *MockClient_CreateFile_Call {
return &MockClient_CreateFile_Call{Call: _e.mock.On("CreateFile", ctx, owner, repository, path, branch, message, content)}
}
func (_c *MockClient_CreateFile_Call) Run(run func(ctx context.Context, owner string, repository string, path string, branch string, message string, content []byte)) *MockClient_CreateFile_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), args[5].(string), args[6].([]byte))
})
return _c
}
func (_c *MockClient_CreateFile_Call) Return(_a0 error) *MockClient_CreateFile_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockClient_CreateFile_Call) RunAndReturn(run func(context.Context, string, string, string, string, string, []byte) error) *MockClient_CreateFile_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)
@@ -415,58 +192,6 @@ func (_c *MockClient_CreateWebhook_Call) RunAndReturn(run func(context.Context,
return _c
}
// DeleteFile provides a mock function with given fields: ctx, owner, repository, path, branch, message, hash
func (_m *MockClient) DeleteFile(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string) error {
ret := _m.Called(ctx, owner, repository, path, branch, message, hash)
if len(ret) == 0 {
panic("no return value specified for DeleteFile")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string) error); ok {
r0 = rf(ctx, owner, repository, path, branch, message, hash)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockClient_DeleteFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteFile'
type MockClient_DeleteFile_Call struct {
*mock.Call
}
// DeleteFile is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repository string
// - path string
// - branch string
// - message string
// - hash string
func (_e *MockClient_Expecter) DeleteFile(ctx interface{}, owner interface{}, repository interface{}, path interface{}, branch interface{}, message interface{}, hash interface{}) *MockClient_DeleteFile_Call {
return &MockClient_DeleteFile_Call{Call: _e.mock.On("DeleteFile", ctx, owner, repository, path, branch, message, hash)}
}
func (_c *MockClient_DeleteFile_Call) Run(run func(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string)) *MockClient_DeleteFile_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), args[5].(string), args[6].(string))
})
return _c
}
func (_c *MockClient_DeleteFile_Call) Return(_a0 error) *MockClient_DeleteFile_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockClient_DeleteFile_Call) RunAndReturn(run func(context.Context, string, string, string, string, string, string) error) *MockClient_DeleteFile_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)
@@ -565,206 +290,6 @@ func (_c *MockClient_EditWebhook_Call) RunAndReturn(run func(context.Context, st
return _c
}
// GetBranch provides a mock function with given fields: ctx, owner, repository, branchName
func (_m *MockClient) GetBranch(ctx context.Context, owner string, repository string, branchName string) (Branch, error) {
ret := _m.Called(ctx, owner, repository, branchName)
if len(ret) == 0 {
panic("no return value specified for GetBranch")
}
var r0 Branch
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (Branch, error)); ok {
return rf(ctx, owner, repository, branchName)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) Branch); ok {
r0 = rf(ctx, owner, repository, branchName)
} else {
r0 = ret.Get(0).(Branch)
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
r1 = rf(ctx, owner, repository, branchName)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockClient_GetBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBranch'
type MockClient_GetBranch_Call struct {
*mock.Call
}
// GetBranch is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repository string
// - branchName string
func (_e *MockClient_Expecter) GetBranch(ctx interface{}, owner interface{}, repository interface{}, branchName interface{}) *MockClient_GetBranch_Call {
return &MockClient_GetBranch_Call{Call: _e.mock.On("GetBranch", ctx, owner, repository, branchName)}
}
func (_c *MockClient_GetBranch_Call) Run(run func(ctx context.Context, owner string, repository string, branchName string)) *MockClient_GetBranch_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string))
})
return _c
}
func (_c *MockClient_GetBranch_Call) Return(_a0 Branch, _a1 error) *MockClient_GetBranch_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockClient_GetBranch_Call) RunAndReturn(run func(context.Context, string, string, string) (Branch, error)) *MockClient_GetBranch_Call {
_c.Call.Return(run)
return _c
}
// GetContents provides a mock function with given fields: ctx, owner, repository, path, ref
func (_m *MockClient) GetContents(ctx context.Context, owner string, repository string, path string, ref string) (RepositoryContent, []RepositoryContent, error) {
ret := _m.Called(ctx, owner, repository, path, ref)
if len(ret) == 0 {
panic("no return value specified for GetContents")
}
var r0 RepositoryContent
var r1 []RepositoryContent
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) (RepositoryContent, []RepositoryContent, error)); ok {
return rf(ctx, owner, repository, path, ref)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) RepositoryContent); ok {
r0 = rf(ctx, owner, repository, path, ref)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(RepositoryContent)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) []RepositoryContent); ok {
r1 = rf(ctx, owner, repository, path, ref)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).([]RepositoryContent)
}
}
if rf, ok := ret.Get(2).(func(context.Context, string, string, string, string) error); ok {
r2 = rf(ctx, owner, repository, path, ref)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// MockClient_GetContents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetContents'
type MockClient_GetContents_Call struct {
*mock.Call
}
// GetContents is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repository string
// - path string
// - ref string
func (_e *MockClient_Expecter) GetContents(ctx interface{}, owner interface{}, repository interface{}, path interface{}, ref interface{}) *MockClient_GetContents_Call {
return &MockClient_GetContents_Call{Call: _e.mock.On("GetContents", ctx, owner, repository, path, ref)}
}
func (_c *MockClient_GetContents_Call) Run(run func(ctx context.Context, owner string, repository string, path string, ref string)) *MockClient_GetContents_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_GetContents_Call) Return(fileContents RepositoryContent, dirContents []RepositoryContent, err error) *MockClient_GetContents_Call {
_c.Call.Return(fileContents, dirContents, err)
return _c
}
func (_c *MockClient_GetContents_Call) RunAndReturn(run func(context.Context, string, string, string, string) (RepositoryContent, []RepositoryContent, error)) *MockClient_GetContents_Call {
_c.Call.Return(run)
return _c
}
// GetTree provides a mock function with given fields: ctx, owner, repository, basePath, ref, recursive
func (_m *MockClient) GetTree(ctx context.Context, owner string, repository string, basePath string, ref string, recursive bool) ([]RepositoryContent, bool, error) {
ret := _m.Called(ctx, owner, repository, basePath, ref, recursive)
if len(ret) == 0 {
panic("no return value specified for GetTree")
}
var r0 []RepositoryContent
var r1 bool
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool) ([]RepositoryContent, bool, error)); ok {
return rf(ctx, owner, repository, basePath, ref, recursive)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, bool) []RepositoryContent); ok {
r0 = rf(ctx, owner, repository, basePath, ref, recursive)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]RepositoryContent)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, bool) bool); ok {
r1 = rf(ctx, owner, repository, basePath, ref, recursive)
} else {
r1 = ret.Get(1).(bool)
}
if rf, ok := ret.Get(2).(func(context.Context, string, string, string, string, bool) error); ok {
r2 = rf(ctx, owner, repository, basePath, ref, recursive)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// MockClient_GetTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTree'
type MockClient_GetTree_Call struct {
*mock.Call
}
// GetTree is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repository string
// - basePath string
// - ref string
// - recursive bool
func (_e *MockClient_Expecter) GetTree(ctx interface{}, owner interface{}, repository interface{}, basePath interface{}, ref interface{}, recursive interface{}) *MockClient_GetTree_Call {
return &MockClient_GetTree_Call{Call: _e.mock.On("GetTree", ctx, owner, repository, basePath, ref, recursive)}
}
func (_c *MockClient_GetTree_Call) Run(run func(ctx context.Context, owner string, repository string, basePath string, ref string, recursive bool)) *MockClient_GetTree_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), args[5].(bool))
})
return _c
}
func (_c *MockClient_GetTree_Call) Return(entries []RepositoryContent, truncated bool, err error) *MockClient_GetTree_Call {
_c.Call.Return(entries, truncated, err)
return _c
}
func (_c *MockClient_GetTree_Call) RunAndReturn(run func(context.Context, string, string, string, string, bool) ([]RepositoryContent, bool, error)) *MockClient_GetTree_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)
@@ -824,52 +349,6 @@ func (_c *MockClient_GetWebhook_Call) RunAndReturn(run func(context.Context, str
return _c
}
// IsAuthenticated provides a mock function with given fields: ctx
func (_m *MockClient) IsAuthenticated(ctx context.Context) error {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for IsAuthenticated")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockClient_IsAuthenticated_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsAuthenticated'
type MockClient_IsAuthenticated_Call struct {
*mock.Call
}
// IsAuthenticated is a helper method to define mock.On call
// - ctx context.Context
func (_e *MockClient_Expecter) IsAuthenticated(ctx interface{}) *MockClient_IsAuthenticated_Call {
return &MockClient_IsAuthenticated_Call{Call: _e.mock.On("IsAuthenticated", ctx)}
}
func (_c *MockClient_IsAuthenticated_Call) Run(run func(ctx context.Context)) *MockClient_IsAuthenticated_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *MockClient_IsAuthenticated_Call) Return(_a0 error) *MockClient_IsAuthenticated_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockClient_IsAuthenticated_Call) RunAndReturn(run func(context.Context) error) *MockClient_IsAuthenticated_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)
@@ -991,117 +470,6 @@ func (_c *MockClient_ListWebhooks_Call) RunAndReturn(run func(context.Context, s
return _c
}
// RepoExists provides a mock function with given fields: ctx, owner, repository
func (_m *MockClient) RepoExists(ctx context.Context, owner string, repository string) (bool, error) {
ret := _m.Called(ctx, owner, repository)
if len(ret) == 0 {
panic("no return value specified for RepoExists")
}
var r0 bool
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok {
return rf(ctx, owner, repository)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok {
r0 = rf(ctx, owner, repository)
} else {
r0 = ret.Get(0).(bool)
}
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_RepoExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoExists'
type MockClient_RepoExists_Call struct {
*mock.Call
}
// RepoExists is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repository string
func (_e *MockClient_Expecter) RepoExists(ctx interface{}, owner interface{}, repository interface{}) *MockClient_RepoExists_Call {
return &MockClient_RepoExists_Call{Call: _e.mock.On("RepoExists", ctx, owner, repository)}
}
func (_c *MockClient_RepoExists_Call) Run(run func(ctx context.Context, owner string, repository string)) *MockClient_RepoExists_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string))
})
return _c
}
func (_c *MockClient_RepoExists_Call) Return(_a0 bool, _a1 error) *MockClient_RepoExists_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockClient_RepoExists_Call) RunAndReturn(run func(context.Context, string, string) (bool, error)) *MockClient_RepoExists_Call {
_c.Call.Return(run)
return _c
}
// UpdateFile provides a mock function with given fields: ctx, owner, repository, path, branch, message, hash, content
func (_m *MockClient) UpdateFile(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string, content []byte) error {
ret := _m.Called(ctx, owner, repository, path, branch, message, hash, content)
if len(ret) == 0 {
panic("no return value specified for UpdateFile")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string, []byte) error); ok {
r0 = rf(ctx, owner, repository, path, branch, message, hash, content)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockClient_UpdateFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateFile'
type MockClient_UpdateFile_Call struct {
*mock.Call
}
// UpdateFile is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repository string
// - path string
// - branch string
// - message string
// - hash string
// - content []byte
func (_e *MockClient_Expecter) UpdateFile(ctx interface{}, owner interface{}, repository interface{}, path interface{}, branch interface{}, message interface{}, hash interface{}, content interface{}) *MockClient_UpdateFile_Call {
return &MockClient_UpdateFile_Call{Call: _e.mock.On("UpdateFile", ctx, owner, repository, path, branch, message, hash, content)}
}
func (_c *MockClient_UpdateFile_Call) Run(run func(ctx context.Context, owner string, repository string, path string, branch string, message string, hash string, content []byte)) *MockClient_UpdateFile_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), args[5].(string), args[6].(string), args[7].([]byte))
})
return _c
}
func (_c *MockClient_UpdateFile_Call) Return(_a0 error) *MockClient_UpdateFile_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockClient_UpdateFile_Call) RunAndReturn(run func(context.Context, string, string, string, string, string, string, []byte) error) *MockClient_UpdateFile_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 {
@@ -1,4 +1,4 @@
// Code generated by mockery v2.53.4. DO NOT EDIT.
// Code generated by mockery v2.52.4. DO NOT EDIT.
package github
@@ -1,312 +0,0 @@
// Code generated by mockery v2.53.4. DO NOT EDIT.
package github
import mock "github.com/stretchr/testify/mock"
// MockRepositoryContent is an autogenerated mock type for the RepositoryContent type
type MockRepositoryContent struct {
mock.Mock
}
type MockRepositoryContent_Expecter struct {
mock *mock.Mock
}
func (_m *MockRepositoryContent) EXPECT() *MockRepositoryContent_Expecter {
return &MockRepositoryContent_Expecter{mock: &_m.Mock}
}
// GetFileContent provides a mock function with no fields
func (_m *MockRepositoryContent) GetFileContent() (string, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetFileContent")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func() (string, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockRepositoryContent_GetFileContent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFileContent'
type MockRepositoryContent_GetFileContent_Call struct {
*mock.Call
}
// GetFileContent is a helper method to define mock.On call
func (_e *MockRepositoryContent_Expecter) GetFileContent() *MockRepositoryContent_GetFileContent_Call {
return &MockRepositoryContent_GetFileContent_Call{Call: _e.mock.On("GetFileContent")}
}
func (_c *MockRepositoryContent_GetFileContent_Call) Run(run func()) *MockRepositoryContent_GetFileContent_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRepositoryContent_GetFileContent_Call) Return(_a0 string, _a1 error) *MockRepositoryContent_GetFileContent_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockRepositoryContent_GetFileContent_Call) RunAndReturn(run func() (string, error)) *MockRepositoryContent_GetFileContent_Call {
_c.Call.Return(run)
return _c
}
// GetPath provides a mock function with no fields
func (_m *MockRepositoryContent) GetPath() string {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetPath")
}
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// MockRepositoryContent_GetPath_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPath'
type MockRepositoryContent_GetPath_Call struct {
*mock.Call
}
// GetPath is a helper method to define mock.On call
func (_e *MockRepositoryContent_Expecter) GetPath() *MockRepositoryContent_GetPath_Call {
return &MockRepositoryContent_GetPath_Call{Call: _e.mock.On("GetPath")}
}
func (_c *MockRepositoryContent_GetPath_Call) Run(run func()) *MockRepositoryContent_GetPath_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRepositoryContent_GetPath_Call) Return(_a0 string) *MockRepositoryContent_GetPath_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockRepositoryContent_GetPath_Call) RunAndReturn(run func() string) *MockRepositoryContent_GetPath_Call {
_c.Call.Return(run)
return _c
}
// GetSHA provides a mock function with no fields
func (_m *MockRepositoryContent) 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
}
// MockRepositoryContent_GetSHA_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSHA'
type MockRepositoryContent_GetSHA_Call struct {
*mock.Call
}
// GetSHA is a helper method to define mock.On call
func (_e *MockRepositoryContent_Expecter) GetSHA() *MockRepositoryContent_GetSHA_Call {
return &MockRepositoryContent_GetSHA_Call{Call: _e.mock.On("GetSHA")}
}
func (_c *MockRepositoryContent_GetSHA_Call) Run(run func()) *MockRepositoryContent_GetSHA_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRepositoryContent_GetSHA_Call) Return(_a0 string) *MockRepositoryContent_GetSHA_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockRepositoryContent_GetSHA_Call) RunAndReturn(run func() string) *MockRepositoryContent_GetSHA_Call {
_c.Call.Return(run)
return _c
}
// GetSize provides a mock function with no fields
func (_m *MockRepositoryContent) GetSize() int64 {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetSize")
}
var r0 int64
if rf, ok := ret.Get(0).(func() int64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(int64)
}
return r0
}
// MockRepositoryContent_GetSize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSize'
type MockRepositoryContent_GetSize_Call struct {
*mock.Call
}
// GetSize is a helper method to define mock.On call
func (_e *MockRepositoryContent_Expecter) GetSize() *MockRepositoryContent_GetSize_Call {
return &MockRepositoryContent_GetSize_Call{Call: _e.mock.On("GetSize")}
}
func (_c *MockRepositoryContent_GetSize_Call) Run(run func()) *MockRepositoryContent_GetSize_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRepositoryContent_GetSize_Call) Return(_a0 int64) *MockRepositoryContent_GetSize_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockRepositoryContent_GetSize_Call) RunAndReturn(run func() int64) *MockRepositoryContent_GetSize_Call {
_c.Call.Return(run)
return _c
}
// IsDirectory provides a mock function with no fields
func (_m *MockRepositoryContent) IsDirectory() bool {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for IsDirectory")
}
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// MockRepositoryContent_IsDirectory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsDirectory'
type MockRepositoryContent_IsDirectory_Call struct {
*mock.Call
}
// IsDirectory is a helper method to define mock.On call
func (_e *MockRepositoryContent_Expecter) IsDirectory() *MockRepositoryContent_IsDirectory_Call {
return &MockRepositoryContent_IsDirectory_Call{Call: _e.mock.On("IsDirectory")}
}
func (_c *MockRepositoryContent_IsDirectory_Call) Run(run func()) *MockRepositoryContent_IsDirectory_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRepositoryContent_IsDirectory_Call) Return(_a0 bool) *MockRepositoryContent_IsDirectory_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockRepositoryContent_IsDirectory_Call) RunAndReturn(run func() bool) *MockRepositoryContent_IsDirectory_Call {
_c.Call.Return(run)
return _c
}
// IsSymlink provides a mock function with no fields
func (_m *MockRepositoryContent) IsSymlink() bool {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for IsSymlink")
}
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// MockRepositoryContent_IsSymlink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsSymlink'
type MockRepositoryContent_IsSymlink_Call struct {
*mock.Call
}
// IsSymlink is a helper method to define mock.On call
func (_e *MockRepositoryContent_Expecter) IsSymlink() *MockRepositoryContent_IsSymlink_Call {
return &MockRepositoryContent_IsSymlink_Call{Call: _e.mock.On("IsSymlink")}
}
func (_c *MockRepositoryContent_IsSymlink_Call) Run(run func()) *MockRepositoryContent_IsSymlink_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRepositoryContent_IsSymlink_Call) Return(_a0 bool) *MockRepositoryContent_IsSymlink_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockRepositoryContent_IsSymlink_Call) RunAndReturn(run func() bool) *MockRepositoryContent_IsSymlink_Call {
_c.Call.Return(run)
return _c
}
// NewMockRepositoryContent creates a new instance of MockRepositoryContent. 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 NewMockRepositoryContent(t interface {
mock.TestingT
Cleanup(func())
}) *MockRepositoryContent {
mock := &MockRepositoryContent{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
@@ -0,0 +1,240 @@
package github
import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field"
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/git"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
)
// Make sure all public functions of this struct call the (*githubRepository).logger function, to ensure the GH repo details are included.
type githubRepository struct {
gitRepo 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 NewGitHub(
ctx context.Context,
config *provisioning.Repository,
gitRepo git.GitRepository,
factory *Factory,
token string,
) (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,
gitRepo: gitRepo,
gh: factory.New(ctx, token), // TODO, baseURL from config
owner: owner,
repo: repo,
}, nil
}
func (r *githubRepository) Config() *provisioning.Repository {
return r.gitRepo.Config()
}
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.gitRepo.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.gitRepo.Validate()
}
func ParseOwnerRepoGithub(giturl string) (owner string, repo string, err error) {
parsed, e := url.Parse(strings.TrimSuffix(giturl, ".git"))
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.gitRepo.Test(ctx)
}
// ReadResource implements provisioning.Repository.
func (r *githubRepository) Read(ctx context.Context, filePath, ref string) (*repository.FileInfo, error) {
return r.gitRepo.Read(ctx, filePath, ref)
}
func (r *githubRepository) ReadTree(ctx context.Context, ref string) ([]repository.FileTreeEntry, error) {
return r.gitRepo.ReadTree(ctx, ref)
}
func (r *githubRepository) Create(ctx context.Context, path, ref string, data []byte, comment string) error {
return r.gitRepo.Create(ctx, path, ref, data, comment)
}
func (r *githubRepository) Update(ctx context.Context, path, ref string, data []byte, comment string) error {
return r.gitRepo.Update(ctx, path, ref, data, comment)
}
func (r *githubRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error {
return r.gitRepo.Write(ctx, path, ref, data, message)
}
func (r *githubRepository) Delete(ctx context.Context, path, ref, comment string) error {
return r.gitRepo.Delete(ctx, path, ref, comment)
}
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
}
func (r *githubRepository) LatestRef(ctx context.Context) (string, error) {
return r.gitRepo.LatestRef(ctx)
}
func (r *githubRepository) CompareFiles(ctx context.Context, base, ref string) ([]repository.VersionedFileChange, error) {
return r.gitRepo.CompareFiles(ctx, base, ref)
}
// ResourceURLs implements RepositoryWithURLs.
func (r *githubRepository) ResourceURLs(ctx context.Context, file *repository.FileInfo) (*provisioning.ResourceURLs, error) {
cfg := r.config.Spec.GitHub
if file.Path == "" || cfg == nil {
return nil, nil
}
ref := file.Ref
if ref == "" {
ref = cfg.Branch
}
urls := &provisioning.ResourceURLs{
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
}
func (r *githubRepository) Stage(ctx context.Context, opts repository.StageOptions) (repository.StagedRepository, error) {
return r.gitRepo.Stage(ctx, opts)
}
File diff suppressed because it is too large Load Diff