horizoncd/horizon

View on GitHub
lib/gitlab/gitlab.go

Summary

Maintainability
D
1 day
Test Coverage
// Copyright © 2023 Horizoncd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
 
package gitlab
 
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"time"
 
herrors "github.com/horizoncd/horizon/core/errors"
perror "github.com/horizoncd/horizon/pkg/errors"
"github.com/horizoncd/horizon/pkg/util/log"
"github.com/horizoncd/horizon/pkg/util/wlog"
 
"github.com/xanzy/go-gitlab"
)
 
// Interface to interact with gitlab
// nolint
//
//go:generate mockgen -source=$GOFILE -destination=../../mock/lib/gitlab/mock_gitlab.go -package=mock_gitlab
type Interface interface {
// GetGroup gets a group's detail with the given gid.
// The gid can be the group's ID or relative path such as first/second/third.
// See https://docs.gitlab.com/ee/api/groups.html#details-of-a-group for more information.
GetGroup(ctx context.Context, gid interface{}) (*gitlab.Group, error)
 
// ListGroupProjects list a group's project
ListGroupProjects(ctx context.Context, gid interface{}, page, perPage int) ([]*gitlab.Project, error)
 
// CreateGroup create a gitlab group with the given name and path.
// The parentID is alternative, if you specify the parentID, it will
// create a subgroup of this parent.
// See https://docs.gitlab.com/ee/api/groups.html#new-group for more information.
CreateGroup(ctx context.Context, name, path string, parentID *int, visibility string) (*gitlab.Group, error)
 
// DeleteGroup delete a gitlab group with the given gid.
// The gid can be the group's ID or relative path such as first/second/third.
// See https://docs.gitlab.com/ee/api/groups.html#remove-group for more information.
DeleteGroup(ctx context.Context, gid interface{}) error
 
// GetProject get a project with the specified pid.
// The pid can be the project's ID or relative path such as fist/second.
// See https://docs.gitlab.com/ee/api/projects.html#get-single-project for more information.
GetProject(ctx context.Context, pid interface{}) (*gitlab.Project, error)
 
// CreateProject create a project under the specified group.
// See https://docs.gitlab.com/ee/api/projects.html#create-project for more information.
CreateProject(ctx context.Context, name string, groupID int, visibility string) (*gitlab.Project, error)
 
// DeleteProject delete a project with the given pid.
// The pid can be the project's ID or relative path such as fist/second.
// See https://docs.gitlab.com/ee/api/projects.html#delete-project for more information.
DeleteProject(ctx context.Context, pid interface{}) error
 
// GetCommit get a specified commit
// The pid can be the project's ID or relative path such as fist/second.
// See https://docs.gitlab.com/ee/api/commits.html#get-a-single-commit for more information.
GetCommit(ctx context.Context, pid interface{}, commit string) (_ *gitlab.Commit, err error)
 
// GetBranch get branch of the specified project.
// The pid can be the project's ID or relative path such as fist/second.
// See https://docs.gitlab.com/ee/api/branches.html#get-single-repository-branch for more information.
GetBranch(ctx context.Context, pid interface{}, branch string) (*gitlab.Branch, error)
 
// GetTag get tag of the specified project.
// The pid can be the project's ID or relative path such as fist/second.
// see https://docs.gitlab.com/ee/api/tags.html#get-a-single-repository-tag for more information
GetTag(ctx context.Context, pid interface{}, tag string) (_ *gitlab.Tag, err error)
 
// CreateBranch create a branch from fromRef for the specified project.
// The pid can be the project's ID or relative path such as fist/second.
// The fromRef can be the name of branch, tag or commit.
// See https://docs.gitlab.com/ee/api/branches.html#create-repository-branch for more information.
CreateBranch(ctx context.Context, pid interface{}, branch, fromRef string) (*gitlab.Branch, error)
 
// DeleteBranch delete a branch for the specified project.
// The pid can be the project's ID or relative path such as fist/second.
// See https://docs.gitlab.com/ee/api/branches.html#delete-repository-branch for more information.
DeleteBranch(ctx context.Context, pid interface{}, branch string) error
 
// ListBranch list the branch, Get a list of repository branches from a project, sorted by name alphabetically.
// see https://docs.gitlab.com/ee/api/branches.html#list-repository-branches
ListBranch(ctx context.Context, pid interface{},
listBranchOptions *gitlab.ListBranchesOptions) (_ []*gitlab.Branch, err error)
 
// ListTag list the tag, Get a list of repository tag from a project, sorted by name alphabetically.
// see https://docs.gitlab.com/ee/api/tags.html#list-project-repository-tags for more information
ListTag(ctx context.Context, pid interface{},
listTagOptions *gitlab.ListTagsOptions) (_ []*gitlab.Tag, err error)
 
// CreateMR create a merge request from source to target with the specified title in project.
// The pid can be the project's ID or relative path such as fist/second.
// See https://docs.gitlab.com/ee/api/merge_requests.html#create-mr for more information.
CreateMR(ctx context.Context, pid interface{}, source, target, title string) (*gitlab.MergeRequest, error)
 
// ListMRs list merge requests for specified project.
// The pid should be the project's ID.
// See https://docs.gitlab.com/ee/api/merge_requests.html#list-project-merge-requests for more information.
ListMRs(ctx context.Context, pid interface{}, source, target, state string) ([]*gitlab.MergeRequest, error)
 
// AcceptMR merge a merge request for specified project.
// The pid can be the project's ID or relative path such as fist/second.
// See https://docs.gitlab.com/ee/api/merge_requests.html#accept-mr for more information.
AcceptMR(ctx context.Context, pid interface{}, mrID int,
mergeCommitMsg *string, shouldRemoveSourceBranch *bool) (*gitlab.MergeRequest, error)
 
// CloseMR merge a merge request for specified project.
// The pid can be the project's ID or relative path such as fist/second.
// See https://docs.gitlab.com/ee/api/merge_requests.html#update-mr for more information.
CloseMR(ctx context.Context, pid interface{}, mrID int) (mr *gitlab.MergeRequest, err error)
 
// WriteFiles write including create, delete, update multiple files within a specified project.
// The pid can be the project's ID or relative path such as fist/second.
// See https://docs.gitlab.com/ee/api/commits.html#create-a-commit-with-multiple-files-and-actions
// for more information.
WriteFiles(ctx context.Context, pid interface{}, branch, commitMsg string,
startBranch *string, actions []CommitAction) (*gitlab.Commit, error)
 
// GetFile get a file content for specified filepath in the specified project with the ref.
// The pid can be the project's ID or relative path such as fist/second.
// The ref can be the name of branch, tag or commit.
// See https://docs.gitlab.com/ee/api/repository_files.html#get-file-from-repository for more information.
GetFile(ctx context.Context, pid interface{}, ref, filepath string) ([]byte, error)
 
// TransferProject transfer a project with the specified pid to the new group with the gid.
// The pid can be the project's ID or relative path such as fist/second.
// The gid can be the group's ID or relative path such as first/third.
TransferProject(ctx context.Context, pid interface{}, gid interface{}) error
 
// EditNameAndPathForProject update name and path for a specified project.
// The pid can be the project's ID or relative path such as fist/second.
EditNameAndPathForProject(ctx context.Context, pid interface{}, newName, newPath *string) error
 
// Compare branches, tags or commits.
// The pid can be the project's ID or relative path such as fist/second.
// See https://docs.gitlab.com/ee/api/repositories.html#compare-branches-tags-or-commits for more information.
Compare(ctx context.Context, pid interface{}, from, to string, straight *bool) (*gitlab.Compare, error)
 
// GetRepositoryArchive gets an archive of the repository.
// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html#get-file-archive
GetRepositoryArchive(ctx context.Context, pid interface{}, sha string) ([]byte, error)
 
GetHTTPURL(ctx context.Context) string
 
GetCreatedGroup(ctx context.Context, parentID int, parentPath string,
name string, visibility string) (*gitlab.Group, error)
}
 
