Skip to content

Commit

Permalink
gitlab activity callback (#3140)
Browse files Browse the repository at this point in the history
  • Loading branch information
makkalot authored Aug 11, 2021
1 parent 4145e9c commit e959221
Show file tree
Hide file tree
Showing 12 changed files with 839 additions and 4 deletions.
4 changes: 4 additions & 0 deletions cla-backend-go/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"strconv"
"strings"

gitlab_activity "github.com/communitybridge/easycla/cla-backend-go/v2/gitlab-activity"

"github.com/go-openapi/strfmt"

"github.com/communitybridge/easycla/cla-backend-go/v2/gitlab_organizations"
Expand Down Expand Up @@ -302,6 +304,7 @@ func server(localMode bool) http.Handler {
v2MetricsService := metrics.NewService(metricsRepo, v1ProjectClaGroupRepo)
githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, repositoriesRepo, v1ProjectClaGroupRepo)
gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v1ProjectClaGroupRepo)
gitlabActivityService := gitlab_activity.NewService(gitlabOrganizationRepo, repositoriesRepo, usersRepo, signaturesRepo, v1ProjectClaGroupRepo, v1CompanyRepo, signaturesRepo)
v2GithubOrganizationsService := v2GithubOrganizations.NewService(githubOrganizationsRepo, repositoriesRepo, v1ProjectClaGroupRepo, githubOrganizationsService)
autoEnableService := dynamo_events.NewAutoEnableService(v1RepositoriesService, repositoriesRepo, githubOrganizationsRepo, v1ProjectClaGroupRepo, v1ProjectService)
v2GithubActivityService := v2GithubActivity.NewService(repositoriesRepo, githubOrganizationsRepo, eventsService, autoEnableService, emailService)
Expand Down Expand Up @@ -342,6 +345,7 @@ func server(localMode bool) http.Handler {
github_organizations.Configure(api, githubOrganizationsService, eventsService)
v2GithubOrganizations.Configure(v2API, v2GithubOrganizationsService, eventsService)
gitlab_organizations.Configure(v2API, gitlabOrganizationsService, eventsService)
gitlab_activity.Configure(v2API, gitlabActivityService, eventsService)
repositories.Configure(api, v1RepositoriesService, eventsService)
v2Repositories.Configure(v2API, v2RepositoriesService, eventsService)
gerrits.Configure(api, gerritService, v1ProjectService, eventsService)
Expand Down
162 changes: 162 additions & 0 deletions cla-backend-go/gitlab/mr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright The Linux Foundation and each contributor to CommunityBridge.
// SPDX-License-Identifier: MIT

package gitlab

import (
"fmt"
"strings"

log "github.com/communitybridge/easycla/cla-backend-go/logging"
"github.com/xanzy/go-gitlab"
)

// FetchMrInfo is responsible for fetching the MR info for given project
func FetchMrInfo(client *gitlab.Client, projectID int, mergeID int) (*gitlab.MergeRequest, error) {
m, _, err := client.MergeRequests.GetMergeRequest(projectID, mergeID, &gitlab.GetMergeRequestsOptions{})
if err != nil {
return nil, fmt.Errorf("fetching merge request : %d for project : %v failed : %v", mergeID, projectID, err)
}

return m, nil
}

// FetchMrParticipants is responsible to get unique mr participants
func FetchMrParticipants(client *gitlab.Client, projectID int, mergeID int, unique bool) ([]*gitlab.User, error) {
commits, _, err := client.MergeRequests.GetMergeRequestCommits(projectID, mergeID, &gitlab.GetMergeRequestCommitsOptions{})
if err != nil {
return nil, fmt.Errorf("fetching gitlab participants for project : %d and merge id : %d, failed : %v", projectID, mergeID, err)
}

if len(commits) == 0 {
return nil, nil
}

var results []*gitlab.User
uniqueUsers := map[int]bool{}

for _, commit := range commits {
authorEmail := commit.AuthorEmail
authorName := commit.AuthorName

log.Debugf("user email found : %s, user name : %s, searching in gitlab ...", authorEmail, authorName)

var user *gitlab.User
if authorName != "" {
user, err = searchForUser(client, authorEmail)
if err != nil {
return nil, fmt.Errorf("searching for author email : %s, failed : %v", authorEmail, err)
}
}

if authorName != "" && user == nil {
user, err = searchForUser(client, authorName)
if err != nil {
return nil, fmt.Errorf("searching for author name : %s, failed : %v", authorName, err)
}
}

if user == nil {
return nil, fmt.Errorf("no users found for commit author email : %s, name : %s", authorEmail, authorName)
}

if uniqueUsers[user.ID] {
continue
}

results = append(results, user)
uniqueUsers[user.ID] = true
}

return results, nil
}

// SetCommitStatus is responsible for setting the MR status for commit sha
func SetCommitStatus(client *gitlab.Client, projectID int, commitSha string, state gitlab.BuildStateValue, message string) error {
options := &gitlab.SetCommitStatusOptions{
State: state,
Name: gitlab.String("easyCLA Bot"),
Description: gitlab.String(message),
}

if state == gitlab.Failed {
options.TargetURL = gitlab.String("http://localhost:8080/gitlab/sign")
}

_, _, err := client.Commits.SetCommitStatus(projectID, commitSha, options)
if err != nil {
return fmt.Errorf("setting commit status for the sha : %s and project id : %d failed : %v", commitSha, projectID, err)
}

return nil
}

// SetMrComment is responsible for setting the comment body for project and merge id
func SetMrComment(client *gitlab.Client, projectID int, mergeID int, state gitlab.BuildStateValue, message string) error {
covered := `<a href="http://localhost:8080">
<img src="https://s3.amazonaws.com/cla-project-logo-dev/cla-signed.svg" alt="covered" align="left" height="28" width="328" ></a><br/>`
failed := `<a href="http://localhost:8080">
<img src="https://s3.amazonaws.com/cla-project-logo-dev/cla-not-signed.svg" alt="covered" align="left" height="28" width="328" ></a><br/>`

var body string
if state == gitlab.Failed {
body = failed
} else {
body = covered
}

notes, _, err := client.Notes.ListMergeRequestNotes(projectID, mergeID, &gitlab.ListMergeRequestNotesOptions{})
if err != nil {
return fmt.Errorf("fetching comments for project id : %d and merge id : %d : failed %v", projectID, mergeID, err)
}

var previousNote *gitlab.Note
if len(notes) > 0 {
for _, n := range notes {
if strings.Contains(n.Body, "cla-signed.svg") || strings.Contains(n.Body, "cla-not-signed.svg") {
previousNote = n
break
}
}
}

if previousNote == nil {
log.Debugf("no previous comments found for project id : %d and merge id : %d", projectID, mergeID)
_, _, err = client.Notes.CreateMergeRequestNote(projectID, mergeID, &gitlab.CreateMergeRequestNoteOptions{
Body: &body,
})
if err != nil {
return fmt.Errorf("creating comment for project id : %d and merge id : %d : failed %v", projectID, mergeID, err)
}
} else {
log.Debugf("previous comments found for project id : %d and merge id : %d", projectID, mergeID)
_, _, err = client.Notes.UpdateMergeRequestNote(projectID, mergeID, previousNote.ID, &gitlab.UpdateMergeRequestNoteOptions{
Body: &body,
})
if err != nil {
return fmt.Errorf("updtae comment for project id : %d and merge id : %d : failed %v", projectID, mergeID, err)
}
}

return nil
}

func searchForUser(client *gitlab.Client, search string) (*gitlab.User, error) {
users, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{
Search: gitlab.String(search),
})

if err != nil {
return nil, fmt.Errorf("searching for user string : %s failed : %v", search, err)
}

if len(users) == 0 {
return nil, nil
}

if len(users) > 1 {
return nil, fmt.Errorf("found more than one gitlab user for search string : %s", search)
}

return users[0], nil
}
52 changes: 52 additions & 0 deletions cla-backend-go/gitlab/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ package gitlab
import (
"fmt"

log "github.com/communitybridge/easycla/cla-backend-go/logging"

"github.com/xanzy/go-gitlab"
)

