Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get commit status #75

Merged
merged 26 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
331c083
Add new interface method and GitHub implementation
EyalDelarea Feb 28, 2023
62324e3
Gitlab implementations
EyalDelarea Mar 1, 2023
8c3cf38
Azure repos implementations
EyalDelarea Mar 2, 2023
5865687
Bitbucket server implementations
EyalDelarea Mar 5, 2023
9535a53
initial bitbucketcloud.go implementation need to fix
EyalDelarea Mar 6, 2023
9746144
Merge branch 'master' of https://github.com/jfrog/froggit-go into imp…
EyalDelarea Mar 6, 2023
1b00d2d
remove hard coded name
EyalDelarea Mar 6, 2023
dd6c0e0
Implement bitbucketcloud
EyalDelarea Mar 6, 2023
b56fdd6
Merge branch 'master' of https://github.com/jfrog/froggit-go into imp…
EyalDelarea Mar 16, 2023
76f2453
handle errors
EyalDelarea Mar 16, 2023
fc8f240
handle errors
EyalDelarea Mar 16, 2023
860fcbb
Merge branch 'implement_get_commit_status' of https://github.com/Eyal…
EyalDelarea Mar 16, 2023
fbcc34f
refactor
EyalDelarea Mar 16, 2023
6bdd81b
Add dummy responses to all VCS providers
EyalDelarea Mar 19, 2023
bdbf594
Add mock tests and add map function for states
EyalDelarea Mar 19, 2023
6a91300
fix unused vars
EyalDelarea Mar 19, 2023
aaf86dc
fix code test coverage
EyalDelarea Mar 19, 2023
a58b9ea
add test coverage #@2
EyalDelarea Mar 19, 2023
8c47740
add lastUpdate time to commit status will fall back to creation time …
EyalDelarea Mar 23, 2023
8d685f2
CR notes
EyalDelarea Mar 23, 2023
80110c5
Merge branch 'master' of https://github.com/jfrog/froggit-go into imp…
EyalDelarea Mar 23, 2023
495ecd3
Add README.md
EyalDelarea Mar 23, 2023
9c2e81b
refactor tests
EyalDelarea Mar 23, 2023
390acdc
add creation time to CommitStatusInfo and remove fallback to be more …
EyalDelarea Mar 23, 2023
edad2af
Refactor CR notes.
EyalDelarea Mar 26, 2023
095fcd5
Refactor CR notes.
EyalDelarea Mar 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ Currently supported providers are: [GitHub](#github), [Bitbucket Server](#bitbuc
- [Update Webhook](#update-webhook)
- [Delete Webhook](#delete-webhook)
- [Set Commit Status](#set-commit-status)
- [Create Pull Request](#create-pull-request)
- [Get Commit Status](#get-commit-status)
- [Create Pull Request](#create-pull-request)
- [List Open Pull Requests](#list-open-pull-requests)
- [Add Pull Request Comment](#add-pull-request-comment)
- [List Pull Request Comments](#list-pull-request-comments)
- [Add Pull Request Comment](#add-pull-request-comment)
- [List Pull Request Comments](#list-pull-request-comments)
- [Get Latest Commit](#get-latest-commit)
- [Get Commit By SHA](#get-commit-by-sha)
- [Get List of Modified Files](#get-list-of-modified-files)
Expand Down Expand Up @@ -291,6 +292,20 @@ detailsURL := "https://acme.jfrog.io/ui/xray-scan-results-url"
err := client.SetCommitStatus(ctx, commitStatus, owner, repository, ref, title, description, detailsURL)
```

#### Get Commit Status

```go
// Go context
ctx := context.Background()
// Organization or username
owner := "jfrog"
// VCS repository
repository := "jfrog-cli"
// Commit tag on GitHub and GitLab, commit on Bitbucket
ref := "5c05522fecf8d93a11752ff255c99fcb0f0557cd"
commitStatuses, err := client.GetCommitStatus(ctx, owner, repository, ref)
EyalDelarea marked this conversation as resolved.
Show resolved Hide resolved
```

##### Create Pull Request

```go
Expand Down
80 changes: 72 additions & 8 deletions vcsclient/azurerepos.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@ import (
"context"
"errors"
"fmt"
"github.com/jfrog/froggit-go/vcsutils"
"github.com/jfrog/gofrog/datastructures"
"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
"github.com/mitchellh/mapstructure"
"io"
"net/http"
"os"
"sort"
"strings"

"github.com/jfrog/gofrog/datastructures"
"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/git"
"github.com/mitchellh/mapstructure"

"github.com/jfrog/froggit-go/vcsutils"
"time"
)

// Azure Devops API version 6
Expand All @@ -25,6 +24,34 @@ type AzureReposClient struct {
logger Log
}

func (client *AzureReposClient) GetCommitStatuses(ctx context.Context, owner, repository, ref string) (status []CommitStatusInfo, err error) {
EyalDelarea marked this conversation as resolved.
Show resolved Hide resolved
azureReposGitClient, err := client.buildAzureReposClient(ctx)
if err != nil {
return nil, err
}
commitStatusArgs := git.GetStatusesArgs{
CommitId: &ref,
RepositoryId: &repository,
Project: &repository,
}
resGitStatus, err := azureReposGitClient.GetStatuses(ctx, commitStatusArgs)
if err != nil {
return nil, err
}
results := make([]CommitStatusInfo, 0)
for _, singleStatus := range *resGitStatus {
results = append(results, CommitStatusInfo{
State: CommitStatusAsStringToStatus(string(*singleStatus.State)),
Description: *singleStatus.Description,
DetailsUrl: *singleStatus.TargetUrl,
Creator: *singleStatus.CreatedBy.DisplayName,
LastUpdatedAt: extractTimeFromAzuredevopsTime(singleStatus.UpdatedDate),
CreatedAt: extractTimeFromAzuredevopsTime(singleStatus.CreationDate),
})
}
return results, err
}

// NewAzureReposClient create a new AzureReposClient
func NewAzureReposClient(vcsInfo VcsInfo, logger Log) (*AzureReposClient, error) {
client := &AzureReposClient{vcsInfo: vcsInfo, logger: logger}
Expand Down Expand Up @@ -360,7 +387,27 @@ func (client *AzureReposClient) DeleteWebhook(ctx context.Context, owner, reposi

// SetCommitStatus on Azure Repos
func (client *AzureReposClient) SetCommitStatus(ctx context.Context, commitStatus CommitStatus, owner, repository, ref, title, description, detailsURL string) error {
return getUnsupportedInAzureError("set commit status")
azureReposGitClient, err := client.buildAzureReposClient(ctx)
if err != nil {
return err
}
statusState := git.GitStatusState(mapStatusToString(commitStatus))
commitStatusArgs := git.CreateCommitStatusArgs{
GitCommitStatusToCreate: &git.GitStatus{
Description: &description,
State: &statusState,
TargetUrl: &detailsURL,
Context: &git.GitStatusContext{
Name: &owner,
Genre: &title,
},
},
CommitId: &ref,
RepositoryId: &repository,
Project: &repository,
}
_, err = azureReposGitClient.CreateCommitStatus(ctx, commitStatusArgs)
return err
}

// DownloadFileFromRepo on Azure Repos
Expand Down Expand Up @@ -453,3 +500,20 @@ func remapFields[T any](src any, tagName string) (T, error) {
}
return dst, nil
}

// mapStatusToString - Maps commit status enum to string, specific for azure.
EyalDelarea marked this conversation as resolved.
Show resolved Hide resolved
func mapStatusToString(status CommitStatus) string {
conversionMap := map[CommitStatus]string{
Pass: "Succeeded",
Fail: "Failed",
Error: "Error",
InProgress: "Pending",
}
return conversionMap[status]
}
func extractTimeFromAzuredevopsTime(rawStatus *azuredevops.Time) time.Time {
if rawStatus == nil {
return time.Time{}
}
return rawStatus.Time.UTC()
}
EyalDelarea marked this conversation as resolved.
Show resolved Hide resolved
41 changes: 39 additions & 2 deletions vcsclient/azurerepos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,16 @@ func TestAzureReposClient_DeleteWebhook(t *testing.T) {

func TestAzureReposClient_SetCommitStatus(t *testing.T) {
ctx := context.Background()
client, cleanUp := createServerAndClient(t, vcsutils.AzureRepos, true, "", "unsupportedTest", createAzureReposHandler)
commitHash := "86d6919952702f9ab03bc95b45687f145a663de0"
expectedUri := "/_apis/ResourceAreas/commitStatus"
client, cleanUp := createServerAndClient(t, vcsutils.AzureRepos, true, nil, expectedUri, createAzureReposHandler)
defer cleanUp()
err := client.SetCommitStatus(ctx, 1, owner, repo1, "", "", "", "")
err := client.SetCommitStatus(ctx, 1, owner, repo1, commitHash, "", "", "")
assert.NoError(t, err)
yahavi marked this conversation as resolved.
Show resolved Hide resolved

badClient, badClientCleanup := createBadAzureReposClient(t, []byte{})
defer badClientCleanup()
err = badClient.SetCommitStatus(ctx, 1, owner, repo1, commitHash, "", "", "")
assert.Error(t, err)
}

Expand Down Expand Up @@ -443,6 +450,36 @@ func TestAzureReposClient_GetModifiedFiles(t *testing.T) {
})
}

func TestAzureReposClient_GetCommitStatus(t *testing.T) {
ctx := context.Background()
commitHash := "86d6919952702f9ab03bc95b45687f145a663de0"
expectedUri := "/_apis/ResourceAreas/commitStatus"
t.Run("Valid response", func(t *testing.T) {
response, err := os.ReadFile(filepath.Join("testdata", "azurerepos", "commits_statuses.json"))
assert.NoError(t, err)
client, cleanUp := createServerAndClient(t, vcsutils.AzureRepos, true, response, expectedUri, createAzureReposHandler)
defer cleanUp()
commitStatuses, err := client.GetCommitStatuses(ctx, owner, repo1, commitHash)
assert.NoError(t, err)
assert.True(t, len(commitStatuses) == 3)
assert.True(t, commitStatuses[0].State == Pass)
assert.True(t, commitStatuses[1].State == InProgress)
assert.True(t, commitStatuses[2].State == Fail)
})
t.Run("Empty response", func(t *testing.T) {
client, cleanUp := createServerAndClient(t, vcsutils.AzureRepos, true, nil, expectedUri, createAzureReposHandler)
defer cleanUp()
_, err := client.GetCommitStatuses(ctx, owner, repo1, commitHash)
assert.NoError(t, err)
})
t.Run("Bad client", func(t *testing.T) {
badClient, badClientCleanup := createBadAzureReposClient(t, []byte{})
defer badClientCleanup()
_, err := badClient.GetCommitStatuses(ctx, owner, repo1, "")
assert.Error(t, err)
})
}

func createAzureReposHandler(t *testing.T, expectedURI string, response []byte, expectedStatusCode int) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
base64Token := base64.StdEncoding.EncodeToString([]byte(":" + token))
Expand Down
19 changes: 19 additions & 0 deletions vcsclient/bitbucketcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@ type BitbucketCloudClient struct {
logger Log
}

// GetCommitStatuses on Bitbucket cloud
func (client *BitbucketCloudClient) GetCommitStatuses(ctx context.Context, owner, repository, ref string) (status []CommitStatusInfo, err error) {
EyalDelarea marked this conversation as resolved.
Show resolved Hide resolved
bitbucketClient := client.buildBitbucketCloudClient(ctx)
commitOptions := &bitbucket.CommitsOptions{
Owner: owner,
RepoSlug: repository,
Revision: ref,
}
rawStatuses, err := bitbucketClient.Repositories.Commits.GetCommitStatuses(commitOptions)
if err != nil {
return nil, err
}
results, err := BitbucketParseCommitStatuses(rawStatuses)
if err != nil {
return nil, err
}
return results, err
}

// NewBitbucketCloudClient create a new BitbucketCloudClient
func NewBitbucketCloudClient(vcsInfo VcsInfo, logger Log) (*BitbucketCloudClient, error) {
bitbucketClient := &BitbucketCloudClient{
Expand Down
23 changes: 23 additions & 0 deletions vcsclient/bitbucketcloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,29 @@ func TestBitbucketCloudClient_GetModifiedFiles(t *testing.T) {
})
}

func TestBitbucketCloudClient_GetCommitStatus(t *testing.T) {
ctx := context.Background()
t.Run("empty response", func(t *testing.T) {
client, cleanUp := createServerAndClient(t, vcsutils.BitbucketCloud, true, nil, "/repositories/owner/repo/commit/ref/statuses", createBitbucketCloudHandler)
defer cleanUp()
_, err := client.GetCommitStatuses(ctx, "owner", "repo", "ref")
assert.NoError(t, err)
})

t.Run("non empty response", func(t *testing.T) {
response, err := os.ReadFile(filepath.Join("testdata", "bitbucketcloud", "commits_statuses.json"))
assert.NoError(t, err)
client, cleanUp := createServerAndClient(t, vcsutils.BitbucketCloud, true, response, "/repositories/owner/repo/commit/ref/statuses", createBitbucketCloudHandler)
defer cleanUp()
commitStatuses, err := client.GetCommitStatuses(ctx, "owner", "repo", "ref")
assert.NoError(t, err)
assert.True(t, len(commitStatuses) == 3)
assert.True(t, commitStatuses[0].State == InProgress)
assert.True(t, commitStatuses[1].State == Pass)
assert.True(t, commitStatuses[2].State == Fail)
})
}

func createBitbucketCloudHandler(t *testing.T, expectedURI string, response []byte, expectedStatusCode int) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(expectedStatusCode)
Expand Down
57 changes: 57 additions & 0 deletions vcsclient/bitbucketserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,23 @@ func (client *BitbucketServerClient) SetCommitStatus(ctx context.Context, commit
return err
}

// GetCommitStatuses on Bitbucket server
func (client *BitbucketServerClient) GetCommitStatuses(ctx context.Context, owner, repository, ref string) (status []CommitStatusInfo, err error) {
bitbucketClient, err := client.buildBitbucketClient(ctx)
if err != nil {
return nil, err
}
response, err := bitbucketClient.GetCommitStatus(ref)
if err != nil {
return nil, err
}
results, err := BitbucketParseCommitStatuses(response.Values)
if err != nil {
return nil, err
}
return results, err
EyalDelarea marked this conversation as resolved.
Show resolved Hide resolved
}

// DownloadRepository on Bitbucket server
func (client *BitbucketServerClient) DownloadRepository(ctx context.Context, owner, repository, branch, localPath string) error {
bitbucketClient, err := client.buildBitbucketClient(ctx)
Expand Down Expand Up @@ -701,3 +718,43 @@ func getBitbucketServerRepositoryVisibility(public bool) RepositoryVisibility {
}
return Private
}

// BitbucketParseCommitStatuses parse raw response into CommitStatusInfo slice
// The response is the same for BitBucket cloud and server
func BitbucketParseCommitStatuses(rawStatuses interface{}) ([]CommitStatusInfo, error) {
EyalDelarea marked this conversation as resolved.
Show resolved Hide resolved
EyalDelarea marked this conversation as resolved.
Show resolved Hide resolved
results := make([]CommitStatusInfo, 0)
statuses := struct {
Statuses []struct {
Title string `mapstructure:"key"`
Url string `mapstructure:"url"`
State string `mapstructure:"state"`
Description string `mapstructure:"description"`
Creator string `mapstructure:"name"`
LastUpdatedAt string `mapstructure:"updated_on"`
CreatedAt string `mapstructure:"created_at"`
} `mapstructure:"values"`
}{}
err := mapstructure.Decode(rawStatuses, &statuses)
if err != nil {
return nil, err
}
for _, commitStatus := range statuses.Statuses {
lastUpdatedAt, err := time.Parse(time.RFC3339, commitStatus.LastUpdatedAt)
if err != nil {
return nil, err
}
createdAt, err := time.Parse(time.RFC3339, commitStatus.LastUpdatedAt)
if err != nil {
return nil, err
}
results = append(results, CommitStatusInfo{
State: CommitStatusAsStringToStatus(commitStatus.State),
Description: commitStatus.Description,
DetailsUrl: commitStatus.Url,
Creator: commitStatus.Creator,
LastUpdatedAt: lastUpdatedAt,
CreatedAt: createdAt,
})
}
return results, err
}
38 changes: 38 additions & 0 deletions vcsclient/bitbucketserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,44 @@ func createBitbucketServerWithBodyHandler(t *testing.T, expectedURI string, resp
assert.NoError(t, err)
}
}
func TestBitbucketServer_TestGetCommitStatus(t *testing.T) {
ctx := context.Background()
ref := "9caf1c431fb783b669f0f909bd018b40f2ea3808"
t.Run("Empty response", func(t *testing.T) {
client, cleanUp := createServerAndClient(t, vcsutils.BitbucketServer, false, nil,
fmt.Sprintf("/rest/build-status/1.0/commits/%s", ref), createBitbucketServerHandler)
defer cleanUp()
_, err := client.GetCommitStatuses(ctx, owner, repo1, ref)
assert.NoError(t, err)
})
t.Run("Valid response", func(t *testing.T) {
response, err := os.ReadFile(filepath.Join("testdata", "bitbucketserver", "commits_statuses.json"))
assert.NoError(t, err)
client, cleanUp := createServerAndClient(t, vcsutils.BitbucketServer, false, response,
fmt.Sprintf("/rest/build-status/1.0/commits/%s", ref), createBitbucketServerHandler)
defer cleanUp()
commitStatuses, err := client.GetCommitStatuses(ctx, owner, repo1, ref)
assert.NoError(t, err)
assert.True(t, len(commitStatuses) == 3)
assert.True(t, commitStatuses[0].State == InProgress)
assert.True(t, commitStatuses[1].State == Pass)
assert.True(t, commitStatuses[2].State == Fail)
})
t.Run("Decode failure", func(t *testing.T) {
response, err := os.ReadFile(filepath.Join("testdata", "bitbucketserver", "commits_statuses_bad_decode.json"))
assert.NoError(t, err)
client, cleanUp := createServerAndClient(t, vcsutils.BitbucketServer, false, response,
fmt.Sprintf("/rest/build-status/1.0/commits/%s", ref), createBitbucketServerHandler)
defer cleanUp()
_, err = client.GetCommitStatuses(ctx, owner, repo1, ref)
assert.Error(t, err)
})
t.Run("bad client", func(t *testing.T) {
client := createBadBitbucketServerClient(t)
_, err := client.GetCommitStatuses(ctx, owner, repo1, ref)
assert.Error(t, err)
})
}

func createBadBitbucketServerClient(t *testing.T) VcsClient {
client, err := NewClientBuilder(vcsutils.BitbucketServer).ApiEndpoint("https://bad^endpoint").Build()
Expand Down
Loading