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

resource/aws_wafregional_web_acl: Add arn attribute and logging_configuration argument #7480

Merged
merged 1 commit into from
Feb 11, 2019
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
205 changes: 205 additions & 0 deletions aws/resource_aws_wafregional_web_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/waf"
"github.com/aws/aws-sdk-go/service/wafregional"
"github.com/hashicorp/terraform/helper/schema"
Expand All @@ -19,6 +20,10 @@ func resourceAwsWafRegionalWebAcl() *schema.Resource {
Delete: resourceAwsWafRegionalWebAclDelete,

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Required: true,
Expand All @@ -37,6 +42,44 @@ func resourceAwsWafRegionalWebAcl() *schema.Resource {
},
},
},
"logging_configuration": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Copy link
Contributor

@nywilken nywilken Feb 8, 2019

Choose a reason for hiding this comment

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

General question: MaxItems implies the number of resources that can be defined within this TypeList, but since this resource happens to be a map of *scheme.Schema the resource within the TypeList has no bounds on the number of keys defined within it. Is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thats correct, the MaxItems on a schema.TypeList defines the number of configuration blocks allowed, not the number of nested attributes inside. TypeList and MaxItems: 1 is a common pattern for resources to generate a single configuration block, e.g.

resource "TYPE" "NAME" {
  logging_configuration {
    # ... nested attributes ...
  }
}

The following configuration will throw a validation error at plan time:

resource "TYPE" "NAME" {
  logging_configuration {
    # ... nested attributes ...
  }

  logging_configuration {
    # ... nested attributes ...
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🙏 thanks for explanation and example case, which I validated as well 😉

Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"log_destination": {
Type: schema.TypeString,
Required: true,
},
"redacted_fields": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"field_to_match": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"data": {
Type: schema.TypeString,
Optional: true,
},
"type": {
Type: schema.TypeString,
Required: true,
},
},
},
},
},
},
},
},
},
},
"metric_name": {
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -118,6 +161,21 @@ func resourceAwsWafRegionalWebAclCreate(d *schema.ResourceData, meta interface{}
}
resp := out.(*waf.CreateWebACLOutput)
d.SetId(*resp.WebACL.WebACLId)

// The WAF API currently omits this, but use it when it becomes available
webACLARN := aws.StringValue(resp.WebACL.WebACLArn)
if webACLARN == "" {
webACLARN = arn.ARN{
AccountID: meta.(*AWSClient).accountid,
Partition: meta.(*AWSClient).partition,
Region: meta.(*AWSClient).region,
Resource: fmt.Sprintf("webacl/%s", d.Id()),
Service: "waf-regional",
}.String()
}
// Set for update
d.Set("arn", webACLARN)

return resourceAwsWafRegionalWebAclUpdate(d, meta)
}

Expand All @@ -144,6 +202,19 @@ func resourceAwsWafRegionalWebAclRead(d *schema.ResourceData, meta interface{})
return nil
}

// The WAF API currently omits this, but use it when it becomes available
webACLARN := aws.StringValue(resp.WebACL.WebACLArn)
if webACLARN == "" {
webACLARN = arn.ARN{
AccountID: meta.(*AWSClient).accountid,
Partition: meta.(*AWSClient).partition,
Region: meta.(*AWSClient).region,
Resource: fmt.Sprintf("webacl/%s", d.Id()),
Service: "waf-regional",
}.String()
}
d.Set("arn", webACLARN)

if err := d.Set("default_action", flattenWafAction(resp.WebACL.DefaultAction)); err != nil {
return fmt.Errorf("error setting default_action: %s", err)
}
Expand All @@ -153,6 +224,26 @@ func resourceAwsWafRegionalWebAclRead(d *schema.ResourceData, meta interface{})
return fmt.Errorf("error setting rule: %s", err)
}

getLoggingConfigurationInput := &waf.GetLoggingConfigurationInput{
ResourceArn: aws.String(d.Get("arn").(string)),
}
loggingConfiguration := []interface{}{}

log.Printf("[DEBUG] Getting WAF Regional Web ACL (%s) Logging Configuration: %s", d.Id(), getLoggingConfigurationInput)
getLoggingConfigurationOutput, err := conn.GetLoggingConfiguration(getLoggingConfigurationInput)

if err != nil && !isAWSErr(err, waf.ErrCodeNonexistentItemException, "") {
return fmt.Errorf("error getting WAF Regional Web ACL (%s) Logging Configuration: %s", d.Id(), err)
}

if getLoggingConfigurationOutput != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

