Skip to content

Commit

Permalink
feat(cloud): send reviewers in deployment creation. (#1330)
Browse files Browse the repository at this point in the history
## What this PR does / why we need it:

The Terramate Cloud frontend needs the list of reviewers of each
deployment.

## Which issue(s) this PR fixes:

## Special notes for your reviewer:

Unfortunately no tests were added because it requires a GH API fake
server.

## Does this PR introduce a user-facing change?
```
no
```
  • Loading branch information
i4ki committed Jan 5, 2024
2 parents 6d57f90 + f6f38d3 commit fa55721
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 20 deletions.
39 changes: 31 additions & 8 deletions cloud/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,14 +220,15 @@ type (

// DeploymentReviewRequest is the review_request object.
DeploymentReviewRequest struct {
Platform string `json:"platform"`
Repository string `json:"repository"`
CommitSHA string `json:"commit_sha"`
Number int `json:"number"`
Title string `json:"title"`
Description string `json:"description"`
URL string `json:"url"`
Labels []Label `json:"labels,omitempty"`
Platform string `json:"platform"`
Repository string `json:"repository"`
CommitSHA string `json:"commit_sha"`
Number int `json:"number"`
Title string `json:"title"`
Description string `json:"description"`
URL string `json:"url"`
Labels []Label `json:"labels,omitempty"`
Reviewers Reviewers `json:"reviewers,omitempty"`
}

// Label of a review request.
Expand All @@ -237,6 +238,15 @@ type (
Description string `json:"description,omitempty"`
}

// Reviewer is the user's reviewer of a Pull/Merge Request.
Reviewer struct {
Login string `json:"login"`
AvatarURL string `json:"avatar_url,omitempty"`
}

// Reviewers is a list of reviewers.
Reviewers []Reviewer

// UpdateDeploymentStack is the request payload item for updating the deployment status.
UpdateDeploymentStack struct {
StackID int64 `json:"stack_id"`
Expand Down Expand Up @@ -295,6 +305,8 @@ var (
_ = Resource(UpdateDeploymentStack{})
_ = Resource(UpdateDeploymentStacks{})
_ = Resource(DeploymentReviewRequest{})
_ = Resource(Reviewer{})
_ = Resource(Reviewers{})
_ = Resource(Label{})
_ = Resource(Drifts{})
_ = Resource(DriftStackPayloadRequest{})
Expand Down Expand Up @@ -534,6 +546,17 @@ func (l Label) Validate() error {
return nil
}

// Validate the reviewer.
func (r Reviewer) Validate() error {
if r.Login == "" {
return errors.E(`missing "login" field`)
}
return nil
}

// Validate the reviewers list.
func (rs Reviewers) Validate() error { return validateResourceList(rs...) }

// Validate the UpdateDeploymentStack object.
func (d UpdateDeploymentStack) Validate() error {
if d.StackID == 0 {
Expand Down
28 changes: 22 additions & 6 deletions cmd/terramate/cli/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,20 @@ func (c *cli) detectCloudMetadata() {
})
}

reviews, err := getGithubPullReviews(&ghClient, ghRepo, pull.Number)
if err != nil {
logger.Warn().
Err(err).
Msg("failed to retrieve PR reviews")
}

for _, review := range reviews {
reviewRequest.Reviewers = append(reviewRequest.Reviewers, cloud.Reviewer{
Login: review.User.Login,
AvatarURL: review.User.AvatarURL,
})
}

c.cloud.run.reviewRequest = reviewRequest

} else {
Expand Down Expand Up @@ -460,16 +474,18 @@ func getGithubCommit(ghClient *github.Client, repo string, commitName string) (*
return commit, nil
}

func getGithubPR(ghClient *github.Client, repo string, commitName string) ([]github.Pull, error) {
func getGithubPR(ghClient *github.Client, repo string, commitName string) (github.Pulls, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultGithubTimeout)
defer cancel()

pulls, err := ghClient.PullsForCommit(ctx, repo, commitName)
if err != nil {
return nil, err
}
return ghClient.PullsForCommit(ctx, repo, commitName)
}

func getGithubPullReviews(ghClient *github.Client, repo string, pullNumber int) (github.Reviews, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultGithubTimeout)
defer cancel()

return pulls, nil
return ghClient.PullReviews(ctx, repo, pullNumber)
}

func setDefaultGitMetadata(md *cloud.DeploymentMetadata, commit *git.CommitMetadata) {
Expand Down
57 changes: 52 additions & 5 deletions cmd/terramate/cli/github/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"

"github.com/terramate-io/terramate/errors"
Expand Down Expand Up @@ -56,9 +58,9 @@ type (

// PullsForCommit returns a list of pull request objects associated with the
// given commit SHA.
func (c *Client) PullsForCommit(ctx context.Context, repository, commit string) (pulls []Pull, err error) {
if !strings.Contains(repository, "/") {
return nil, errors.E("expects a valid Github repository of format <owner>/<name>")
func (c *Client) PullsForCommit(ctx context.Context, repository, commit string) (pulls Pulls, err error) {
if err := validateRepo(repository); err != nil {
return nil, err
}

url := fmt.Sprintf("%s/repos/%s/commits/%s/pulls", c.baseURL(), repository, commit)
Expand All @@ -75,8 +77,8 @@ func (c *Client) PullsForCommit(ctx context.Context, repository, commit string)

// Commit retrieves information about an specific commit in the GitHub API.
func (c *Client) Commit(ctx context.Context, repository, sha string) (*Commit, error) {
if !strings.Contains(repository, "/") {
return nil, errors.E("expects a valid Github repository of format <owner>/<name>")
if err := validateRepo(repository); err != nil {
return nil, err
}
url := fmt.Sprintf("%s/repos/%s/commits/%s", c.baseURL(), repository, sha)
data, err := c.doGet(ctx, url)
Expand All @@ -91,6 +93,44 @@ func (c *Client) Commit(ctx context.Context, repository, sha string) (*Commit, e
return &commit, nil
}

// PullReviews returns the list of reviews of the given pull request number.
func (c *Client) PullReviews(ctx context.Context, repository string, nr int) (Reviews, error) {
if err := validateRepo(repository); err != nil {
return nil, err
}
const perPage = 100
urlStr := fmt.Sprintf("%s/repos/%s/pulls/%d/reviews", c.baseURL(), repository, nr)
u, err := url.Parse(urlStr)
if err != nil {
return nil, err
}

query := url.Values{}
query.Set("per_page", strconv.Itoa(perPage))

nextPage := 1
var reviews Reviews
for {
query.Set("page", strconv.Itoa(nextPage))
u.RawQuery = query.Encode()
data, err := c.doGet(ctx, u.String())
if err != nil {
return nil, err
}
var reviewsPage Reviews
err = json.Unmarshal(data, &reviewsPage)
if err != nil {
return nil, errors.E(err, "unmarshaling reviews")
}
reviews = append(reviews, reviewsPage...)
if len(reviewsPage) < perPage {
break
}
nextPage++
}
return reviews, nil
}

// OIDCToken requests a new OIDC token.
func (c *Client) OIDCToken(ctx context.Context, cfg OIDCVars) (token string, err error) {
req, err := http.NewRequestWithContext(ctx, "GET", cfg.ReqURL, nil)
Expand Down Expand Up @@ -172,3 +212,10 @@ func (c *Client) httpClient() *http.Client {
}
return c.HTTPClient
}

func validateRepo(repository string) error {
if !strings.Contains(repository, "/") {
return errors.E("expects a valid Github repository of format <owner>/<name>")
}
return nil
}
13 changes: 13 additions & 0 deletions cmd/terramate/cli/github/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ type (
// rest of the fields aren't important for the cli.
}

// Review represents the review information.
Review struct {
User User `json:"user"`
Body string `json:"body,omitempty"`
State string `json:"state,omitempty"`
SubmittedAt time.Time `json:"submitted_at,omitempty"`
AuthorAssociation string `json:"author_association,omitempty"`
CommitID string `json:"commit_id,omitempty"`
}

// Label of the issue or pull request.
Label struct {
Name string `json:"name"`
Expand Down Expand Up @@ -85,4 +95,7 @@ type (

// Pulls represents a list of pull objects.
Pulls []Pull

// Reviews is a list of review objects.
Reviews []Review
)
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestInteropDrift(t *testing.T) {
AssertRunResult(t, tmcli.Run("list"), RunExpected{
Stdout: nljoin("."),
})
// inititialize the providers
// initialize the providers
AssertRunResult(t,
tmcli.Run("run", "--", TerraformTestPath, "init"),
RunExpected{
Expand Down

0 comments on commit fa55721

Please sign in to comment.