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

CodeBuild Webhook FilterGroups #8110

Merged
merged 14 commits into from
Jun 6, 2019
149 changes: 142 additions & 7 deletions aws/resource_aws_codebuild_webhook.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package aws

import (
"bytes"
"fmt"
"github.com/hashicorp/terraform/helper/validation"
"log"

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

Expand All @@ -27,8 +30,47 @@ func resourceAwsCodeBuildWebhook() *schema.Resource {
ForceNew: true,
},
"branch_filter": {
Type: schema.TypeString,
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"filter_group"},
},
"filter_group": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"filter": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
codebuild.WebhookFilterTypeEvent,
codebuild.WebhookFilterTypeActorAccountId,
codebuild.WebhookFilterTypeBaseRef,
codebuild.WebhookFilterTypeFilePath,
codebuild.WebhookFilterTypeHeadRef,
}, false),
},
"exclude_matched_pattern": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"pattern": {
Type: schema.TypeString,
Required: true,
},
},
},
},
},
},
Set: resourceAwsCodeBuildWebhookFilterHash,
ConflictsWith: []string{"branch_filter"},
},
"payload_url": {
Type: schema.TypeString,
Expand All @@ -51,7 +93,8 @@ func resourceAwsCodeBuildWebhookCreate(d *schema.ResourceData, meta interface{})
conn := meta.(*AWSClient).codebuildconn

input := &codebuild.CreateWebhookInput{
ProjectName: aws.String(d.Get("project_name").(string)),
ProjectName: aws.String(d.Get("project_name").(string)),
FilterGroups: expandWebhookFilterGroups(d),
}

// The CodeBuild API requires this to be non-empty if defined
Expand All @@ -73,6 +116,42 @@ func resourceAwsCodeBuildWebhookCreate(d *schema.ResourceData, meta interface{})
return resourceAwsCodeBuildWebhookRead(d, meta)
}

func expandWebhookFilterGroups(d *schema.ResourceData) [][]*codebuild.WebhookFilter {
configs := d.Get("filter_group").(*schema.Set).List()

webhookFilters := make([][]*codebuild.WebhookFilter, 0)

if len(configs) == 0 {
return nil
}

for _, config := range configs {
filters := expandWebhookFilterData(config.(map[string]interface{}))
webhookFilters = append(webhookFilters, filters)
}

return webhookFilters
}

func expandWebhookFilterData(data map[string]interface{}) []*codebuild.WebhookFilter {
filters := make([]*codebuild.WebhookFilter, 0)

filterConfigs := data["filter"].([]interface{})

for i, filterConfig := range filterConfigs {
filter := filterConfig.(map[string]interface{})
filters = append(filters, &codebuild.WebhookFilter{
Type: aws.String(filter["type"].(string)),
ExcludeMatchedPattern: aws.Bool(filter["exclude_matched_pattern"].(bool)),
})
if v := filter["pattern"]; v != nil {
filters[i].Pattern = aws.String(v.(string))
}
}

return filters
}

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

Expand Down Expand Up @@ -101,6 +180,7 @@ func resourceAwsCodeBuildWebhookRead(d *schema.ResourceData, meta interface{}) e
}

d.Set("branch_filter", project.Webhook.BranchFilter)
d.Set("filter_group", flattenAwsCodeBuildWebhookFilterGroups(project.Webhook.FilterGroups))
d.Set("payload_url", project.Webhook.PayloadUrl)
d.Set("project_name", project.Name)
d.Set("url", project.Webhook.Url)
Expand All @@ -112,11 +192,22 @@ func resourceAwsCodeBuildWebhookRead(d *schema.ResourceData, meta interface{}) e
func resourceAwsCodeBuildWebhookUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).codebuildconn

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

if len(filterGroups) >= 1 {
_, err = conn.UpdateWebhook(&codebuild.UpdateWebhookInput{
ProjectName: aws.String(d.Id()),
FilterGroups: filterGroups,
RotateSecret: aws.Bool(false),
})
} else {
_, 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
Expand All @@ -141,3 +232,47 @@ func resourceAwsCodeBuildWebhookDelete(d *schema.ResourceData, meta interface{})

return nil
}

func flattenAwsCodeBuildWebhookFilterGroups(filterList [][]*codebuild.WebhookFilter) *schema.Set {
filterSet := schema.Set{
F: resourceAwsCodeBuildWebhookFilterHash,
}

for _, filters := range filterList {
filterSet.Add(flattenAwsCodeBuildWebhookFilterData(filters))
}
return &filterSet
}

func resourceAwsCodeBuildWebhookFilterHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})

for _, g := range m {
for _, f := range g.([]interface{}) {
r := f.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", r["type"].(string)))
buf.WriteString(fmt.Sprintf("%s-", r["pattern"].(string)))
buf.WriteString(fmt.Sprintf("%q", r["exclude_matched_pattern"]))
}
}