var _ Interface = (*helper)(nil)
 
type FileAction string
 
// The available file actions.
const (
FileCreate FileAction = "create"
FileUpdate FileAction = "update"
FileDelete FileAction = "delete"
FileMove FileAction = "move"
)
 
// CommitAction represents a single file action within a commit.
type CommitAction struct {
Action FileAction
FilePath string
Content string
PreviousPath string
}
 
func (a FileAction) toFileActionValuePtr() *gitlab.FileActionValue {
s := gitlab.FileActionValue(a)
return &s
}
 
`helper` has 26 methods (exceeds 20 allowed). Consider refactoring.
type helper struct {
client *gitlab.Client
httpURL string
}
 
// New an instance of Gitlab
func New(token, httpURL string) (Interface, error) {
client, err := gitlab.NewClient(token,
gitlab.WithBaseURL(httpURL),
gitlab.WithHTTPClient(&http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}))
if err != nil {
return nil, herrors.NewErrCreateFailed(herrors.GitlabResource, err.Error())
}
return &helper{
client: client,
httpURL: httpURL,
}, nil
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
func (h *helper) GetGroup(ctx context.Context, gid interface{}) (_ *gitlab.Group, err error) {
const op = "gitlab: get group"
defer wlog.Start(ctx, op).StopPrint()
 
group, rsp, err := h.client.Groups.GetGroup(gid, nil, gitlab.WithContext(ctx))
if err != nil {
return nil, parseError(rsp, err)
}
 
return group, nil
}
 
