Skip to content

Commit

Permalink
Merge branch 'main' into variable_sets
Browse files Browse the repository at this point in the history
  • Loading branch information
rexredinger committed Mar 22, 2022
2 parents 30c245e + 2525151 commit 716b4e4
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/merged-pr.yml
@@ -0,0 +1,24 @@
name: Merged Pull Request
permissions:
pull-requests: write

# only trigger on pull request closed events
on:
pull_request_target:
types: [ closed ]

jobs:
merge_job:
# this job will only run if the PR has been merged
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v5
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Reminder to the contributor that merged this PR: if your changes have added important functionality or fixed a relevant bug, open a follow-up PR to update CHANGELOG.md with a note on your changes."
})
129 changes: 129 additions & 0 deletions comment.go
@@ -0,0 +1,129 @@
package tfe

import (
"context"
"fmt"
"net/url"
)

// Compile-time proof of interface implementation.
var _ Comments = (*comments)(nil)

// Comments describes all the comment related methods that the
// Terraform Enterprise API supports.
//
// TFE API docs:
// https://www.terraform.io/docs/cloud/api/comments.html
type Comments interface {
// List all comments of the given run.
List(ctx context.Context, runID string) (*CommentList, error)

// Read a comment by its ID.
Read(ctx context.Context, commentID string) (*Comment, error)

// Create a new comment with the given options.
Create(ctx context.Context, runID string, options CommentCreateOptions) (*Comment, error)
}

// Comments implements Comments.
type comments struct {
client *Client
}

// CommentList represents a list of comments.
type CommentList struct {
*Pagination
Items []*Comment
}

// Comment represents a Terraform Enterprise comment.
type Comment struct {
ID string `jsonapi:"primary,comments"`
Body string `jsonapi:"attr,body"`
}

type CommentCreateOptions struct {
// Type is a public field utilized by JSON:API to
// set the resource type via the field tag.
// It is not a user-defined value and does not need to be set.
// https://jsonapi.org/format/#crud-creating
Type string `jsonapi:"primary,comments"`

// Required: Body of the comment.
Body string `jsonapi:"attr,body"`
}