return hashcode.String(buf.String())
}

func flattenAwsCodeBuildWebhookFilterData(filters []*codebuild.WebhookFilter) map[string]interface{} {
values := map[string]interface{}{}
ff := make([]interface{}, 0)

for _, f := range filters {
ff = append(ff, map[string]interface{}{
"type": *f.Type,
"pattern": *f.Pattern,
"exclude_matched_pattern": *f.ExcludeMatchedPattern,
})
}

values["filter"] = ff

return values
}
90 changes: 90 additions & 0 deletions aws/resource_aws_codebuild_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package aws

import (
"fmt"
"reflect"
"regexp"
"testing"

Expand Down Expand Up @@ -156,6 +157,67 @@ func TestAccAWSCodeBuildWebhook_BranchFilter(t *testing.T) {
})
}

func TestAccAWSCodeBuildWebhook_FilterGroup(t *testing.T) {
var webhook codebuild.Webhook
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "aws_codebuild_webhook.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSCodeBuild(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCodeBuildWebhookDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCodeBuildWebhookConfig_FilterGroup(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSCodeBuildWebhookExists(resourceName, &webhook),
testAccCheckAWSCodeBuildWebhookFilter(&webhook, [][]*codebuild.WebhookFilter{
{
{
Type: aws.String("EVENT"),
Pattern: aws.String("PUSH"),
ExcludeMatchedPattern: aws.Bool(false),
},
{
Type: aws.String("HEAD_REF"),
Pattern: aws.String("refs/heads/master"),
ExcludeMatchedPattern: aws.Bool(true),
},
},
{
{
Type: aws.String("EVENT"),
Pattern: aws.String("PULL_REQUEST_UPDATED"),
ExcludeMatchedPattern: aws.Bool(false),
},
},
}),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"secret"},
},
},
})
}

func testAccCheckAWSCodeBuildWebhookFilter(webhook *codebuild.Webhook, expectedFilters [][]*codebuild.WebhookFilter) resource.TestCheckFunc {
return func(s *terraform.State) error {
if webhook == nil {
return fmt.Errorf("webhook missing")
}

if !reflect.DeepEqual(webhook.FilterGroups, expectedFilters) {
return fmt.Errorf("expected webhook filter configuration (%v), got: %v", expectedFilters, webhook.FilterGroups)
}

return nil
}
}

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

Expand Down Expand Up @@ -273,3 +335,31 @@ resource "aws_codebuild_webhook" "test" {
}
`, branchFilter)
}

func testAccAWSCodeBuildWebhookConfig_FilterGroup(rName string) string {
return fmt.Sprintf(testAccAWSCodeBuildProjectConfig_basic(rName) + `
resource "aws_codebuild_webhook" "test" {
project_name = "${aws_codebuild_project.test.name}"

filter_group {
filter {
type = "EVENT"
pattern = "PUSH"
}

filter {
type = "HEAD_REF"
pattern = "refs/heads/master"
exclude_matched_pattern = true
}
}

filter_group {
filter {
type = "EVENT"
pattern = "PULL_REQUEST_UPDATED"
}
}
}
`)
}
25 changes: 24 additions & 1 deletion website/docs/r/codebuild_webhook.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ When working with [Bitbucket](https://bitbucket.org) and [GitHub](https://github
```hcl
resource "aws_codebuild_webhook" "example" {
project_name = "${aws_codebuild_project.example.name}"

filter_group {
filter {
type = "EVENT"
pattern = "PUSH"
}

filter {
type = "HEAD_REF"
pattern = "master"
}
}
}
```

Expand Down Expand Up @@ -57,7 +69,18 @@ resource "github_repository_webhook" "example" {
The following arguments are supported:

* `project_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.
* `branch_filter` - (Optional) A regular expression used to determine which branches get built. Default is all branches are built. It is recommended to use `filter_group` over `branch_filter`.
* `filter_group` - (Optional) Information about the webhook's trigger. Filter group blocks are documented below.

`filter_group` supports the following:

* `filter` - (Required) A webhook filter for the group. Filter blocks are documented below.

`filter` supports the following:

* `type` - (Required) The webhook filter group's type. Valid values for this parameter are: `EVENT`, `BASE_REF`, `HEAD_REF`, `ACTOR_ACCOUNT_ID`, `FILE_PATH`. At least one filter group must specify `EVENT` as its type.
* `pattern` - (Required) For a filter that uses `EVENT` type, a comma-separated string that specifies one event: `PUSH`, `PULL_REQUEST_CREATED`, `PULL_REQUEST_UPDATED`, `PULL_REQUEST_REOPENED`. `PULL_REQUEST_REOPENED` works with GitHub & GitHub Enterprise only. For a fitler that uses any of the other filter types, a regular expression.
* `exclude_matched_pattern` - (Optional) If set to `true`, the specified filter does *not* trigger a build. Defaults to `false`.

## Attributes Reference

Expand Down