func (h *helper) ListGroupProjects(ctx context.Context, gid interface{},
page, perPage int) (_ []*gitlab.Project, err error) {
const op = "gitlab: list group projects"
defer wlog.Start(ctx, op).StopPrint()
 
if page < 1 {
return nil, perror.Wrap(herrors.ErrParamInvalid, "page cannot be less 1")
}
if perPage < 1 {
return nil, perror.Wrap(herrors.ErrParamInvalid, "perPage cannot be less 1")
}
 
projects, rsp, err := h.client.Groups.ListGroupProjects(gid, &gitlab.ListGroupProjectsOptions{
ListOptions: gitlab.ListOptions{
Page: page,
PerPage: perPage,
},
}, gitlab.WithContext(ctx))
if err != nil {
return nil, parseError(rsp, err)
}
 
return projects, nil
}
 
func (h *helper) CreateGroup(ctx context.Context, name, path string,
parentID *int, visibility string) (_ *gitlab.Group, err error) {
const op = "gitlab: create group"
defer wlog.Start(ctx, op).StopPrint()
 
visibilityValue := gitlab.VisibilityValue(visibility)
 
group, rsp, err := h.client.Groups.CreateGroup(&gitlab.CreateGroupOptions{
Name: &name,
Path: &path,
ParentID: parentID,
Visibility: &visibilityValue,
}, gitlab.WithContext(ctx))
 
if err != nil {
return nil, parseError(rsp, err)
}
 
return group, nil
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
func (h *helper) DeleteGroup(ctx context.Context, gid interface{}) (err error) {
const op = "gitlab: delete group"
defer wlog.Start(ctx, op).StopPrint()
 
rsp, err := h.client.Groups.DeleteGroup(gid, gitlab.WithContext(ctx))
 
return parseError(rsp, err)
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
func (h *helper) GetProject(ctx context.Context, pid interface{}) (_ *gitlab.Project, err error) {
const op = "gitlab: get project"
defer wlog.Start(ctx, op).StopPrint()
 
project, rsp, err := h.client.Projects.GetProject(pid, nil, gitlab.WithContext(ctx))
if err != nil {
return nil, parseError(rsp, err)
}
return project, nil
}
 
func (h *helper) CreateProject(ctx context.Context, name string,
groupID int, visibility string) (_ *gitlab.Project, err error) {
const op = "gitlab: create project"
defer wlog.Start(ctx, op).StopPrint()
 
visibilityValue := gitlab.VisibilityValue(visibility)
 
project, rsp, err := h.client.Projects.CreateProject(&gitlab.CreateProjectOptions{
InitializeWithReadme: func() *bool { b := true; return &b }(),
Name: &name,
Path: &name,
NamespaceID: &groupID,
Visibility: &visibilityValue,
}, gitlab.WithContext(ctx))
 
if err != nil {
return nil, parseError(rsp, err)
}
 
return project, err
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
func (h *helper) DeleteProject(ctx context.Context, pid interface{}) (err error) {
const op = "gitlab: delete project"
defer wlog.Start(ctx, op).StopPrint()
 
rsp, err := h.client.Projects.DeleteProject(pid, gitlab.WithContext(ctx))
 
return parseError(rsp, err)
}
 
Similar blocks of code found in 3 locations. Consider refactoring.
func (h *helper) GetBranch(ctx context.Context, pid interface{}, branch string) (_ *gitlab.Branch, err error) {
const op = "gitlab: get branch"
defer wlog.Start(ctx, op).StopPrint()
 
b, rsp, err := h.client.Branches.GetBranch(pid, branch, gitlab.WithContext(ctx))
if err != nil {
return nil, parseError(rsp, err)
}
 
return b, nil
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
func (h *helper) ListBranch(ctx context.Context, pid interface{},
listBranchOptions *gitlab.ListBranchesOptions) (_ []*gitlab.Branch, err error) {
const op = "gitlab: list branch"
defer wlog.Start(ctx, op).StopPrint()
 
branches, rsp, err := h.client.Branches.ListBranches(pid, listBranchOptions, nil)
if err != nil {
return nil, parseError(rsp, err)
}
return branches, nil
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
func (h *helper) ListTag(ctx context.Context, pid interface{},
listTagsOptions *gitlab.ListTagsOptions) (_ []*gitlab.Tag, err error) {
const op = "gitlab: list tag"
defer wlog.Start(ctx, op).StopPrint()
 
tags, rsp, err := h.client.Tags.ListTags(pid, listTagsOptions, nil)
if err != nil {
return nil, parseError(rsp, err)
}
return tags, nil
}
 
Similar blocks of code found in 3 locations. Consider refactoring.
func (h *helper) GetCommit(ctx context.Context, pid interface{}, commit string) (_ *gitlab.Commit, err error) {
const op = "gitlab: get commit"
defer wlog.Start(ctx, op).StopPrint()
 
c, rsp, err := h.client.Commits.GetCommit(pid, commit, gitlab.WithContext(ctx))
if err != nil {
return nil, parseError(rsp, err)
}
 
return c, nil
}
 
Similar blocks of code found in 3 locations. Consider refactoring.
func (h *helper) GetTag(ctx context.Context, pid interface{}, tag string) (_ *gitlab.Tag, err error) {
const op = "gitlab: get tag"
defer wlog.Start(ctx, op).StopPrint()
 
c, rsp, err := h.client.Tags.GetTag(pid, tag, gitlab.WithContext(ctx))
if err != nil {
return nil, parseError(rsp, err)
}
 
return c, nil
}
 
func (h *helper) CreateBranch(ctx context.Context, pid interface{},
branch, fromRef string) (_ *gitlab.Branch, err error) {
const op = "gitlab: create branch"
defer wlog.Start(ctx, op).StopPrint()
 
b, rsp, err := h.client.Branches.CreateBranch(pid, &gitlab.CreateBranchOptions{
Branch: &branch,
Ref: &fromRef,
}, gitlab.WithContext(ctx))
 
if err != nil {
return nil, parseError(rsp, err)
}
 
return b, nil
}
 
func (h *helper) DeleteBranch(ctx context.Context, pid interface{}, branch string) (err error) {
const op = "gitlab: delete branch"
defer wlog.Start(ctx, op).StopPrint()
 
rsp, err := h.client.Branches.DeleteBranch(pid, branch, gitlab.WithContext(ctx))
 
return parseError(rsp, err)
}
 
func (h *helper) CreateMR(ctx context.Context, pid interface{},
source, target, title string) (_ *gitlab.MergeRequest, err error) {
const op = "gitlab: create mr"
defer wlog.Start(ctx, op).StopPrint()
 
mr, rsp, err := h.client.MergeRequests.CreateMergeRequest(pid, &gitlab.CreateMergeRequestOptions{
Title: &title,
SourceBranch: &source,
TargetBranch: &target,
}, gitlab.WithContext(ctx))
 
if err != nil {
return nil, parseError(rsp, err)
}
 
return mr, nil
}
 
func (h *helper) ListMRs(ctx context.Context, pid interface{},
source, target, state string) (_ []*gitlab.MergeRequest, err error) {
mrs, rsp, err := h.client.MergeRequests.ListProjectMergeRequests(pid, &gitlab.ListProjectMergeRequestsOptions{
SourceBranch: &source,
TargetBranch: &target,
State: &state,
}, gitlab.WithContext(ctx))
 
if err != nil {
return nil, perror.WithMessagef(parseError(rsp, err),
"failed to list merge requests for project: %v", pid)
}
 
return mrs, nil
}
 
func (h *helper) CloseMR(ctx context.Context, pid interface{}, mrID int) (mr *gitlab.MergeRequest, err error) {
const op = "gitlab: close mr"
defer wlog.Start(ctx, op).StopPrint()
 
closeEvent := "close"
mr, resp, err := h.client.MergeRequests.UpdateMergeRequest(pid, mrID, &gitlab.UpdateMergeRequestOptions{
StateEvent: &closeEvent,
}, gitlab.WithContext(ctx))
if err == nil {
return mr, nil
}
 
err2 := parseError(resp, err)
if _, ok := perror.Cause(err2).(*herrors.HorizonErrNotFound); ok {
log.Warningf(ctx, "project %v, mr %d have been closed", pid, mrID)
return nil, nil
}
return nil, err2
}
 
Method `helper.AcceptMR` has 5 arguments (exceeds 4 allowed). Consider refactoring.
Method `helper.AcceptMR` has 5 return statements (exceeds 4 allowed).
func (h *helper) AcceptMR(ctx context.Context, pid interface{}, mrID int,
mergeCommitMsg *string, shouldRemoveSourceBranch *bool) (mr *gitlab.MergeRequest, err error) {
const op = "gitlab: accept mr"
defer wlog.Start(ctx, op).StopPrint()
 
retryFunc := func(i int) (*gitlab.MergeRequest, error) {
mr, rsp, err := h.client.MergeRequests.AcceptMergeRequest(pid, mrID, &gitlab.AcceptMergeRequestOptions{
MergeCommitMessage: mergeCommitMsg,
ShouldRemoveSourceBranch: shouldRemoveSourceBranch,
}, gitlab.WithContext(ctx))
 
if err != nil {
return nil, parseError(rsp, err)
}
return mr, nil
}
for i := 0; i < 20; i++ {
mr, err = retryFunc(i)
if err == nil {
return mr, nil
} else if perror.Cause(err) != herrors.ErrGitlabMRNotReady {
return nil, err
}
time.Sleep(time.Second)
}
return nil, err
}
 
Method `helper.WriteFiles` has 5 arguments (exceeds 4 allowed). Consider refactoring.
func (h *helper) WriteFiles(ctx context.Context, pid interface{}, branch, commitMsg string,
startBranch *string, actions []CommitAction) (_ *gitlab.Commit, err error) {
const op = "gitlab: write files"
defer wlog.Start(ctx, op).StopPrint()
 
commit, rsp, err := h.client.Commits.CreateCommit(pid, &gitlab.CreateCommitOptions{
Branch: &branch,
CommitMessage: &commitMsg,
StartBranch: startBranch,
Actions: func() []*gitlab.CommitActionOptions {
acts := make([]*gitlab.CommitActionOptions, 0)
for i := range actions {
acts = append(acts, &gitlab.CommitActionOptions{
Action: actions[i].Action.toFileActionValuePtr(),
FilePath: &actions[i].FilePath,
Content: &actions[i].Content,
PreviousPath: &actions[i].PreviousPath,
})
}
return acts
}(),
}, gitlab.WithContext(ctx))
 
if err != nil {
log.Errorf(ctx, "err: %v", err)
return nil, parseError(rsp, err)
}
 
return commit, nil
}
 
func (h *helper) GetFile(ctx context.Context, pid interface{}, ref, filepath string) (_ []byte, err error) {
const op = "gitlab: get file"
defer wlog.Start(ctx, op).StopPrint()
 
content, rsp, err := h.client.RepositoryFiles.GetRawFile(pid, filepath, &gitlab.GetRawFileOptions{
Ref: &ref,
}, gitlab.WithContext(ctx))
 
if err != nil {
return nil, parseError(rsp, err)
}
 
return content, nil
}
 
func (h *helper) TransferProject(ctx context.Context, pid interface{}, gid interface{}) (err error) {
const op = "gitlab: transfer project"
defer wlog.Start(ctx, op).StopPrint()
 
if _, rsp, err := h.client.Projects.TransferProject(pid, &gitlab.TransferProjectOptions{
Namespace: gid,
}, gitlab.WithContext(ctx)); err != nil {
return parseError(rsp, err)
}
 
return nil
}
 
func (h *helper) EditNameAndPathForProject(ctx context.Context, pid interface{}, newName, newPath *string) (err error) {
const op = "gitlab: edit name and path for project"
defer wlog.Start(ctx, op).StopPrint()
 
if _, rsp, err := h.client.Projects.EditProject(pid, &gitlab.EditProjectOptions{
Name: newName,
Path: newPath,
}, gitlab.WithContext(ctx)); err != nil {
return parseError(rsp, err)
}
 
return nil
}
 
func (h *helper) Compare(ctx context.Context, pid interface{}, from, to string,
straight *bool) (_ *gitlab.Compare, err error) {
const op = "gitlab: compare branchs"
defer wlog.Start(ctx, op).StopPrint()
 
compare, rsp, err := h.client.Repositories.Compare(pid, &gitlab.CompareOptions{
From: &from,
To: &to,
Straight: straight,
}, gitlab.WithContext(ctx))
if err != nil {
return nil, parseError(rsp, err)
}
 
return compare, nil
}
func (h *helper) GetRepositoryArchive(ctx context.Context, pid interface{}, sha string) ([]byte, error) {
const op = "gitlab: get repository archive"
defer wlog.Start(ctx, op).StopPrint()
 
format := "tar.gz"
archive, resp, err := h.client.Repositories.Archive(pid, &gitlab.ArchiveOptions{
Format: &format,
SHA: &sha,
})
 
if err != nil {
return nil, parseError(resp, err)
}
return archive, nil
}
 
// GetHTTPURL implements Interface
func (h *helper) GetHTTPURL(ctx context.Context) string {
return h.httpURL
}
 
Method `helper.GetCreatedGroup` has 5 arguments (exceeds 4 allowed). Consider refactoring.
func (h *helper) GetCreatedGroup(ctx context.Context, parentID int,
parentFullPath string, name string, visibility string) (*gitlab.Group, error) {
var group *gitlab.Group
group, err := h.GetGroup(ctx, fmt.Sprintf("%v/%v", parentFullPath, name))
if err != nil {
if _, ok := perror.Cause(err).(*herrors.HorizonErrNotFound); !ok {
return nil, err
}
return h.CreateGroup(ctx, name, name, &parentID, visibility)
}
 
return group, nil
}
 
func parseError(resp *gitlab.Response, err error) error {
if err == nil {
return nil
}
 
if resp.StatusCode == http.StatusNotFound {
return herrors.NewErrNotFound(herrors.GitlabResource, err.Error())
} else if resp.StatusCode == http.StatusNotAcceptable {
// https://docs.gitlab.com/ee/api/merge_requests.html#single-merge-request-response-notes
return perror.Wrap(herrors.ErrGitlabMRNotReady, err.Error())
}
 
return perror.Wrap(herrors.ErrGitlabInternal, err.Error())
}