* 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.
237 lines
6.4 KiB
Go
237 lines
6.4 KiB
Go
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
|
|
}
|