-
Notifications
You must be signed in to change notification settings - Fork 9.2k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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, | ||
|
@@ -37,6 +42,44 @@ func resourceAwsWafRegionalWebAcl() *schema.Resource { | |
}, | ||
}, | ||
}, | ||
"logging_configuration": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
MaxItems: 1, | ||
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, | ||
|
@@ -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) | ||
} | ||
|
||
|
@@ -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) | ||
} | ||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
|
||
|
@@ -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) | ||
} | ||
|
||
|
@@ -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} | ||
} |
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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 aschema.TypeList
defines the number of configuration blocks allowed, not the number of nested attributes inside.TypeList
andMaxItems: 1
is a common pattern for resources to generate a single configuration block, e.g.The following configuration will throw a validation error at plan time:
There was a problem hiding this comment.
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 😉