Skip to content

Commit

Permalink
Merge pull request #7 from matthyx/bitbucket
Browse files Browse the repository at this point in the history
add support for parsing bitbucket urls
  • Loading branch information
David Wertenteil authored Jan 9, 2023
2 parents f6af835 + 3731215 commit a13758b
Show file tree
Hide file tree
Showing 13 changed files with 560 additions and 5 deletions.
86 changes: 86 additions & 0 deletions apis/bitbucketapi/apis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package bitbucketapi

import (
"encoding/json"
"fmt"
"net/http"
"net/url"

"github.com/kubescape/go-git-url/apis"
)

const (
DEFAULT_HOST string = "bitbucket.org"
)

type IBitBucketAPI interface {
GetRepoTree(owner, repo, branch string, headers *Headers) (*Tree, error)
GetDefaultBranchName(owner, repo string, headers *Headers) (string, error)
GetLatestCommit(owner, repo, branch string, headers *Headers) (*Commit, error)
}

type BitBucketAPI struct {
httpClient *http.Client
}

func NewBitBucketAPI() *BitBucketAPI { return &BitBucketAPI{httpClient: &http.Client{}} }

func (gl *BitBucketAPI) GetRepoTree(owner, repo, branch string, headers *Headers) (*Tree, error) {
//TODO implement me
return nil, fmt.Errorf("GetRepoTree is not supported")
}

func (gl *BitBucketAPI) GetDefaultBranchName(owner, repo string, headers *Headers) (string, error) {
body, err := apis.HttpGet(gl.httpClient, APIBranchingModel(owner, repo), headers.ToMap())
if err != nil {
return "", err
}

var data bitbucketBranchingModel
if err = json.Unmarshal([]byte(body), &data); err != nil {
return "", err
}

return data.Development.Name, nil
}

func (gl *BitBucketAPI) GetLatestCommit(owner, repo, branch string, headers *Headers) (*Commit, error) {
body, err := apis.HttpGet(gl.httpClient, APILastCommitsOfBranch(owner, repo, branch), headers.ToMap())
if err != nil {
return nil, err
}

var data Commits
err = json.Unmarshal([]byte(body), &data)
if err != nil {
return nil, err
}
return &data.Values[0], nil
}

// APIBranchingModel
// API Ref: https://developer.atlassian.com/cloud/bitbucket/rest/api-group-branching-model/#api-group-branching-model
// Example: https://bitbucket.org/!api/2.0/repositories/matthyx/ks-testing-public/branching-model
func APIBranchingModel(owner, repo string) string {
p, _ := url.JoinPath("!api/2.0/repositories", owner, repo, "branching-model")
u := url.URL{
Scheme: "https",
Host: DEFAULT_HOST,
Path: p,
}
return u.String()
}

// APILastCommitsOfBranch
// API Ref: https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-commits-get
// Example: https://bitbucket.org/!api/2.0/repositories/matthyx/ks-testing-public/commits/?include=master
func APILastCommitsOfBranch(owner, repo, branch string) string {
p, _ := url.JoinPath("!api/2.0/repositories", owner, repo, "commits")
u := url.URL{
Scheme: "https",
Host: DEFAULT_HOST,
Path: p,
RawQuery: fmt.Sprintf("include=%s", branch),
}
return u.String()
}
34 changes: 34 additions & 0 deletions apis/bitbucketapi/datastructures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package bitbucketapi

import "time"

type ObjectType string

type Tree struct{}

type bitbucketBranchingModel struct {
Development struct {
Name string `json:"name"`
} `json:"development"`
}

type Headers struct {
Token string
}

type Commits struct {
Values []Commit `json:"values"`
Pagelen int `json:"pagelen"`
Next string `json:"next"`
}

type Commit struct {
Type string `json:"type"`
Hash string `json:"hash"`
Date time.Time `json:"date"`
Author struct {
Type string `json:"type"`
Raw string `json:"raw"`
} `json:"author"`
Message string `json:"message"`
}
12 changes: 12 additions & 0 deletions apis/bitbucketapi/methods.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package bitbucketapi

import "fmt"

// ToMap convert headers to map[string]string
func (h *Headers) ToMap() map[string]string {
m := make(map[string]string)
if h.Token != "" {
m["Authorization"] = fmt.Sprintf("Bearer %s", h.Token)
}
return m
}
9 changes: 5 additions & 4 deletions apis/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import "errors"
type ProviderType string

