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.
418 lines
11 KiB
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
|
|
}
|
|
}
|