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

r/codepipeline_webhook - validations #22406

Merged
merged 5 commits into from
Jan 5, 2022
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
11 changes: 11 additions & 0 deletions .changelog/22406.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:enhancement
resource/aws_codepipeline_webhook: Add plan time validation for `authentication_configuration.secret_token`, `filter.json_path`, `filter.match_equals`, `name`.
```

```release-note:enhancement
resource/aws_codepipeline_webhook: Add `arn` attribute.
```

```release-note:enhancement
resource/aws_codepipeline_webhook: Allow updating `filter` in place.
```
285 changes: 155 additions & 130 deletions internal/service/codepipeline/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package codepipeline
import (
"fmt"
"log"
"regexp"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/codepipeline"
Expand All @@ -26,15 +27,15 @@ func ResourceWebhook() *schema.Resource {
},

Schema: map[string]*schema.Schema{
"authentication": {
"arn": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
codepipeline.WebhookAuthenticationTypeGithubHmac,
codepipeline.WebhookAuthenticationTypeIp,
codepipeline.WebhookAuthenticationTypeUnauthenticated,
}, false),
Computed: true,
},
"authentication": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
ValidateFunc: validation.StringInSlice(codepipeline.WebhookAuthenticationType_Values(), false),
},
"authentication_configuration": {
Type: schema.TypeList,
Expand All @@ -45,10 +46,11 @@ func ResourceWebhook() *schema.Resource {
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"secret_token": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Sensitive: true,
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Sensitive: true,
ValidateFunc: validation.StringLenBetween(1, 100),
},
"allowed_ip_range": {
Type: schema.TypeString,
Expand All @@ -61,19 +63,21 @@ func ResourceWebhook() *schema.Resource {
},
"filter": {
Type: schema.TypeSet,
ForceNew: true,
Required: true,
MinItems: 1,
MaxItems: 5,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"json_path": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 150),
},

"match_equals": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 150),
},
},
},
Expand All @@ -82,13 +86,15 @@ func ResourceWebhook() *schema.Resource {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.All(
validation.StringLenBetween(1, 100),
validation.StringMatch(regexp.MustCompile(`[A-Za-z0-9.@\-_]+`), ""),
),
},

"url": {
Type: schema.TypeString,
Computed: true,
},

"target_action": {
Type: schema.TypeString,
ForceNew: true,
Expand All @@ -107,44 +113,15 @@ func ResourceWebhook() *schema.Resource {
}
}

func extractCodePipelineWebhookRules(filters *schema.Set) []*codepipeline.WebhookFilterRule {
var rules []*codepipeline.WebhookFilterRule

for _, f := range filters.List() {
r := f.(map[string]interface{})
filter := codepipeline.WebhookFilterRule{
JsonPath: aws.String(r["json_path"].(string)),
MatchEquals: aws.String(r["match_equals"].(string)),
}

rules = append(rules, &filter)
}

return rules
}

func extractCodePipelineWebhookAuthConfig(authType string, authConfig map[string]interface{}) *codepipeline.WebhookAuthConfiguration {
var conf codepipeline.WebhookAuthConfiguration
switch authType {
case codepipeline.WebhookAuthenticationTypeIp:
conf.AllowedIPRange = aws.String(authConfig["allowed_ip_range"].(string))
case codepipeline.WebhookAuthenticationTypeGithubHmac:
conf.SecretToken = aws.String(authConfig["secret_token"].(string))
}

return &conf
}

func resourceWebhookCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).CodePipelineConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))
authType := d.Get("authentication").(string)

var authConfig map[string]interface{}
if v, ok := d.GetOk("authentication_configuration"); ok {
l := v.([]interface{})
authConfig = l[0].(map[string]interface{})
if v, ok := d.GetOk("authentication_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
authConfig = v.([]interface{})[0].(map[string]interface{})
}

request := &codepipeline.PutWebhookInput{
Expand All @@ -161,14 +138,123 @@ func resourceWebhookCreate(d *schema.ResourceData, meta interface{}) error {

webhook, err := conn.PutWebhook(request)
if err != nil {
return fmt.Errorf("Error creating webhook: %s", err)
return fmt.Errorf("Error creating webhook: %w", err)
}

d.SetId(aws.StringValue(webhook.Webhook.Arn))

return resourceWebhookRead(d, meta)
}

func resourceWebhookRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).CodePipelineConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

arn := d.Id()
webhook, err := GetWebhook(conn, arn)

if tfresource.NotFound(err) {
log.Printf("[WARN] CodePipeline Webhook (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error getting CodePipeline Webhook (%s): %s", d.Id(), err)
}

webhookDef := webhook.Definition

name := aws.StringValue(webhookDef.Name)
if name == "" {
return fmt.Errorf("Webhook not found: %s", arn)
}

d.Set("name", name)
d.Set("url", webhook.Url)
d.Set("target_action", webhookDef.TargetAction)
d.Set("target_pipeline", webhookDef.TargetPipeline)
d.Set("authentication", webhookDef.Authentication)
d.Set("arn", webhook.Arn)

if err := d.Set("authentication_configuration", flattenCodePipelineWebhookAuthenticationConfiguration(webhookDef.AuthenticationConfiguration)); err != nil {
return fmt.Errorf("error setting authentication_configuration: %w", err)
}

if err := d.Set("filter", flattenCodePipelineWebhookFilters(webhookDef.Filters)); err != nil {
return fmt.Errorf("error setting filter: %w", err)
}

tags := KeyValueTags(webhook.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

if err := d.Set("tags_all", tags.Map()); err != nil {
return fmt.Errorf("error setting tags_all: %w", err)
}

return nil
}

func resourceWebhookUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).CodePipelineConn

if d.HasChangesExcept("tags_all", "tags", "register_with_third_party") {
authType := d.Get("authentication").(string)

var authConfig map[string]interface{}
if v, ok := d.GetOk("authentication_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
authConfig = v.([]interface{})[0].(map[string]interface{})
}

request := &codepipeline.PutWebhookInput{
Webhook: &codepipeline.WebhookDefinition{
Authentication: aws.String(authType),
Filters: extractCodePipelineWebhookRules(d.Get("filter").(*schema.Set)),
Name: aws.String(d.Get("name").(string)),
TargetAction: aws.String(d.Get("target_action").(string)),
TargetPipeline: aws.String(d.Get("target_pipeline").(string)),
AuthenticationConfiguration: extractCodePipelineWebhookAuthConfig(authType, authConfig),
},
}

_, err := conn.PutWebhook(request)
if err != nil {
return fmt.Errorf("Error updating webhook: %w", err)
}
}

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")

if err := UpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating CodePipeline Webhook (%s) tags: %w", d.Id(), err)
}
}

return resourceWebhookRead(d, meta)
}

func resourceWebhookDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).CodePipelineConn
name := d.Get("name").(string)

input := codepipeline.DeleteWebhookInput{
Name: &name,
}
_, err := conn.DeleteWebhook(&input)

if err != nil {
return fmt.Errorf("Could not delete webhook: %w", err)
}

return nil
}

func GetWebhook(conn *codepipeline.CodePipeline, arn string) (*codepipeline.ListWebhookItem, error) {
var nextToken string

Expand Down Expand Up @@ -234,92 +320,31 @@ func flattenCodePipelineWebhookAuthenticationConfiguration(authConfig *codepipel
return results
}

func resourceWebhookRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).CodePipelineConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

arn := d.Id()
webhook, err := GetWebhook(conn, arn)

if tfresource.NotFound(err) {
log.Printf("[WARN] CodePipeline Webhook (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error getting CodePipeline Webhook (%s): %s", d.Id(), err)
}

name := aws.StringValue(webhook.Definition.Name)
if name == "" {
return fmt.Errorf("Webhook not found: %s", arn)
}

d.Set("name", name)
d.Set("url", webhook.Url)

if err := d.Set("target_action", webhook.Definition.TargetAction); err != nil {
return err
}

if err := d.Set("target_pipeline", webhook.Definition.TargetPipeline); err != nil {
return err
}

if err := d.Set("authentication", webhook.Definition.Authentication); err != nil {
return err
}

if err := d.Set("authentication_configuration", flattenCodePipelineWebhookAuthenticationConfiguration(webhook.Definition.AuthenticationConfiguration)); err != nil {
return fmt.Errorf("error setting authentication_configuration: %s", err)
}

if err := d.Set("filter", flattenCodePipelineWebhookFilters(webhook.Definition.Filters)); err != nil {
return fmt.Errorf("error setting filter: %s", err)
}

tags := KeyValueTags(webhook.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

if err := d.Set("tags_all", tags.Map()); err != nil {
return fmt.Errorf("error setting tags_all: %w", err)
}

return nil
}

func resourceWebhookUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).CodePipelineConn

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")
func extractCodePipelineWebhookRules(filters *schema.Set) []*codepipeline.WebhookFilterRule {
var rules []*codepipeline.WebhookFilterRule

if err := UpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating CodePipeline Webhook (%s) tags: %s", d.Id(), err)
for _, f := range filters.List() {
r := f.(map[string]interface{})
filter := codepipeline.WebhookFilterRule{
JsonPath: aws.String(r["json_path"].(string)),
MatchEquals: aws.String(r["match_equals"].(string)),
}

rules = append(rules, &filter)
}

return resourceWebhookRead(d, meta)
return rules
}

func resourceWebhookDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).CodePipelineConn
name := d.Get("name").(string)

input := codepipeline.DeleteWebhookInput{
Name: &name,
}
_, err := conn.DeleteWebhook(&input)
func extractCodePipelineWebhookAuthConfig(authType string, authConfig map[string]interface{}) *codepipeline.WebhookAuthConfiguration {
var conf codepipeline.WebhookAuthConfiguration

if err != nil {
return fmt.Errorf("Could not delete webhook: %s", err)
switch authType {
case codepipeline.WebhookAuthenticationTypeIp:
conf.AllowedIPRange = aws.String(authConfig["allowed_ip_range"].(string))
case codepipeline.WebhookAuthenticationTypeGithubHmac:
conf.SecretToken = aws.String(authConfig["secret_token"].(string))
}

return nil
return &conf
}
Loading