Skip to content

Commit

Permalink
Merge branch 'doc3' of github.com:never112/reviewbot into doc3
Browse files Browse the repository at this point in the history
  • Loading branch information
never112 committed Sep 19, 2024
2 parents 49195b6 + fec25ac commit f0a5491
Show file tree
Hide file tree
Showing 9 changed files with 974 additions and 5 deletions.
33 changes: 31 additions & 2 deletions docs/proposal/gitlab-intergration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
> mergerequest 页面的comment 展示总结果
> 代码 discussion 中 展示详细信息,定位到异常代码行
- 尽量保持与其他平台接入方式大致一致
- 节点的选择是基于 repo/linter 粒度的
> 当然,如果后续有需要,可以做更灵活的配置,比如基于 repo 粒度,基于 org 粒度,或者基于 linter 粒度

## 整体设计

不破坏现有部署架构,请求路径仍然是 webhook ->MergeRequestEvent->LinterCommand> Report,但是 由于gitlab 与github 功能上,API提供上都有些差异,在细节上又些许区别
Expand All @@ -35,9 +34,39 @@ webhook ->MergeRequestEvent→ gitpull代码-> DiffFile → lineter command-> co

根据变动文件,执行对应的Linter,与github保持一致


报告
根据检查结果,创建MergeRequesDiscussion,传入指定行信息,检查结果信息。
根据检查结果,创建MergeRequestComments,在MergeRequest中增加对应的comment信息
### Agent
```go
type Agent struct {

GitLabClient *gitlab.Client
MergeRequestEvent *gitlab.MergeEvent
MergeRequestChangedFiles []*gitlab.MergeRequestDiff
Report report.Report
}
```
### Report
```go
type Report interface {

Repot(log *xlog.Logger, a Agent, lintResults map[string][]LinterOutput) error
}

func (l *gitlabreport) Reprot(log *xlog.Logger, a Agent, lintResults map[string][]LinterOutput)

func (l *githubreport) Reprot(log *xlog.Logger, a Agent, lintResults map[string][]LinterOutput)
```

###









