Skip to content

Commit

Permalink
bitbucket repors logic implementation checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
taraspos committed Oct 1, 2020
1 parent f71f0c4 commit 47878f1
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 8 deletions.
9 changes: 9 additions & 0 deletions cienv/bitbucket_pipelines.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cienv

import "os"

// IsInBitbucketPipeline returns true if reviewdog is running in Bitbucket Pipelines.
func IsInBitbucketPipeline() bool {
// https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/#Default-variables
return os.Getenv("BITBUCKET_PIPELINE_UUID") != ""
}
4 changes: 4 additions & 0 deletions cienv/cienv.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func GetBuildInfo() (prInfo *BuildInfo, isPR bool, err error) {
owner, repo := getOwnerAndRepoFromSlug([]string{
"TRAVIS_REPO_SLUG",
"DRONE_REPO", // drone<=0.4
"BITBUCKET_REPO_FULL_NAME",
})
if owner == "" {
owner = getOneEnvValue([]string{
Expand Down Expand Up @@ -74,6 +75,7 @@ func GetBuildInfo() (prInfo *BuildInfo, isPR bool, err error) {
"CIRCLE_SHA1",
"DRONE_COMMIT",
"CI_COMMIT_SHA", // GitLab CI
"BITBUCKET_COMMIT",
})
if sha == "" {
return nil, false, errors.New("cannot get commit SHA from environment variable. Set CI_COMMIT?")
Expand All @@ -84,6 +86,7 @@ func GetBuildInfo() (prInfo *BuildInfo, isPR bool, err error) {
"TRAVIS_PULL_REQUEST_BRANCH",
"CIRCLE_BRANCH",
"DRONE_COMMIT_BRANCH",
"BITBUCKET_BRANCH",
})

pr := getPullRequestNum()
Expand Down Expand Up @@ -134,6 +137,7 @@ func getPullRequestNum() int {
"DRONE_PULL_REQUEST",
// GitLab CI MergeTrains
"CI_MERGE_REQUEST_IID",
"BITBUCKET_PR_ID",
}
// regexp.MustCompile() in func intentionally because this func is called
// once for one run.
Expand Down
35 changes: 34 additions & 1 deletion cmd/reviewdog/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
"github.com/reviewdog/reviewdog/filter"
"github.com/reviewdog/reviewdog/parser"
"github.com/reviewdog/reviewdog/project"
bbservice "github.com/reviewdog/reviewdog/service/bitbucket"
bitbucket "github.com/reviewdog/reviewdog/service/bitbucket/openapi"
gerritservice "github.com/reviewdog/reviewdog/service/gerrit"
githubservice "github.com/reviewdog/reviewdog/service/github"
"github.com/reviewdog/reviewdog/service/github/githubutils"
Expand Down Expand Up @@ -152,13 +154,24 @@ const (
$ export GERRIT_REVISION_ID=ed318bf9a3c
$ export GERRIT_BRANCH=master
$ export GERRIT_ADDRESS=http://localhost:8080
"bitbucket-code-report"
Create Bitbucket Code Report via Code Insights
(https://confluence.atlassian.com/display/BITBUCKET/Code+insights).
You can set custom report name with:
$ export BITBUCKET_REPORT_NAME="Linter Report",
otherwise report will be called "Reviewdog Report"
If running as part of Bitbucket Pipelines no additional configurations is needed.
Running outside of Bitbucket Pipelines or Bitbucket server not supported yet.
For GitHub Enterprise and self hosted GitLab, set
REVIEWDOG_INSECURE_SKIP_VERIFY to skip verifying SSL (please use this at your own risk)
$ export REVIEWDOG_INSECURE_SKIP_VERIFY=true
For non-local reporters, reviewdog automatically get necessary data from
environment variable in CI service (GitHub Actions, Travis CI, Circle CI, drone.io, GitLab CI).
environment variable in CI service (GitHub Actions, Travis CI, Circle CI, drone.io, GitLab CI, Bitbucket Pipelines).
You can set necessary data with following environment variable manually if
you want (e.g. run reviewdog in Jenkins).
Expand Down Expand Up @@ -329,6 +342,16 @@ github-pr-check reporter as a fallback.
return err
}
ds = d
case "bitbucket-code-report":
build, client, err := bitbucketBuildWithClient()
if err != nil {
return err
}

reportName := os.Getenv("BITBUCKET_REPORT_NAME")

cs = bbservice.NewReportAnnotator(client, reportName,
build.Owner, build.Repo, build.SHA)
case "local":
if opt.diffCmd == "" && opt.filterMode == filter.ModeNoFilter {
ds = &reviewdog.EmptyDiff{}
Expand Down Expand Up @@ -561,6 +584,16 @@ func gerritBuildWithClient() (*cienv.BuildInfo, *gerrit.Client, error) {
return buildInfo, client, nil
}

func bitbucketBuildWithClient() (*cienv.BuildInfo, *bitbucket.APIClient, error) {
build, _, err := cienv.GetBuildInfo()
if err != nil {
return nil, nil, err
}

client := bbservice.NewAPIClient(cienv.IsInBitbucketPipeline())
return build, client, nil
}

func fetchMergeRequestIDFromCommit(cli *gitlab.Client, projectID, sha string) (id int, err error) {
// https://docs.gitlab.com/ce/api/merge_requests.html#list-project-merge-requests
opt := &gitlab.ListProjectMergeRequestsOptions{
Expand Down
203 changes: 203 additions & 0 deletions service/bitbucket/annotator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package bitbucket

import (
"context"
"errors"
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"sync"

"github.com/reviewdog/reviewdog"
"github.com/reviewdog/reviewdog/proto/rdf"
"github.com/reviewdog/reviewdog/service/bitbucket/openapi"
"github.com/reviewdog/reviewdog/service/commentutil"
)

var _ reviewdog.CommentService = &ReportAnnotator{}

const (
logoURL = "https://raw.githubusercontent.com/haya14busa/i/d598ed7dc49fefb0018e422e4c43e5ab8f207a6b/reviewdog/reviewdog.logo.png"
reporter = "reviewdog"

annotationTypeCodeSmell = "CODE_SMELL"
annotationTypeVulnerability = "VULNERABILITY"
annotationTypeBug = "BUG"

annotationResultPassed = "PASSED"
annotationResultFailed = "FAILED"
annotationResultSkipped = "SKIPPED"
annotationResultIgnored = "IGNORED"
annotationResultPending = "PENDING"

annotationSeverityHigh = "HIGH"
annotationSeverityMedium = "MEDIUM"
annotationSeverityLow = "LOW"
annotationSeverityCritical = "CRITICAL"

reportTypeSecurity = "SECURITY"
reportTypeCoverage = "COVERAGE"
reportTypeTest = "TEST"
reportTypeBug = "BUG"

reportDataTypeBool = "BOOLEAN"
reportDataTypeDate = "DATE"
reportDataTypeDuration = "DURATION"
reportDataTypeLink = "LINK"
reportDataTypeNumber = "NUMBER"
reportDataTypePercentage = "PERCENTAGE"
reportDataTypeText = "TEXT"

reportResultPassed = "PASSED"
reportResultFailed = "FAILED"
reportResultPending = "PENDING"
)

var severityMap = map[rdf.Severity]string{
rdf.Severity_INFO: annotationSeverityLow,
rdf.Severity_WARNING: annotationSeverityMedium,
rdf.Severity_ERROR: annotationSeverityHigh,
}

// ReportAnnotator is a comment service for Bitbucket Code Insights reports.
//
// API:
// https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Bworkspace%7D/%7Brepo_slug%7D/commit/%7Bcommit%7D/reports/%7BreportId%7D/annotations#post
// POST /2.0/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations
type ReportAnnotator struct {
cli *openapi.APIClient
sha string
owner, repo string
reportTitle string

muComments sync.Mutex
postComments []*reviewdog.Comment

// postedcs commentutil.PostedComments

// wd is working directory relative to root of repository.
wd string
reportID string
}

// NewReportAnnotator creates new Bitbucket Report Annotator
func NewReportAnnotator(cli *openapi.APIClient, reportTitle, owner, repo, sha string) *ReportAnnotator {
if reportTitle == "" {
reportTitle = "Reviewdog Report"
}

return &ReportAnnotator{
cli: cli,
reportTitle: reportTitle,
sha: sha,
owner: owner,
repo: repo,
reportID: reporter + "-" + reportTitle,
}
}

// Post accepts a comment and holds it. Flush method actually posts comments to
// Bitbucket in batch.
func (r *ReportAnnotator) Post(_ context.Context, c *reviewdog.Comment) error {
c.Result.Diagnostic.GetLocation().Path = filepath.ToSlash(
filepath.Join(r.wd, c.Result.Diagnostic.GetLocation().GetPath()))
r.muComments.Lock()
defer r.muComments.Unlock()
r.postComments = append(r.postComments, c)
return nil
}

// Flush posts comments which has not been posted yet.
func (r *ReportAnnotator) Flush(ctx context.Context) error {
r.muComments.Lock()
defer r.muComments.Unlock()

var annotations []openapi.ReportAnnotation

issuesCount := map[rdf.Severity]int{}

for _, c := range r.postComments {
issuesCount[c.Result.Diagnostic.GetSeverity()]++
annotations = append(annotations, annotationFromReviewDogComment(*c))
}

if len(annotations) == 0 {
return r.createOrUpdateReport(ctx, reportResultPassed)
}

reportStatus := reportResultPending
if issuesCount[rdf.Severity_ERROR] > 0 {
reportStatus = reportResultFailed
}

if err := r.createOrUpdateReport(ctx, reportStatus); err != nil {
return err
}

_, resp, err := r.cli.ReportsApi.BulkCreateOrUpdateAnnotations(
ctx, r.owner, r.repo, r.sha, r.reportID,
).Body(annotations).Execute()

if err != nil {
return err
}

return checkHTTPResp(resp, http.StatusOK)
}

func annotationFromReviewDogComment(c reviewdog.Comment) openapi.ReportAnnotation {
a := openapi.NewReportAnnotation()
switch c.ToolName {
// TODO: different type of annotation based on tool?
default:
a.SetAnnotationType(annotationTypeCodeSmell)
}

a.SetSummary(c.Result.Diagnostic.GetMessage())
a.SetDetails(commentutil.MarkdownComment(&c))
a.SetLine(c.Result.Diagnostic.GetLocation().GetRange().GetStart().GetLine())
a.SetPath(c.Result.Diagnostic.GetLocation().GetPath())
if v, ok := severityMap[c.Result.Diagnostic.GetSeverity()]; ok {
a.SetSeverity(v)
}
a.SetLink(c.Result.Diagnostic.GetCode().GetUrl())

return *a
}

func (r *ReportAnnotator) createOrUpdateReport(ctx context.Context, status string) error {
var report = openapi.NewReport()
report.SetTitle(r.reportTitle)
// TODO: different report types?
report.SetReportType(reportTypeBug)
report.SetReporter(reporter)
report.SetLogoUrl(logoURL)
report.SetResult(status)

_, resp, err := r.cli.ReportsApi.CreateOrUpdateReport(
ctx, r.owner, r.repo, r.sha, r.reportID,
).Body(*report).Execute()

if err != nil {
return err
}

return checkHTTPResp(resp, http.StatusOK)
}

func checkHTTPResp(resp *http.Response, expectedCode int) error {
if resp.StatusCode != expectedCode {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
msg := fmt.Sprintf("Received unexpected %d code from Bitbucket API", resp.StatusCode)
if len(body) > 0 {
msg += " with message:\n" + string(body)
}
return errors.New(msg)
}

return nil
}
10 changes: 3 additions & 7 deletions service/bitbucket/bitbucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package bitbucket
import (
"net/http"
"net/url"
"os"
"time"

"github.com/reviewdog/reviewdog/service/bitbucket/openapi"
Expand All @@ -30,19 +29,16 @@ using HTTP API endpoint and AuthProxy`,
URL: "https://api.bitbucket.org/2.0",
Description: `HTTPS API endpoint`,
}

client *openapi.APIClient
bitbucketPipelineUUID = os.Getenv("BITBUCKET_PIPELINE_UUID")
)

func init() {
func NewAPIClient(isInPipeline bool) *openapi.APIClient {
proxyURL, _ := url.Parse(pipelineProxyURL)
config := openapi.NewConfiguration()
config.HTTPClient = &http.Client{
Timeout: httpTimeout,
}

if bitbucketPipelineUUID != "" {
if isInPipeline {
// if we are on the Bitbucket Pipeline, use HTTP endpoint
// and proxy
config.Servers = openapi.ServerConfigurations{httpServer}
Expand All @@ -54,5 +50,5 @@ func init() {
config.Servers = openapi.ServerConfigurations{httpsServer}
}

client = openapi.NewAPIClient(config)
return openapi.NewAPIClient(config)
}

0 comments on commit 47878f1

Please sign in to comment.