General question: This seems to imply that the output can be nil even if there is no error. Is that the case or is this more of a guard pattern that is followed in the case base?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is one of those panic protections that is likely to never get used, but just in case the API or SDK does something unexpected in the future. Better safe than sorry; we've gotten burned in the past with API response changes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for that background. I'll keep that in mind for the future.

loggingConfiguration = flattenWAFRegionalLoggingConfiguration(getLoggingConfigurationOutput.LoggingConfiguration)
}

if err := d.Set("logging_configuration", loggingConfiguration); err != nil {
return fmt.Errorf("error setting logging_configuration: %s", err)
}

return nil
}

Expand All @@ -178,6 +269,31 @@ func resourceAwsWafRegionalWebAclUpdate(d *schema.ResourceData, meta interface{}
return fmt.Errorf("Error Updating WAF Regional ACL: %s", err)
}
}

if d.HasChange("logging_configuration") {
loggingConfiguration := d.Get("logging_configuration").([]interface{})

if len(loggingConfiguration) == 1 {
input := &waf.PutLoggingConfigurationInput{
LoggingConfiguration: expandWAFRegionalLoggingConfiguration(loggingConfiguration, d.Get("arn").(string)),
}

log.Printf("[DEBUG] Updating WAF Regional Web ACL (%s) Logging Configuration: %s", d.Id(), input)
if _, err := conn.PutLoggingConfiguration(input); err != nil {
return fmt.Errorf("error updating WAF Regional Web ACL (%s) Logging Configuration: %s", d.Id(), err)
}
} else {
input := &waf.DeleteLoggingConfigurationInput{
ResourceArn: aws.String(d.Get("arn").(string)),
}

log.Printf("[DEBUG] Deleting WAF Regional Web ACL (%s) Logging Configuration: %s", d.Id(), input)
if _, err := conn.DeleteLoggingConfiguration(input); err != nil {
return fmt.Errorf("error deleting WAF Regional Web ACL (%s) Logging Configuration: %s", d.Id(), err)
}
}
}

return resourceAwsWafRegionalWebAclRead(d, meta)
}

Expand Down Expand Up @@ -218,3 +334,92 @@ func resourceAwsWafRegionalWebAclDelete(d *schema.ResourceData, meta interface{}
}
return nil
}

func expandWAFRegionalLoggingConfiguration(l []interface{}, resourceARN string) *waf.LoggingConfiguration {
if len(l) == 0 || l[0] == nil {
return nil
}

m := l[0].(map[string]interface{})

loggingConfiguration := &waf.LoggingConfiguration{
LogDestinationConfigs: []*string{
aws.String(m["log_destination"].(string)),
},
RedactedFields: expandWAFRegionalRedactedFields(m["redacted_fields"].([]interface{})),
ResourceArn: aws.String(resourceARN),
}

return loggingConfiguration
}

func expandWAFRegionalRedactedFields(l []interface{}) []*waf.FieldToMatch {
if len(l) == 0 || l[0] == nil {
return nil
}

m := l[0].(map[string]interface{})

if m["field_to_match"] == nil {
return nil
}

redactedFields := make([]*waf.FieldToMatch, 0)

for _, fieldToMatch := range m["field_to_match"].(*schema.Set).List() {
if fieldToMatch == nil {
continue
}

redactedFields = append(redactedFields, expandFieldToMatch(fieldToMatch.(map[string]interface{})))
}

return redactedFields
}

func flattenWAFRegionalLoggingConfiguration(loggingConfiguration *waf.LoggingConfiguration) []interface{} {
if loggingConfiguration == nil {
return []interface{}{}
}

m := map[string]interface{}{
"log_destination": "",
"redacted_fields": flattenWAFRegionalRedactedFields(loggingConfiguration.RedactedFields),
}

if len(loggingConfiguration.LogDestinationConfigs) > 0 {
m["log_destination"] = aws.StringValue(loggingConfiguration.LogDestinationConfigs[0])
}

return []interface{}{m}
}

func flattenWAFRegionalRedactedFields(fieldToMatches []*waf.FieldToMatch) []interface{} {
if len(fieldToMatches) == 0 {
return []interface{}{}
}

fieldToMatchResource := &schema.Resource{
Schema: map[string]*schema.Schema{
"data": {
Type: schema.TypeString,
Optional: true,
},
"type": {
Type: schema.TypeString,
Required: true,
},
},
}
l := make([]interface{}, len(fieldToMatches))

for i, fieldToMatch := range fieldToMatches {
l[i] = flattenFieldToMatch(fieldToMatch)[0]
}

m := map[string]interface{}{
"field_to_match": schema.NewSet(schema.HashResource(fieldToMatchResource), l),
}

return []interface{}{m}
}
Loading