// List all comments of the given run.
func (s *comments) List(ctx context.Context, runID string) (*CommentList, error) {
if !validStringID(&runID) {
return nil, ErrInvalidRunID
}

u := fmt.Sprintf("runs/%s/comments", url.QueryEscape(runID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}

cl := &CommentList{}
err = s.client.do(ctx, req, cl)
if err != nil {
return nil, err
}

return cl, nil
}

// Create a new comment with the given options.
func (s *comments) Create(ctx context.Context, runID string, options CommentCreateOptions) (*Comment, error) {
if err := options.valid(); err != nil {
return nil, err
}

if !validStringID(&runID) {
return nil, ErrInvalidRunID
}

u := fmt.Sprintf("runs/%s/comments", url.QueryEscape(runID))
req, err := s.client.newRequest("POST", u, &options)
if err != nil {
return nil, err
}

comm := &Comment{}
err = s.client.do(ctx, req, comm)
if err != nil {
return nil, err
}

return comm, err
}

// Read a comment by its ID.
func (s *comments) Read(ctx context.Context, commentID string) (*Comment, error) {
if !validStringID(&commentID) {
return nil, ErrInvalidCommentID
}

u := fmt.Sprintf("comments/%s", url.QueryEscape(commentID))
req, err := s.client.newRequest("GET", u, nil)
if err != nil {
return nil, err
}

comm := &Comment{}
err = s.client.do(ctx, req, comm)
if err != nil {
return nil, err
}

return comm, nil
}

func (o CommentCreateOptions) valid() error {
if !validString(&o.Body) {
return ErrInvalidCommentBody
}

return nil
}
77 changes: 77 additions & 0 deletions comment_integration_test.go
@@ -0,0 +1,77 @@
//go:build integration
// +build integration

package tfe

import (
"context"
"testing"

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

func TestCommentsList(t *testing.T) {
client := testClient(t)
ctx := context.Background()

orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

wTest1, wTest1Cleanup := createWorkspace(t, client, orgTest)
defer wTest1Cleanup()

rTest, rTest1Cleanup := createRun(t, client, wTest1)
defer rTest1Cleanup()
commentBody1 := "1st comment test"
commentBody2 := "2nd comment test"

t.Run("without comments", func(t *testing.T) {
_, err := client.Comments.List(ctx, rTest.ID)
require.NoError(t, err)
})

t.Run("without a valid run", func(t *testing.T) {
cl, err := client.Comments.List(ctx, badIdentifier)
assert.Nil(t, cl)
assert.EqualError(t, err, ErrInvalidRunID.Error())
})

t.Run("create a comment", func(t *testing.T) {
options := CommentCreateOptions{
Body: commentBody1,
}
cl, err := client.Comments.Create(ctx, rTest.ID, options)
require.NoError(t, err)
assert.Equal(t, commentBody1, cl.Body)
})

t.Run("create 2nd comment", func(t *testing.T) {
options := CommentCreateOptions{
Body: commentBody2,
}
cl, err := client.Comments.Create(ctx, rTest.ID, options)
require.NoError(t, err)
assert.Equal(t, commentBody2, cl.Body)
})

t.Run("list comments", func(t *testing.T) {
commentsList, err := client.Comments.List(ctx, rTest.ID)
require.NoError(t, err)
assert.Len(t, commentsList.Items, 2)
assert.Equal(t, true, commentItemsContainsBody(commentsList.Items, commentBody1))
assert.Equal(t, true, commentItemsContainsBody(commentsList.Items, commentBody2))
})
}

func commentItemsContainsBody(items []*Comment, body string) bool {
hasBody := false
for _, item := range items {
if item.Body == body {
hasBody = true
break
}
}

return hasBody
}
6 changes: 6 additions & 0 deletions errors.go
Expand Up @@ -147,6 +147,10 @@ var (
ErrInvalidNotificationTrigger = errors.New("invalid value for notification trigger")

ErrInvalidVariableSetID = errors.New("invalid variable set ID")

ErrInvalidCommentID = errors.New("invalid value for comment ID")

ErrInvalidCommentBody = errors.New("invalid value for comment body")
)

// Missing values for required field/option
Expand Down Expand Up @@ -254,4 +258,6 @@ var (
ErrRequiredGlobalFlag = errors.New("global flag is required")

ErrRequiredWorkspacesList = errors.New("no workspaces list provided")

ErrCommentBody = errors.New("comment body is required")
)
1 change: 1 addition & 0 deletions run.go
Expand Up @@ -119,6 +119,7 @@ type Run struct {
PolicyChecks []*PolicyCheck `jsonapi:"relation,policy-checks"`
TaskStages []*TaskStage `jsonapi:"relation,task-stages,omitempty"`
Workspace *Workspace `jsonapi:"relation,workspace"`
Comments []*Comment `jsonapi:"relation,comments"`
}

// RunActions represents the run actions.
Expand Down
2 changes: 2 additions & 0 deletions tfe.go
Expand Up @@ -110,6 +110,7 @@ type Client struct {
AgentPools AgentPools
AgentTokens AgentTokens
Applies Applies
Comments Comments
ConfigurationVersions ConfigurationVersions
CostEstimates CostEstimates
NotificationConfigurations NotificationConfigurations
Expand Down Expand Up @@ -253,6 +254,7 @@ func NewClient(cfg *Config) (*Client, error) {
client.AgentPools = &agentPools{client: client}
client.AgentTokens = &agentTokens{client: client}
client.Applies = &applies{client: client}
client.Comments = &comments{client: client}
client.ConfigurationVersions = &configurationVersions{client: client}
client.CostEstimates = &costEstimates{client: client}
client.NotificationConfigurations = &notificationConfigurations{client: client}
Expand Down

0 comments on commit 716b4e4

Please sign in to comment.