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.
307 lines
11 KiB
Go
307 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 azure
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"git.awesome-for.me/liuzhiguo/go-scm/scm"
|
|
)
|
|
|
|
// RepositoryService implements the repository service for
|
|
// the GitHub driver.
|
|
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) {
|
|
// https://docs.microsoft.com/en-us/rest/api/azure/devops/git/repositories/get?view=azure-devops-rest-4.1
|
|
if s.client.project == "" {
|
|
return nil, nil, ProjectRequiredError()
|
|
}
|
|
endpoint := fmt.Sprintf("%s/%s/_apis/git/repositories/%s?api-version=6.0", s.client.owner, s.client.project, repo)
|
|
|
|
out := new(repository)
|
|
res, err := s.client.do(ctx, "GET", endpoint, nil, &out)
|
|
return convertRepository(out), res, err
|
|
}
|
|
|
|
// FindHook returns a repository hook.
|
|
func (s *RepositoryService) FindHook(ctx context.Context, repo string, id string) (*scm.Hook, *scm.Response, error) {
|
|
return nil, nil, scm.ErrNotSupported
|
|
}
|
|
|
|
// FindPerms returns the repository permissions.
|
|
func (s *RepositoryService) FindPerms(ctx context.Context, repo string) (*scm.Perm, *scm.Response, error) {
|
|
return nil, nil, scm.ErrNotSupported
|
|
}
|
|
|
|
// List returns the user repository list.
|
|
func (s *RepositoryService) List(ctx context.Context, opts scm.ListOptions) ([]*scm.Repository, *scm.Response, error) {
|
|
// https://docs.microsoft.com/en-us/rest/api/azure/devops/git/repositories/list?view=azure-devops-rest-6.0
|
|
var endpoint string
|
|
if s.client.project == "" {
|
|
endpoint = fmt.Sprintf("%s/_apis/git/repositories?api-version=6.0", s.client.owner)
|
|
} else {
|
|
endpoint = fmt.Sprintf("%s/%s/_apis/git/repositories?api-version=6.0", s.client.owner, s.client.project)
|
|
}
|
|
|
|
out := new(repositories)
|
|
res, err := s.client.do(ctx, "GET", endpoint, nil, &out)
|
|
return convertRepositoryList(out), res, err
|
|
}
|
|
|
|
// ListV2 returns the user repository list.
|
|
func (s *RepositoryService) ListV2(ctx context.Context, opts scm.RepoListOptions) ([]*scm.Repository, *scm.Response, error) {
|
|
// Azure does not support search filters, hence calling List api without search filtering
|
|
return s.List(ctx, opts.ListOptions)
|
|
}
|
|
|
|
// ListHooks returns a list or repository hooks.
|
|
func (s *RepositoryService) ListHooks(ctx context.Context, repo string, opts scm.ListOptions) ([]*scm.Hook, *scm.Response, error) {
|
|
// https://docs.microsoft.com/en-us/rest/api/azure/devops/hooks/subscriptions/list?view=azure-devops-rest-6.0
|
|
if s.client.project == "" {
|
|
return nil, nil, ProjectRequiredError()
|
|
}
|
|
projectID, projErr := s.getProjectIDFromProjectName(ctx, s.client.project)
|
|
if projErr != nil {
|
|
return nil, nil, fmt.Errorf("ListHooks was unable to look up the project's projectID, %s", projErr)
|
|
}
|
|
endpoint := fmt.Sprintf("%s/_apis/hooks/subscriptions?api-version=6.0", s.client.owner)
|
|
out := new(subscriptions)
|
|
res, err := s.client.do(ctx, "GET", endpoint, nil, &out)
|
|
return convertHookList(out.Value, projectID, repo), 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) {
|
|
// https://docs.microsoft.com/en-us/rest/api/azure/devops/hooks/subscriptions/create?view=azure-devops-rest-6.0
|
|
if s.client.project == "" {
|
|
return nil, nil, ProjectRequiredError()
|
|
}
|
|
endpoint := fmt.Sprintf("%s/_apis/hooks/subscriptions?api-version=6.0", s.client.owner)
|
|
in := new(subscription)
|
|
in.Status = "enabled"
|
|
in.PublisherID = "tfs"
|
|
in.ResourceVersion = "1.0"
|
|
in.ConsumerID = "webHooks"
|
|
in.ConsumerActionID = "httpRequest"
|
|
// we do not support scm hookevents, only native events
|
|
if input.NativeEvents == nil {
|
|
return nil, nil, fmt.Errorf("CreateHook, You must pass at least one native event")
|
|
}
|
|
if len(input.NativeEvents) > 1 {
|
|
return nil, nil, fmt.Errorf("CreateHook, Azure only allows the creation of a single hook at a time %v", input.NativeEvents)
|
|
}
|
|
in.EventType = input.NativeEvents[0]
|
|
// publisher
|
|
projectID, projErr := s.getProjectIDFromProjectName(ctx, s.client.project)
|
|
if projErr != nil {
|
|
return nil, nil, fmt.Errorf("CreateHook was unable to look up the project's projectID, %s", projErr)
|
|
}
|
|
in.PublisherInputs.ProjectID = projectID
|
|
in.PublisherInputs.Repository = repo
|
|
// consumer
|
|
in.ConsumerInputs.URL = input.Target
|
|
if input.SkipVerify {
|
|
in.ConsumerInputs.AcceptUntrustedCerts = "enabled"
|
|
}
|
|
// with version 1.0, azure provides incomplete data for issue-comment
|
|
if in.EventType == "ms.vss-code.git-pullrequest-comment-event" {
|
|
in.ResourceVersion = "2.0"
|
|
}
|
|
out := new(subscription)
|
|
res, err := s.client.do(ctx, "POST", endpoint, 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) {
|
|
return nil, nil, scm.ErrNotSupported
|
|
}
|
|
|
|
// CreateDeployStatus creates a new deployment status.
|
|
func (s *RepositoryService) CreateDeployStatus(ctx context.Context, repo string, input *scm.DeployStatus) (*scm.DeployStatus, *scm.Response, error) {
|
|
return nil, nil, scm.ErrNotSupported
|
|
}
|
|
|
|
// UpdateHook updates a repository webhook.
|
|
func (s *RepositoryService) UpdateHook(ctx context.Context, repo, id string, input *scm.HookInput) (*scm.Hook, *scm.Response, error) {
|
|
return nil, nil, scm.ErrNotSupported
|
|
}
|
|
|
|
// DeleteHook deletes a repository webhook.
|
|
func (s *RepositoryService) DeleteHook(ctx context.Context, repo, id string) (*scm.Response, error) {
|
|
// https://docs.microsoft.com/en-us/rest/api/azure/devops/hooks/subscriptions/delete?view=azure-devops-rest-6.0
|
|
if s.client.project == "" {
|
|
return nil, ProjectRequiredError()
|
|
}
|
|
endpoint := fmt.Sprintf("%s/_apis/hooks/subscriptions/%s?api-version=6.0", s.client.owner, id)
|
|
return s.client.do(ctx, "DELETE", endpoint, nil, nil)
|
|
}
|
|
|
|
// helper function to return the projectID from the project name
|
|
func (s *RepositoryService) getProjectIDFromProjectName(ctx context.Context, projectName string) (string, error) {
|
|
// https://docs.microsoft.com/en-us/rest/api/azure/devops/core/projects/list?view=azure-devops-rest-6.0
|
|
projectName, err := url.PathUnescape(projectName)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to unscape project: %s", projectName)
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("%s/_apis/projects?api-version=6.0", s.client.owner)
|
|
type projects struct {
|
|
Count int64 `json:"count"`
|
|
Value []struct {
|
|
Description string `json:"description"`
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
State string `json:"state"`
|
|
URL string `json:"url"`
|
|
} `json:"value"`
|
|
}
|
|
|
|
out := new(projects)
|
|
response, err := s.client.do(ctx, "GET", endpoint, nil, &out)
|
|
if err != nil {
|
|
fmt.Println(response)
|
|
return "", fmt.Errorf("failed to list projects: %s", err)
|
|
}
|
|
for _, v := range out.Value {
|
|
if v.Name == projectName {
|
|
return v.ID, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("failed to find project id for %s", projectName)
|
|
}
|
|
|
|
type repositories struct {
|
|
Count int64 `json:"count"`
|
|
Value []*repository `json:"value"`
|
|
}
|
|
|
|
type repository struct {
|
|
DefaultBranch string `json:"defaultBranch"`
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Project struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
State string `json:"state"`
|
|
URL string `json:"url"`
|
|
} `json:"project"`
|
|
RemoteURL string `json:"remoteUrl"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type subscriptions struct {
|
|
Count int64 `json:"count"`
|
|
Value []*subscription `json:"value"`
|
|
}
|
|
|
|
type subscription struct {
|
|
ActionDescription string `json:"actionDescription"`
|
|
ConsumerActionID string `json:"consumerActionId"`
|
|
ConsumerID string `json:"consumerId"`
|
|
ConsumerInputs struct {
|
|
AccountName string `json:"accountName,omitempty"`
|
|
AcceptUntrustedCerts string `json:"acceptUntrustedCerts,omitempty"`
|
|
AddToTop string `json:"addToTop,omitempty"`
|
|
APIToken string `json:"apiToken,omitempty"`
|
|
BoardID string `json:"boardId,omitempty"`
|
|
BuildName string `json:"buildName,omitempty"`
|
|
BuildParameterized string `json:"buildParameterized,omitempty"`
|
|
FeedID string `json:"feedId,omitempty"`
|
|
ListID string `json:"listId,omitempty"`
|
|
PackageSourceID string `json:"packageSourceId,omitempty"`
|
|
Password string `json:"password,omitempty"`
|
|
ServerBaseURL string `json:"serverBaseUrl,omitempty"`
|
|
URL string `json:"url,omitempty"`
|
|
UserToken string `json:"userToken,omitempty"`
|
|
Username string `json:"username,omitempty"`
|
|
} `json:"consumerInputs"`
|
|
CreatedBy struct {
|
|
ID string `json:"id"`
|
|
} `json:"createdBy"`
|
|
CreatedDate string `json:"createdDate"`
|
|
EventDescription string `json:"eventDescription"`
|
|
EventType string `json:"eventType"`
|
|
ID string `json:"id"`
|
|
ModifiedBy struct {
|
|
ID string `json:"id"`
|
|
} `json:"modifiedBy"`
|
|
ModifiedDate string `json:"modifiedDate"`
|
|
ProbationRetries int64 `json:"probationRetries"`
|
|
PublisherID string `json:"publisherId"`
|
|
PublisherInputs struct {
|
|
AreaPath string `json:"areaPath,omitempty"`
|
|
Branch string `json:"branch,omitempty"`
|
|
BuildStatus string `json:"buildStatus,omitempty"`
|
|
ChangedFields string `json:"changedFields,omitempty"`
|
|
CommentPattern string `json:"commentPattern,omitempty"`
|
|
DefinitionName string `json:"definitionName,omitempty"`
|
|
HostID string `json:"hostId,omitempty"`
|
|
Path string `json:"path,omitempty"`
|
|
ProjectID string `json:"projectId,omitempty"`
|
|
Repository string `json:"repository,omitempty"`
|
|
TfsSubscriptionID string `json:"tfsSubscriptionId,omitempty"`
|
|
WorkItemType string `json:"workItemType,omitempty"`
|
|
} `json:"publisherInputs"`
|
|
ResourceVersion string `json:"resourceVersion"`
|
|
Status string `json:"status"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// 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.Value {
|
|
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: from.ID,
|
|
Name: from.Name,
|
|
Link: from.URL,
|
|
Branch: scm.TrimRef(from.DefaultBranch),
|
|
}
|
|
}
|
|
|
|
func convertHookList(from []*subscription, projectFilter string, repositoryFilter string) []*scm.Hook {
|
|
to := []*scm.Hook{}
|
|
for _, v := range from {
|
|
if repositoryFilter != "" && projectFilter == v.PublisherInputs.ProjectID && repositoryFilter == v.PublisherInputs.Repository {
|
|
to = append(to, convertHook(v))
|
|
}
|
|
}
|
|
return to
|
|
}
|
|
|
|
func convertHook(from *subscription) *scm.Hook {
|
|
returnVal := &scm.Hook{
|
|
ID: from.ID,
|
|
|
|
Active: from.Status == "enabled",
|
|
Target: from.ConsumerInputs.URL,
|
|
Events: []string{from.EventType},
|
|
SkipVerify: from.ConsumerInputs.AcceptUntrustedCerts == "true",
|
|
}
|
|
|
|
return returnVal
|
|
}
|