// UserGroup represents gitlab group
type UserGroup struct {
Name string
FullPath string
}

// GetGroupByName gets a gitlab Group by the given name
func GetGroupByName(client *gitlab.Client, name string) (*gitlab.Group, error) {
groups, _, err := client.Groups.ListGroups(&gitlab.ListGroupsOptions{})
Expand All @@ -24,3 +32,47 @@ func GetGroupByName(client *gitlab.Client, name string) (*gitlab.Group, error) {

return nil, nil
}

// ListUserProjectGroups fetches the unique groups of a gitlab users groups,
// note: it doesn't list the projects/groups the user is member of ..., it's very limited
func ListUserProjectGroups(client *gitlab.Client, userID int) ([]*UserGroup, error) {
listOptions := &gitlab.ListProjectsOptions{
ListOptions: gitlab.ListOptions{
PerPage: 100,
}}

userGroupsMap := map[string]*UserGroup{}
for {
log.Debugf("fetching projects for user id : %d with options : %v", userID, listOptions.ListOptions)
projects, resp, err := client.Projects.ListUserProjects(userID, listOptions)
if err != nil {
return nil, fmt.Errorf("listing user : %d projects failed : %v", userID, err)
}
log.Printf("fetched %d projects for the user ", len(projects))

if len(projects) == 0 {
break
}

for _, p := range projects {
log.Debugf("checking following project : %s", p.PathWithNamespace)
log.Debugf("fetched following namespace : %+v", p.Namespace)
userGroupsMap[p.Namespace.FullPath] = &UserGroup{
Name: p.Namespace.Name,
FullPath: p.Namespace.FullPath,
}
}

if listOptions.Page >= resp.NextPage {
break
}
listOptions.Page = resp.NextPage
}

var userGroups []*UserGroup
for _, v := range userGroupsMap {
userGroups = append(userGroups, v)
}

return userGroups, nil
}
4 changes: 4 additions & 0 deletions cla-backend-go/gitlab/repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright The Linux Foundation and each contributor to CommunityBridge.
// SPDX-License-Identifier: MIT

package gitlab
4 changes: 4 additions & 0 deletions cla-backend-go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
Expand Down Expand Up @@ -652,11 +653,13 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg=
github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0 h1:wBza4Dlm/NCQF572oSGNZ69flNFxlwIHjtwS6oy3Rvw=
github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
Expand Down Expand Up @@ -1260,6 +1263,7 @@ honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
Expand Down
5 changes: 5 additions & 0 deletions cla-backend-go/signatures/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -1516,6 +1516,11 @@ func (repo repository) GetProjectCompanyEmployeeSignatures(ctx context.Context,
filter = addAndCondition(filter, expression.Name(SignatureUserGitHubUsername).Equal(expression.Value(criteria.GitHubUsername)), &filterAdded)
}

if criteria != nil && criteria.GitHubUsername != "" {
log.WithFields(f).Debugf("adding Gitlabusername criteria filter for :%s ", criteria.GitlabUsername)
filter = addAndCondition(filter, expression.Name(SignatureUserGitlabUsername).Equal(expression.Value(criteria.GitlabUsername)), &filterAdded)
}

if criteria != nil && criteria.UserEmail != "" {
log.WithFields(f).Debugf("adding useremail criteria filter for : %s ", criteria.UserEmail)
filter = addAndCondition(filter, expression.Name("user_email").Equal(expression.Value(criteria.UserEmail)), &filterAdded)
Expand Down
38 changes: 37 additions & 1 deletion cla-backend-go/swagger/cla.v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3820,7 +3820,7 @@ paths:
get:
summary: The endpoint is called after user authorizes EasyCLA bot
description: The endpoint is responsible for storing the access token for the user and registering the webhooks is autoenable is on
security: []
security: [ ]
operationId: gitlabOauthCallback
parameters:
- name: code
Expand Down Expand Up @@ -3853,6 +3853,35 @@ paths:
tags:
- gitlab-activity

/gitlab/activity:
post:
summary: Gitlab Activity Callback Handler
description: Gitlab Activity Callback Handler reacts to Gitlab events emmited.
security: [ ]
operationId: gitlabActivity
parameters:
- $ref: "#/parameters/x-request-id"
- $ref: "#/parameters/x-github-event"
- $ref: "#/parameters/x-hub-signature"
- name: gitlabActivityInput
in: body
schema:
$ref: '#/definitions/gitlab-activity-input'
responses:
'200':
description: 'Success'
'400':
$ref: '#/responses/invalid-request'
'401':
$ref: '#/responses/unauthorized'
'403':
$ref: '#/responses/forbidden'
'500':
$ref: '#/responses/internal-server-error'
tags:
- gitlab-activity


responses:
unauthorized:
description: Unauthorized
Expand Down Expand Up @@ -4204,6 +4233,13 @@ definitions:
type: string
additionalProperties: true

gitlab-activity-input:
type: object
properties:
object_kind:
type: string
additionalProperties: true

github-repository-input:
type: object
required:
Expand Down
Loading

0 comments on commit e959221

Please sign in to comment.