You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-scm/scm/driver/stash/repo.go

418 lines
11 KiB
Go

// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package stash
import (
"context"
"fmt"
"net/url"
"strconv"
"strings"
"git.awesome-for.me/liuzhiguo/go-scm/scm"
)
type repository struct {
Slug string `json:"slug"`
ID int `json:"id"`
Name string `json:"name"`
ScmID string `json:"scmId"`
State string `json:"state"`
StatusMessage string `json:"statusMessage"`
Forkable bool `json:"forkable"`
Project struct {
Key string `json:"key"`
ID int `json:"id"`
Name string `json:"name"`
Public bool `json:"public"`
Type string `json:"type"`
Links struct {
Self []link `json:"self"`
} `json:"links"`
} `json:"project"`
Public bool `json:"public"`
Links struct {
Clone []link `json:"clone"`
Self []link `json:"self"`
} `json:"links"`
}
type repositories struct {
pagination
Values []*repository `json:"values"`
}
type link struct {
Href string `json:"href"`
Name string `json:"name"`
}
type perms struct {
Values []*perm `json:"values"`
}
type perm struct {
Permissions string `json:"permission"`
}
type hooks struct {
pagination
Values []*hook `json:"values"`
}
type hook struct {
ID int `json:"id"`
Name string `json:"name"`
CreatedDate int64 `json:"createdDate"`
UpdatedDate int64 `json:"updatedDate"`
Events []string `json:"events"`
URL string `json:"url"`
Active bool `json:"active"`
Config struct {
Secret string `json:"secret"`
} `json:"configuration"`
}
type hookInput struct {
Name string `json:"name"`
Events []string `json:"events"`
URL string `json:"url"`
Active bool `json:"active"`
Config struct {
Secret string `json:"secret,omitempty"`
} `json:"configuration"`
}
type status struct {
State string `json:"state"`
Key string `json:"key"`
Name string `json:"name"`
URL string `json:"url"`
Desc string `json:"description"`
}
type repositoryService struct {
client *wrapper
}
// Find returns the repository by name.
func (s *repositoryService) Find(ctx context.Context, repo string) (*scm.Repository, *scm.Response, error) {
namespace, name := scm.Split(repo)
path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s", namespace, name)
out := new(repository)
res, err := s.client.do(ctx, "GET", path, nil, out)
outputRepo := convertRepository(out)
branch := new(branch)
pathBranch := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/branches/default", namespace, name)
_, errBranch := s.client.do(ctx, "GET", pathBranch, nil, branch)
if errBranch == nil {
outputRepo.Branch = branch.DisplayID
}
if err == nil {
err = errBranch
}
return outputRepo, res, err
}
// FindHook returns a repository hook.
func (s *repositoryService) FindHook(ctx context.Context, repo string, id string) (*scm.Hook, *scm.Response, error) {
namespace, name := scm.Split(repo)
path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/webhooks/%s", namespace, name, id)
out := new(hook)
res, err := s.client.do(ctx, "GET", path, nil, out)
return convertHook(out), res, err
}
// FindPerms returns the repository permissions.
func (s *repositoryService) FindPerms(ctx context.Context, repo string) (*scm.Perm, *scm.Response, error) {
// HACK: test if the user has read access to the repository.
_, _, err := s.Find(ctx, repo)
if err != nil {
return &scm.Perm{
Pull: false,
Push: false,
Admin: false,
}, nil, nil
}
// HACK: test if the user has admin access to the repository.
_, _, err = s.ListHooks(ctx, repo, scm.ListOptions{})
if err == nil {
return &scm.Perm{
Pull: true,
Push: true,
Admin: true,
}, nil, nil
}
// HACK: test if the user has write access to the repository.
namespace, _ := scm.Split(repo)
repos, _, _ := s.listWrite(ctx, repo)
for _, repo := range repos {
if repo.Namespace == namespace {
return &scm.Perm{
Pull: true,
Push: true,
Admin: false,
}, nil, nil
}
}
return &scm.Perm{
Pull: true,
Push: false,
Admin: false,
}, nil, nil
}
// List returns the user repository list.
func (s *repositoryService) List(ctx context.Context, opts scm.ListOptions) ([]*scm.Repository, *scm.Response, error) {
path := fmt.Sprintf("rest/api/1.0/repos?%s", encodeListRoleOptions(opts))
out := new(repositories)
res, err := s.client.do(ctx, "GET", path, nil, &out)
if res != nil && !out.pagination.LastPage.Bool {
res.Page.First = 1
res.Page.Next = opts.Page + 1
}
return convertRepositoryList(out), res, err
}
// ListV2 returns the user repository list based on the searchTerm passed.
func (s *repositoryService) ListV2(ctx context.Context, opts scm.RepoListOptions) ([]*scm.Repository, *scm.Response, error) {
path := fmt.Sprintf("rest/api/1.0/repos?%s", encodeRepoListOptions(opts))
out := new(repositories)
res, err := s.client.do(ctx, "GET", path, nil, &out)
if res != nil && !out.pagination.LastPage.Bool {
res.Page.First = 1
res.Page.Next = opts.ListOptions.Page + 1
}
return convertRepositoryList(out), res, err
}
// listWrite returns the user repository list.
func (s *repositoryService) listWrite(ctx context.Context, repo string) ([]*scm.Repository, *scm.Response, error) {
_, name := scm.Split(repo)
path := fmt.Sprintf("rest/api/1.0/repos?size=1000&permission=REPO_WRITE&name=%s", name)
out := new(repositories)
res, err := s.client.do(ctx, "GET", path, nil, out)
return convertRepositoryList(out), res, err
}
// ListHooks returns a list or repository hooks.
func (s *repositoryService) ListHooks(ctx context.Context, repo string, opts scm.ListOptions) ([]*scm.Hook, *scm.Response, error) {
namespace, name := scm.Split(repo)
path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/webhooks?%s", namespace, name, encodeListOptions(opts))
out := new(hooks)
res, err := s.client.do(ctx, "GET", path, nil, out)
if res != nil && !out.pagination.LastPage.Bool {
res.Page.First = 1
res.Page.Next = opts.Page + 1
}
return convertHookList(out), res, err
}
// ListStatus returns a list of commit statuses.
func (s *repositoryService) ListStatus(ctx context.Context, repo, ref string, opts scm.ListOptions) ([]*scm.Status, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
// CreateHook creates a new repository webhook.
func (s *repositoryService) CreateHook(ctx context.Context, repo string, input *scm.HookInput) (*scm.Hook, *scm.Response, error) {
namespace, name := scm.Split(repo)
path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/webhooks", namespace, name)
in := new(hookInput)
in.URL = input.Target
in.Active = true
in.Name = input.Name
in.Config.Secret = input.Secret
in.Events = append(
input.NativeEvents,
convertFromHookEvents(input.Events)...,
)
out := new(hook)
res, err := s.client.do(ctx, "POST", path, in, out)
if err != nil && isUnknownHookEvent(err) {
downgradeHookInput(in)
res, err = s.client.do(ctx, "POST", path, in, out)
}
return convertHook(out), res, err
}
// CreateStatus creates a new commit status.
func (s *repositoryService) CreateStatus(ctx context.Context, repo, ref string, input *scm.StatusInput) (*scm.Status, *scm.Response, error) {
path := fmt.Sprintf("rest/build-status/1.0/commits/%s", ref)
in := status{
State: convertFromState(input.State),
Key: input.Label,
Name: input.Label,
URL: input.Target,
Desc: input.Desc,
}
res, err := s.client.do(ctx, "POST", path, in, nil)
return &scm.Status{
State: input.State,
Label: input.Label,
Desc: input.Desc,
Target: input.Target,
}, res, err
}
// UpdateHook updates new repository webhook.
func (s *repositoryService) UpdateHook(ctx context.Context, repo, id string, input *scm.HookInput) (*scm.Hook, *scm.Response, error) {
namespace, name := scm.Split(repo)
path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/webhooks/%s", namespace, name, id)
in := new(hookInput)
in.URL = input.Target
in.Active = true
in.Name = input.Name
in.Config.Secret = input.Secret
in.Events = append(
input.NativeEvents,
convertFromHookEvents(input.Events)...,
)
out := new(hook)
res, err := s.client.do(ctx, "PUT", path, in, out)
if err != nil && isUnknownHookEvent(err) {
downgradeHookInput(in)
res, err = s.client.do(ctx, "PUT", path, in, out)
}
return convertHook(out), res, err
}
// DeleteHook deletes a repository webhook.
func (s *repositoryService) DeleteHook(ctx context.Context, repo string, id string) (*scm.Response, error) {
namespace, name := scm.Split(repo)
path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/webhooks/%s", namespace, name, id)
return s.client.do(ctx, "DELETE", path, nil, nil)
}
// helper function to convert from the gogs repository list to
// the common repository structure.
func convertRepositoryList(from *repositories) []*scm.Repository {
to := []*scm.Repository{}
for _, v := range from.Values {
to = append(to, convertRepository(v))
}
return to
}
// helper function to convert from the gogs repository structure
// to the common repository structure.
func convertRepository(from *repository) *scm.Repository {
return &scm.Repository{
ID: strconv.Itoa(from.ID),
Name: from.Slug,
Namespace: from.Project.Key,
Link: extractSelfLink(from.Links.Self),
Branch: "master",
Private: !from.Public,
CloneSSH: extractLink(from.Links.Clone, "ssh"),
Clone: anonymizeLink(extractLink(from.Links.Clone, "http")),
}
}
func extractLink(links []link, name string) (href string) {
for _, link := range links {
if link.Name == name {
return link.Href
}
}
return
}
func extractSelfLink(links []link) (href string) {
for _, link := range links {
return link.Href
}
return
}
func anonymizeLink(link string) (href string) {
parsed, err := url.Parse(link)
if err != nil {
return link
}
parsed.User = nil
return parsed.String()
}
func convertHookList(from *hooks) []*scm.Hook {
to := []*scm.Hook{}
for _, v := range from.Values {
to = append(to, convertHook(v))
}
return to
}
func convertHook(from *hook) *scm.Hook {
return &scm.Hook{
ID: strconv.Itoa(from.ID),
Name: from.Name,
Active: from.Active,
Target: from.URL,
Events: from.Events,
}
}
func convertFromHookEvents(from scm.HookEvents) []string {
var events []string
if from.Push || from.Branch || from.Tag {
events = append(events, "repo:refs_changed")
}
if from.PullRequest {
events = append(events, "pr:declined")
events = append(events, "pr:modified")
events = append(events, "pr:deleted")
events = append(events, "pr:opened")
events = append(events, "pr:merged")
events = append(events, "pr:from_ref_updated")
}
if from.PullRequestComment {
events = append(events, "pr:comment:added")
events = append(events, "pr:comment:deleted")
events = append(events, "pr:comment:edited")
}
return events
}
func isUnknownHookEvent(err error) bool {
return strings.Contains(err.Error(), "pr:from_ref_updated is unknown")
}
func downgradeHookInput(in *hookInput) {
var events []string
for _, event := range in.Events {
if event != "pr:from_ref_updated" {
events = append(events, event)
}
}
in.Events = events
}
func convertFromState(from scm.State) string {
switch from {
case scm.StatePending, scm.StateRunning:
return "INPROGRESS"
case scm.StateSuccess:
return "SUCCESSFUL"
default:
return "FAILED"
}
}
func convertState(from string) scm.State {
switch from {
case "FAILED":
return scm.StateFailure
case "INPROGRESS":
return scm.StatePending
case "SUCCESSFUL":
return scm.StateSuccess
default:
return scm.StateUnknown
}
}