-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tmc): add support for bitbucket pipelines.
Signed-off-by: i4k <[email protected]>
- Loading branch information
Showing
12 changed files
with
692 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
image: golang | ||
|
||
pipelines: | ||
pull-requests: | ||
'**': | ||
- step: | ||
name: 'build Terramate' | ||
script: | ||
- make build | ||
- go install github.com/hashicorp/hc-install/cmd/hc-install@latest | ||
- hc-install install -version 1.6.0 terraform | ||
- cp ./terraform /usr/local/bin/terraform | ||
- export TMC_API_HOST=api.stg.terramate.io | ||
- export TMC_API_DEBUG=1 | ||
- export TM_CLOUD_ORGANIZATION=test | ||
- ./bin/terramate script run --tags golang --target linux-go-packages --parallel 12 preview | ||
- ./bin/terramate script run --tags e2etests --target linux-e2e --parallel 12 preview | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
// Copyright 2024 Terramate GmbH | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package bitbucket | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
|
||
"github.com/terramate-io/terramate/errors" | ||
) | ||
|
||
type ( | ||
// Client is a Bitbucket Cloud client. | ||
Client struct { | ||
// BaseURL is the base URL used to construct the final URL of endpoints. | ||
// For Bitbucket Cloud, it should be https://api.bitbucket.org/2.0 | ||
BaseURL string | ||
|
||
// HTTPClient is the HTTP client used to make requests. | ||
// if not set, a new http.Client is used on each request. | ||
HTTPClient *http.Client | ||
|
||
// Token is the Bitbucket Cloud token. | ||
Token string | ||
|
||
// Workspace is the Bitbucket Cloud workspace. | ||
Workspace string | ||
|
||
// RepoSlug is the Bitbucket Cloud repository slug. | ||
RepoSlug string | ||
} | ||
|
||
// PRs is a list of Bitbucket Pull Requests. | ||
PRs []PR | ||
|
||
// RenderedContent is the rendered version of the PR content. | ||
RenderedContent struct { | ||
Raw string `json:"raw"` | ||
Markup string `json:"markup"` | ||
HTML string `json:"html"` | ||
} | ||
|
||
// Rendered is the rendered version of the PR metadata. | ||
Rendered struct { | ||
Title RenderedContent `json:"title"` | ||
Description RenderedContent `json:"description"` | ||
Reason RenderedContent `json:"reason"` | ||
} | ||
|
||
// Summary is a Bitbucket Pull Request summary. | ||
Summary RenderedContent | ||
|
||
// Actor is a Bitbucket actor. | ||
Actor struct { | ||
Type string `json:"type"` | ||
} | ||
|
||
// Commit is a Bitbucket commit. | ||
Commit struct { | ||
Hash string `json:"hash"` | ||
} | ||
|
||
// Branch is a Bitbucket branch. | ||
Branch struct { | ||
Name string `json:"name"` | ||
MergeStrategies []string `json:"merge_strategies"` | ||
DefaultMergeStrategy string `json:"default_merge_strategy"` | ||
} | ||
|
||
// TargetBranch is the source or destination branch of a pull request. | ||
TargetBranch struct { | ||
Repository struct { | ||
Type string `json:"type"` | ||
} | ||
Branch Branch `json:"branch"` | ||
Commit Commit `json:"commit"` | ||
} | ||
|
||
// PR is a Bitbucket Pull Request. | ||
PR struct { | ||
Type string `json:"type"` | ||
ID int `json:"id"` | ||
Title string `json:"title"` | ||
Rendered Rendered `json:"rendered"` | ||
Summary Summary `json:"summary"` | ||
State string `json:"state"` | ||
Author Actor `json:"author"` | ||
Source TargetBranch `json:"source"` | ||
Destination TargetBranch `json:"destination"` | ||
MergeCommit Commit `json:"merge_commit"` | ||
CommentCount int `json:"comment_count"` | ||
TaskCount int `json:"task_count"` | ||
CloseSourceBranch bool `json:"close_source_branch"` | ||
ClosedBy *Actor `json:"closed_by,omitempty"` | ||
Reason string `json:"reason"` | ||
CreatedOn string `json:"created_on"` | ||
UpdatedOn string `json:"updated_on"` | ||
Reviewers []Actor `json:"reviewers"` | ||
Participants []Actor `json:"participants"` | ||
|
||
Links struct { | ||
HTML struct { | ||
Href string `json:"href"` | ||
} `json:"html"` | ||
} | ||
} | ||
|
||
// PullRequestResponse is the response of a pull request list. | ||
PullRequestResponse struct { | ||
Size int `json:"size"` | ||
Page int `json:"page"` | ||
PageLen int `json:"pagelen"` | ||
Next string `json:"next"` | ||
Previous string `json:"previous"` | ||
Values []PR `json:"values"` | ||
} | ||
) | ||
|
||
// GetPullRequestsByCommit fetches a list of pull requests that contain the given commit. | ||
// TODO: implement pagination. | ||
func (c *Client) GetPullRequestsByCommit(ctx context.Context, commit string) (prs []PR, err error) { | ||
url := fmt.Sprintf("%s/repositories/%s/%s/commit/%s/pullrequests", | ||
c.baseURL(), c.Workspace, c.RepoSlug, commit) | ||
|
||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create request: %w", err) | ||
} | ||
|
||
req.Header.Set("Accept", "application/json") | ||
|
||
if c.HTTPClient == nil { | ||
c.HTTPClient = &http.Client{} | ||
} | ||
|
||
if c.Token != "" { | ||
req.Header.Set("Authorization", "Bearer "+c.Token) | ||
} | ||
|
||
resp, err := c.HTTPClient.Do(req) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to execute request: %w", err) | ||
} | ||
|
||
defer func() { | ||
err = errors.L(err, resp.Body.Close()).AsError() | ||
}() | ||
|
||
data, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, errors.E(err, "reading response body") | ||
} | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
return nil, fmt.Errorf("unexpected status code: %d (%s)", resp.StatusCode, data) | ||
} | ||
|
||
var prResp PullRequestResponse | ||
err = json.Unmarshal(data, &prResp) | ||
if err != nil { | ||
return nil, errors.E(err, "unmarshaling PR list") | ||
} | ||
|
||
// fetch each PR details. | ||
for i, pr := range prResp.Values { | ||
pr, err := c.GetPullRequest(pr.ID) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get PR details: %w", err) | ||
} | ||
prResp.Values[i] = pr | ||
} | ||
|
||
return prResp.Values, nil | ||
} | ||
|
||
// GetPullRequest fetches a pull request by its ID. | ||
func (c *Client) GetPullRequest(id int) (pr PR, err error) { | ||
url := fmt.Sprintf("%s/repositories/%s/%s/pullrequests/%d", | ||
c.baseURL(), c.Workspace, c.RepoSlug, id) | ||
|
||
req, err := http.NewRequest("GET", url, nil) | ||
if err != nil { | ||
return PR{}, fmt.Errorf("failed to create request: %w", err) | ||
} | ||
|
||
req.Header.Set("Accept", "application/json") | ||
|
||
if c.HTTPClient == nil { | ||
c.HTTPClient = &http.Client{} | ||
} | ||
|
||
if c.Token != "" { | ||
req.Header.Set("Authorization", "Bearer "+c.Token) | ||
} | ||
|
||
resp, err := c.HTTPClient.Do(req) | ||
if err != nil { | ||
return PR{}, fmt.Errorf("failed to execute request: %w", err) | ||
} | ||
|
||
defer func() { | ||
err = errors.L(err, resp.Body.Close()).AsError() | ||
}() | ||
|
||
data, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return PR{}, errors.E(err, "reading response body") | ||
} | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
return PR{}, fmt.Errorf("unexpected status code: %d (%s)", resp.StatusCode, data) | ||
} | ||
|
||
err = json.Unmarshal(data, &pr) | ||
if err != nil { | ||
return PR{}, errors.E(err, "unmarshaling PR") | ||
} | ||
|
||
return pr, nil | ||
} | ||
|
||
func (c *Client) baseURL() string { | ||
if c.BaseURL != "" { | ||
return c.BaseURL | ||
} | ||
|
||
c.BaseURL = "https://api.bitbucket.org/2.0" | ||
return c.BaseURL | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Copyright 2024 Terramate GmbH | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package bitbucket_test | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/terramate-io/terramate/cmd/terramate/cli/bitbucket" | ||
) | ||
|
||
func TestBitbucketPRSmoke(t *testing.T) { | ||
if os.Getenv("BITBUCKET_BUILD_NUMBER") == "" { | ||
t.Skip("skipping test; not running in Bitbucket pipeline") | ||
} | ||
client := bitbucket.Client{ | ||
BaseURL: "https://api.bitbucket.org/2.0", | ||
Workspace: "terramate", | ||
RepoSlug: "terramate", | ||
Token: os.Getenv("TM_TEST_BITBUCKET_TOKEN"), | ||
} | ||
// Replace with actual workspace, repo, and commit values | ||
prs, err := client.GetPullRequestsByCommit( | ||
context.TODO(), | ||
"944fab3a920d9af4190577ee1e4a94b028038ef6", | ||
) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if len(prs) == 0 { | ||
t.Fatal("no pull requests found") | ||
} | ||
|
||
pr := prs[0] | ||
expected := bitbucket.PR{ | ||
Type: "pullrequest", | ||
ID: 1, | ||
Title: "chore: add basic bitbucket pipeline.", | ||
MergeCommit: bitbucket.Commit{ | ||
Hash: "944fab3a920d9af4190577ee1e4a94b028038ef6", | ||
}, | ||
CreatedOn: "2024-03-14T00:30:37.308Z", | ||
UpdatedOn: "2024-03-14T11:36:22.029Z", | ||
} | ||
|
||
if diff := cmp.Diff(expected, pr); diff != "" { | ||
t.Fatal(diff) | ||
} | ||
} |
Oops, something went wrong.