const (
ProviderGitHub ProviderType = "github"
ProviderAzure ProviderType = "azure"
ProviderGitLab ProviderType = "gitlab"
ProviderGitHub ProviderType = "github"
ProviderAzure ProviderType = "azure"
ProviderBitBucket ProviderType = "bitbucket"
ProviderGitLab ProviderType = "gitlab"
)

func (pt ProviderType) IsSupported() error {
switch pt {
case ProviderGitHub, ProviderAzure, ProviderGitLab:
case ProviderGitHub, ProviderAzure, ProviderBitBucket, ProviderGitLab:
return nil
}
return errors.New("unsupported provider")
Expand Down
58 changes: 58 additions & 0 deletions bitbucketparser/v1/commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package v1

import (
"fmt"
"regexp"
"strings"

"github.com/kubescape/go-git-url/apis"
"github.com/kubescape/go-git-url/apis/bitbucketapi"
)

var rawUserRe = regexp.MustCompile("([^<]*)?(<(.+)>)?")

func (gl *BitBucketURL) GetLatestCommit() (*apis.Commit, error) {
if gl.GetHostName() == "" || gl.GetOwnerName() == "" || gl.GetRepoName() == "" {
return nil, fmt.Errorf("missing host/owner/repo")
}
if gl.GetBranchName() == "" {
if err := gl.SetDefaultBranchName(); err != nil {
return nil, fmt.Errorf("failed to get default branch. reason: %s", err.Error())
}
}

c, err := gl.bitBucketAPI.GetLatestCommit(gl.GetOwnerName(), gl.GetRepoName(), gl.GetBranchName(), gl.headers())
if err != nil {
return nil, fmt.Errorf("failed to get latest commit. reason: %s", err.Error())
}

return bitBucketAPICommitToCommit(c), nil
}

func bitBucketAPICommitToCommit(c *bitbucketapi.Commit) *apis.Commit {
name, email := parseRawUser(c.Author.Raw)
latestCommit := &apis.Commit{
SHA: c.Hash,
Author: apis.Committer{
Name: name,
Email: email,
Date: c.Date,
},
Committer: apis.Committer{ // same as author as API doesn't return the committer
Name: name,
Email: email,
Date: c.Date,
},
Message: c.Message,
}

return latestCommit
}

func parseRawUser(raw string) (string, string) {
match := rawUserRe.FindStringSubmatch(raw)
if match != nil {
return strings.TrimSpace(match[1]), match[3]
}
return raw, ""
}
43 changes: 43 additions & 0 deletions bitbucketparser/v1/commit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package v1

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_parseRawUser(t *testing.T) {
tests := []struct {
raw string
wantName string
wantEmail string
}{
{
raw: "David Wertenteil <dwertent@cyberarmor.io>",
wantName: "David Wertenteil",
wantEmail: "dwertent@cyberarmor.io",
},
{
raw: "github-actions[bot] <github-actions[bot]@users.noreply.github.com>",
wantName: "github-actions[bot]",
wantEmail: "github-actions[bot]@users.noreply.github.com",
},
{
raw: "David Wertenteil",
wantName: "David Wertenteil",
wantEmail: "",
},
{
raw: "<dwertent@cyberarmor.io>",
wantName: "",
wantEmail: "dwertent@cyberarmor.io",
},
}
for _, tt := range tests {
t.Run(tt.raw, func(t *testing.T) {
got, got1 := parseRawUser(tt.raw)
assert.Equalf(t, tt.wantName, got, "parseRawUser(%v)", tt.raw)
assert.Equalf(t, tt.wantEmail, got1, "parseRawUser(%v)", tt.raw)
})
}
}
18 changes: 18 additions & 0 deletions bitbucketparser/v1/datastructures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package v1

import (
"github.com/kubescape/go-git-url/apis/bitbucketapi"
)

type BitBucketURL struct {
host string
owner string // repo owner
repo string // repo name
project string
branch string
path string
token string // github token
isFile bool

bitBucketAPI bitbucketapi.IBitBucketAPI
}
13 changes: 13 additions & 0 deletions bitbucketparser/v1/download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package v1

import "fmt"

func (gl *BitBucketURL) DownloadAllFiles() (map[string][]byte, map[string]error) {
//TODO implement me
return nil, map[string]error{"": fmt.Errorf("DownloadAllFiles is not supported")}
}

func (gl *BitBucketURL) DownloadFilesWithExtension(extensions []string) (map[string][]byte, map[string]error) {
//TODO implement me
return nil, map[string]error{"": fmt.Errorf("DownloadFilesWithExtension is not supported")}
}
Loading

0 comments on commit a13758b

Please sign in to comment.