Expand Down
283 changes: 283 additions & 0 deletions gitlabserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
/*
Copyright 2024 Qiniu Cloud (qiniu.com).
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 main

import (
"context"
"fmt"
"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v57/github"
"github.com/gregjones/httpcache"
"github.com/qiniu/reviewbot/config"
"github.com/qiniu/reviewbot/internal/linters"

Check warning on line 26 in gitlabserver.go

View check run for this annotation

qiniu-x / golangci-lint

gitlabserver.go#L26

could not import github.com/qiniu/reviewbot/internal/linters (-: # github.com/qiniu/reviewbot/internal/linters
"github.com/qiniu/reviewbot/internal/lintersutil"
"github.com/qiniu/reviewbot/internal/runner"
"github.com/qiniu/reviewbot/internal/storage"
"github.com/qiniu/x/log"
gitlabapi "github.com/xanzy/go-gitlab"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
gitv2 "sigs.k8s.io/prow/pkg/git/v2"
"strconv"
"time"
)

type GitlabServer struct {
gitClientFactory gitv2.ClientFactory
config config.Config

// server addr which is used to generate the log view url
// e.g. https://domain
serverAddr string

dockerRunner runner.Runner
storage storage.Storage

webhookSecret []byte

// support developer access token model
accessToken string
// support github app model
appID int64
appPrivateKey string

debug bool
}

func (g *GitlabServer) gitlabRequestHandle(w http.ResponseWriter, r *http.Request) {

now := time.Now()
eventGUID := strconv.FormatInt(now.Unix(), 12)
if len(eventGUID) > 12 {
// limit the length of eventGUID to 12
eventGUID = eventGUID[len(eventGUID)-12:]
}
ctx := context.WithValue(context.Background(), lintersutil.EventGUIDKey, eventGUID)
log := lintersutil.FromContext(ctx)

payload, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
event, err := gitlabapi.ParseHook(gitlabapi.HookEventType(r), payload)
if err != nil {
log.Errorf("parse webhook failed: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprint(w, "Event received. Have a nice day.")

switch event := event.(type) {
case *gitlabapi.MergeEvent:
go func() {
if err := g.processMergeRequestEvent(ctx, event); err != nil {
log.Errorf("process check run request event: %v", err)
}
}()

default:
log.Debugf("skipping event type %s\n", github.WebHookType(r))
}
}

func (g *GitlabServer) processMergeRequestEvent(ctx context.Context, event *gitlabapi.MergeEvent) error {
log := lintersutil.FromContext(ctx)
if event.ObjectAttributes.Action != "opened" && event.ObjectAttributes.Action != "reopened" {
log.Debugf("skipping action %s\n", event.ObjectAttributes.Action)
return nil
}

return g.handle(ctx, event)
}

func (g *GitlabServer) handle(ctx context.Context, event *gitlabapi.MergeEvent) error {
var (
num = event.ObjectAttributes.ID
org = event.Project.Namespace
repo = event.Repository.Name
orgRepo = event.Project.PathWithNamespace
pid = event.Project.ID
)
log := lintersutil.FromContext(ctx)

//installationID := event.GetInstallation().GetID()
//log.Infof("processing pull request %d, (%v/%v), installationID: %d\n", num, org, repo, installationID)
//linters.ListPullRequestsFiles(ctx, g.GitLabClient(), org, repo, num)
mergeRequestAffectedFiles, response, err := linters.ListMergeRequestsFiles(ctx, g.GitLabClient(), org, repo, num, pid)
if err != nil {
return err
}

if response.StatusCode != http.StatusOK {
log.Errorf("list files failed: %v", response)
return fmt.Errorf("list files failed: %v", response)
}
log.Infof("found %d files affected by pull request %d\n", len(mergeRequestAffectedFiles), num)

repoPath, err := prepareRepoDir(org, repo, num)
if err != nil {
return fmt.Errorf("failed to prepare repo dir: %w", err)
}

r, err := g.gitClientFactory.ClientForWithRepoOpts(org, repo, gitv2.RepoOpts{
CopyTo: repoPath,
})
if err != nil {
log.Errorf("failed to create git client: %v", err)
return err
}

if err := r.CheckoutPullRequest(num); err != nil {
log.Errorf("failed to checkout pull request %d: %v", num, err)
return err
}

gitModulesFile := path.Join(r.Directory(), ".gitmodules")
_, err = os.Stat(gitModulesFile)
if err == nil {
log.Info("git pull submodule in progress")
cmd := exec.Command("git", "submodule", "update", "--init", "--recursive")
cmd.Dir = r.Directory()
out, err := cmd.CombinedOutput()
if err != nil {
log.Errorf("error when git pull submodule, marked and continue, details :%v ", err)
}
log.Infof("git pull submodule output: %s ", out)
} else {
log.Infof("no .gitmodules file in repo %s", repo)
}

defer func() {
if g.debug {
return // do not remove the repository in debug mode
}
err := r.Clean()
if err != nil {
log.Errorf("failed to remove the repository , err : %v", err)
}
}()

for name, fn := range linters.TotalPullRequestHandlers() {
linterConfig := g.config.Get(org, repo, name)

// skip linter if it is disabled
if linterConfig.Enable != nil && !*linterConfig.Enable {
continue
}

// set workdir
if linterConfig.WorkDir != "" {
linterConfig.WorkDir = r.Directory() + "/" + linterConfig.WorkDir
} else {
linterConfig.WorkDir = r.Directory()
}

log.Infof("[%s] config on repo %v: %+v", name, orgRepo, linterConfig)

agent := linters.Agent{
GitLabClient: g.GitLabClient(),
LinterConfig: linterConfig,
GitClient: g.gitClientFactory,
MergeRequestEvent: event,
MergeRequestChangedFiles: mergeRequestAffectedFiles,
RepoDir: r.Directory(),
Context: ctx,
ID: lintersutil.GetEventGUID(ctx),
}

if !linters.LinterRelated(name, agent) {
log.Infof("[%s] linter is not related to the PR, skipping", name)
continue
}

r := runner.NewLocalRunner()
if linterConfig.DockerAsRunner.Image != "" {
r = g.dockerRunner
}
agent.Runner = r
agent.Storage = g.storage
agent.GenLogKey = func() string {
return fmt.Sprintf("%s/%s/%s", agent.LinterConfig.Name, agent.PullRequestEvent.Repo.GetFullName(), agent.ID)
}
agent.GenLogViewUrl = func() string {
// if serverAddr is not provided, return empty string
if g.serverAddr == "" {
return ""
}
return g.serverAddr + "/view/" + agent.GenLogKey()
}

if err := fn(ctx, agent); err != nil {
log.Errorf("failed to run linter: %v", err)
// continue to run other linters
continue
}
}

return nil
}

func (s *GitlabServer) githubAppClient(installationID int64) *github.Client {
tr, err := ghinstallation.NewKeyFromFile(httpcache.NewMemoryCacheTransport(), s.appID, installationID, s.appPrivateKey)
if err != nil {
log.Fatalf("failed to create github app transport: %v", err)
}
return github.NewClient(&http.Client{Transport: tr})
}

func (s *GitlabServer) githubAccessTokenClient() *github.Client {
gc := github.NewClient(httpcache.NewMemoryCacheTransport().Client())
gc.WithAuthToken(s.accessToken)
return gc
}

// GithubClient returns a github client
func (s *GitlabServer) GitLabClient() *gitlabapi.Client {
git, err := gitlabapi.NewClient(s.accessToken)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
return git
}

//func prepareRepoDir(org, repo string, num int) (string, error) {
// var parentDir string
// if runtime.GOOS == "darwin" {
// homeDir, err := os.UserHomeDir()
// if err != nil {
// return "", fmt.Errorf("failed to get user home dir: %w", err)
// }
// parentDir = filepath.Join(homeDir, "reviewbot-code")
// } else {
// parentDir = filepath.Join("/tmp", "reviewbot-code")
// }
//
// if err := os.MkdirAll(parentDir, 0o755); err != nil {
// return "", fmt.Errorf("failed to create parent dir: %w", err)
// }
//
// repoPath, err := os.MkdirTemp(parentDir, fmt.Sprintf("%s-%s-%d-", org, repo, num))
// if err != nil {
// return "", fmt.Errorf("failed to create temp dir: %w", err)
// }
//
// return repoPath, nil
//}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
)

require (
github.com/xanzy/go-gitlab v0.109.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xanzy/go-gitlab v0.6.1/go.mod h1:CRKHkvFWNU6C3AEfqLWjnCNnAs4nj8Zk95rX2S3X6Mw=
github.com/xanzy/go-gitlab v0.109.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
Expand Down
Loading

0 comments on commit f0a5491

Please sign in to comment.