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 aws_codebuild_webhook resource for creating GitHub webhooks for CodeBuild projects #4473

Merged
merged 7 commits into from
May 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ func Provider() terraform.ResourceProvider {
"aws_codecommit_repository": resourceAwsCodeCommitRepository(),
"aws_codecommit_trigger": resourceAwsCodeCommitTrigger(),
"aws_codebuild_project": resourceAwsCodeBuildProject(),
"aws_codebuild_webhook": resourceAwsCodeBuildWebhook(),
"aws_codepipeline": resourceAwsCodePipeline(),
"aws_customer_gateway": resourceAwsCustomerGateway(),
"aws_dax_cluster": resourceAwsDaxCluster(),
Expand Down
17 changes: 15 additions & 2 deletions aws/resource_aws_codebuild_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package aws

import (
"fmt"
"os"
"regexp"
"strings"
"testing"
Expand Down Expand Up @@ -290,6 +291,18 @@ func testAccCheckAWSCodeBuildProjectDestroy(s *terraform.State) error {
}

func testAccAWSCodeBuildProjectConfig_basic(rName, vpcConfig, vpcResources string) string {
// This is used for testing aws_codebuild_webhook as well as aws_codebuild_project.
// In order for that resource to work the Terraform AWS user must have done a GitHub
// OAuth dance.
//
// Additionally, the GitHub user that the Terraform AWS user logs in as must have
// access to the GitHub repository. This allows others to run tests for the webhook
// without having to have access to the Packer GitHub repository.
sourceURL, ok := os.LookupEnv("CODEBUILD_GITHUB_SOURCE_URL")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great idea -- I'll make sure this gets ported over in some fashion when I rebase this PR.

if !ok {
sourceURL = "https://github.com/hashicorp/packer.git"
}

return fmt.Sprintf(`
resource "aws_iam_role" "codebuild_role" {
name = "codebuild-role-%s"
Expand Down Expand Up @@ -375,7 +388,7 @@ resource "aws_codebuild_project" "foo" {

source {
type = "GITHUB"
location = "https://github.com/hashicorp/packer.git"
location = "%s"
}

tags {
Expand All @@ -384,7 +397,7 @@ resource "aws_codebuild_project" "foo" {
%s
}
%s
`, rName, rName, rName, rName, vpcConfig, vpcResources)
`, rName, rName, rName, rName, sourceURL, vpcConfig, vpcResources)
}

func testAccAWSCodeBuildProjectConfig_basicUpdated(rName string) string {
Expand Down
112 changes: 112 additions & 0 deletions aws/resource_aws_codebuild_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package aws

import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/codebuild"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsCodeBuildWebhook() *schema.Resource {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can easily add import support for this resource as the ID is already handled properly in the read function. Will add the passthrough importer to this resource and document how to import on merge.

return &schema.Resource{
Create: resourceAwsCodeBuildWebhookCreate,
Read: resourceAwsCodeBuildWebhookRead,
Delete: resourceAwsCodeBuildWebhookDelete,
Update: resourceAwsCodeBuildWebhookUpdate,

Schema: map[string]*schema.Schema{
"name": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might seem pedantic for now, but on merge will rename this attribute to project_name to better align with the API and help operators understand the association with projects.

Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"branch_filter": {
Type: schema.TypeString,
Optional: true,
},
"url": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceAwsCodeBuildWebhookCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).codebuildconn

resp, err := conn.CreateWebhook(&codebuild.CreateWebhookInput{
ProjectName: aws.String(d.Get("name").(string)),
BranchFilter: aws.String(d.Get("branch_filter").(string)),
})
if err != nil {
return err
}

d.SetId(d.Get("name").(string))
d.Set("branch_filter", d.Get("branch_filter").(string))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d.Set() should only be called in the read function 😄 -- will fix this on merge.

d.Set("url", resp.Webhook.Url)
return nil
}

func resourceAwsCodeBuildWebhookRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).codebuildconn

resp, err := conn.BatchGetProjects(&codebuild.BatchGetProjectsInput{
Names: []*string{
aws.String(d.Id()),
},
})

if err != nil {
return err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check for "resource not found" type errors and d.SetId("") similar to how the project resource does this. Will fix on merge. 👍

}

if len(resp.Projects) == 0 {
d.SetId("")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we're removing a resource from the state, we should log an appropriate warning message in the logs, e.g.

log.Printf("[WARN] CodeBuild Project %q not found, removing from state", d.Id())

return nil
}

project := resp.Projects[0]
d.Set("branch_filter", project.Webhook.BranchFilter)
d.Set("url", project.Webhook.Url)
return nil
}

func resourceAwsCodeBuildWebhookUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).codebuildconn

if !d.HasChange("branch_filter") {
return nil
}

_, err := conn.UpdateWebhook(&codebuild.UpdateWebhookInput{
ProjectName: aws.String(d.Id()),
BranchFilter: aws.String(d.Get("branch_filter").(string)),
RotateSecret: aws.Bool(false),
})

if err != nil {
return err
}

return nil
}

func resourceAwsCodeBuildWebhookDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).codebuildconn

_, err := conn.DeleteWebhook(&codebuild.DeleteWebhookInput{
ProjectName: aws.String(d.Id()),
})

if err != nil {
if isAWSErr(err, codebuild.ErrCodeResourceNotFoundException, "") {
d.SetId("")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d.SetId("") is extraneous in delete functions and can be removed. 👍

return nil
}
return err
}

d.SetId("")
return nil
}
78 changes: 78 additions & 0 deletions aws/resource_aws_codebuild_webhook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package aws

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/codebuild"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAwsCodeBuildWebhook_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsCodeBuildWebhookDestroy,
Steps: []resource.TestStep{
{
Config: testAccCodeBuildWebhookConfig_basic(acctest.RandString(5)),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsCodeBuildWebhookExists("aws_codebuild_webhook.test"),
resource.TestCheckResourceAttrSet("aws_codebuild_webhook.test", "url"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably be a little more cautious and at least partially validate the value as resource.TestCheckResourceAttrSet() will pass with empty strings and I believe even d.Set("XXX", nil)

e.g.

resource.TestMatchResourceAttr("aws_codebuild_webhook.test", "url", regexp.MustCompile(`^https://`))

),
},
},
})
}

func testAccCheckAwsCodeBuildWebhookDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).codebuildconn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_codebuild_webhook" {
continue
}

resp, err := conn.BatchGetProjects(&codebuild.BatchGetProjectsInput{
Names: []*string{
aws.String(rs.Primary.ID),
},
})

if err != nil {
return err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A resource not found exception here would be a good thing and should return nil -- will fix on merge.

}

if len(resp.Projects) == 0 {
return nil
}

project := resp.Projects[0]
if project.Webhook != nil && project.Webhook.Url != nil {
return fmt.Errorf("Found CodeBuild Webhook: %s", rs.Primary.ID)
}
}
return nil
}

func testAccCheckAwsCodeBuildWebhookExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
_, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to how the destroy acceptance test function above is checking against the API, we should do the same thing here to ensure the "physical" webhook actually exists.

return nil
}
}

func testAccCodeBuildWebhookConfig_basic(rName string) string {
return fmt.Sprintf(testAccAWSCodeBuildProjectConfig_basic(rName, "", "") + `
resource "aws_codebuild_webhook" "test" {
name = "${aws_codebuild_project.foo.name}"
}
`)
}
4 changes: 4 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,10 @@
<a href="/docs/providers/aws/r/codebuild_project.html">aws_codebuild_project</a>
</li>

<li<%= sidebar_current("docs-aws-resource-codebuild-webhook") %>>
<a href="/docs/providers/aws/r/codebuild_webhook.html">aws_codebuild_webhook</a>
</li>

</ul>
</li>

Expand Down
35 changes: 35 additions & 0 deletions website/docs/r/codebuild_webhook.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
layout: "aws"
page_title: "AWS: aws_codebuild_webhook"
sidebar_current: "docs-aws-resource-codebuild-webhook"
description: |-
Provides a CodeBuild Webhook resource.
---

# aws_codebuild_webhook

Provides a CodeBuild Webhook resource.

~> **Note:** The AWS account that Terraform uses to create this resource *must* have authorized CodeBuild to access GitHub's OAuth API. This is a manual step that must be done *before* creating webhooks with this resource. If OAuth is not configured, AWS will return an error similar to `ResourceNotFoundException: Could not find access token for server type github`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This manual step must also be done in every applicable region where the resource will be used. We should probably also point to the AWS documentation surrounding project webhooks here. Will update on merge.


## Example Usage

```hcl
resource "aws_codebuild_webhook" "github" {
name = "${aws_codebuild_project.my_project.name}"
}
```

## Argument Reference

The following arguments are supported:

* `name` - (Required) The name of the build project.
* `branch_filter` - (Optional) A regular expression used to determine which branches get built. Default is all branches are built.

## Attributes Reference

The following attributes are exported:

* `id` - The name of the build project.
* `url` - The URL to the webhook.