Skip to content

Commit

Permalink
resource/aws_sns_platform_application: Finalize platform_credential a…
Browse files Browse the repository at this point in the history
…nd platform_princial hash removal

Reference: #3894
Reference: #9951
Reference: #12085
Reference: #13406

This also attempts to prevent the SetPlatformApplicationAttributes API call if no API updates need to occur. While we are in the midst of breaking changes and since this resource cannot be acceptance tested by the HashiCorp maintainers, this also fixes some other technical debt issues.
  • Loading branch information
bflad committed Jul 14, 2020
1 parent 91f448c commit a05aa9e
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 53 deletions.
152 changes: 99 additions & 53 deletions aws/resource_aws_sns_platform_application.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package aws

import (
"crypto/sha256"
"fmt"
"log"
"strings"
Expand All @@ -18,19 +19,6 @@ var snsPlatformRequiresPlatformPrincipal = map[string]bool{
"APNS_SANDBOX": true,
}

// Mutable attributes
// http://docs.aws.amazon.com/sns/latest/api/API_SetPlatformApplicationAttributes.html
var snsPlatformApplicationAttributeMap = map[string]string{
"event_delivery_failure_topic_arn": "EventDeliveryFailure",
"event_endpoint_created_topic_arn": "EventEndpointCreated",
"event_endpoint_deleted_topic_arn": "EventEndpointDeleted",
"event_endpoint_updated_topic_arn": "EventEndpointUpdated",
"failure_feedback_role_arn": "FailureFeedbackRoleArn",
"platform_principal": "PlatformPrincipal",
"success_feedback_role_arn": "SuccessFeedbackRoleArn",
"success_feedback_sample_rate": "SuccessFeedbackSampleRate",
}

func resourceAwsSnsPlatformApplication() *schema.Resource {
return &schema.Resource{
Create: resourceAwsSnsPlatformApplicationCreate,
Expand Down Expand Up @@ -137,17 +125,45 @@ func resourceAwsSnsPlatformApplicationUpdate(d *schema.ResourceData, meta interf

attributes := make(map[string]*string)

for k := range resourceAwsSnsPlatformApplication().Schema {
if attrKey, ok := snsPlatformApplicationAttributeMap[k]; ok {
if d.HasChange(k) {
log.Printf("[DEBUG] Updating %s", attrKey)
_, n := d.GetChange(k)
attributes[attrKey] = aws.String(n.(string))
}
}
if d.HasChange("event_delivery_failure_topic_arn") {
attributes["EventDeliveryFailure"] = aws.String(d.Get("event_delivery_failure_topic_arn").(string))
}

if d.HasChange("event_endpoint_created_topic_arn") {
attributes["EventEndpointCreated"] = aws.String(d.Get("event_endpoint_created_topic_arn").(string))
}

if d.HasChange("event_endpoint_deleted_topic_arn") {
attributes["EventEndpointDeleted"] = aws.String(d.Get("event_endpoint_deleted_topic_arn").(string))
}

if d.HasChange("event_endpoint_updated_topic_arn") {
attributes["EventEndpointUpdated"] = aws.String(d.Get("event_endpoint_updated_topic_arn").(string))
}

if d.HasChange("failure_feedback_role_arn") {
attributes["FailureFeedbackRoleArn"] = aws.String(d.Get("failure_feedback_role_arn").(string))
}

if d.HasChange("success_feedback_role_arn") {
attributes["SuccessFeedbackRoleArn"] = aws.String(d.Get("success_feedback_role_arn").(string))
}

if d.HasChange("success_feedback_sample_rate") {
attributes["SuccessFeedbackSampleRate"] = aws.String(d.Get("success_feedback_sample_rate").(string))
}

if d.HasChange("platform_credential") {
if d.HasChanges("platform_credential", "platform_principal") {
// Prior to version 3.0.0 of the Terraform AWS Provider, the platform_credential and platform_principal
// attributes were stored in state as SHA256 hashes. If the changes to these two attributes are the only
// changes and if both of their changes only match updating the state value, then skip the API call.
oPCRaw, nPCRaw := d.GetChange("platform_credential")
oPPRaw, nPPRaw := d.GetChange("platform_principal")

if len(attributes) == 0 && isChangeSha256Removal(oPCRaw, nPCRaw) && isChangeSha256Removal(oPPRaw, nPPRaw) {
return nil
}

attributes["PlatformCredential"] = aws.String(d.Get("platform_credential").(string))
// If the platform requires a principal it must also be specified, even if it didn't change
// since credential is stored as a hash, the only way to update principal is to update both
Expand All @@ -157,12 +173,6 @@ func resourceAwsSnsPlatformApplicationUpdate(d *schema.ResourceData, meta interf
}
}

if d.HasChange("platform_principal") {
// If the principal has changed we must also send the credential, even if it didn't change,
// as they must be specified together in the request.
attributes["PlatformCredential"] = aws.String(d.Get("platform_credential").(string))
}

// Make API call to update attributes
req := &sns.SetPlatformApplicationAttributesInput{
PlatformApplicationArn: aws.String(d.Id()),
Expand Down Expand Up @@ -206,36 +216,56 @@ func resourceAwsSnsPlatformApplicationRead(d *schema.ResourceData, meta interfac
d.Set("name", name)
d.Set("platform", platform)

attributeOutput, err := snsconn.GetPlatformApplicationAttributes(&sns.GetPlatformApplicationAttributesInput{
input := &sns.GetPlatformApplicationAttributesInput{
PlatformApplicationArn: aws.String(arn),
})
}

output, err := snsconn.GetPlatformApplicationAttributes(input)

if isAWSErr(err, sns.ErrCodeNotFoundException, "") {
log.Printf("[WARN] SNS Platform Application (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
if isAWSErr(err, sns.ErrCodeNotFoundException, "") {
log.Printf("[WARN] SNS Platform Application (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
return err
return fmt.Errorf("error getting SNS Platform Application (%s) attributes: %w", d.Id(), err)
}

if attributeOutput.Attributes != nil && len(attributeOutput.Attributes) > 0 {
attrmap := attributeOutput.Attributes
resource := *resourceAwsSnsPlatformApplication()
// iKey = internal struct key, oKey = AWS Attribute Map key
for iKey, oKey := range snsPlatformApplicationAttributeMap {
log.Printf("[DEBUG] Updating %s => %s", iKey, oKey)

if attrmap[oKey] != nil {
// Some of the fetched attributes are stateful properties such as
// the number of subscriptions, the owner, etc. skip those
if resource.Schema[iKey] != nil {
value := aws.StringValue(attrmap[oKey])
log.Printf("[DEBUG] Updating %s => %s -> %s", iKey, oKey, value)
d.Set(iKey, value)
}
}
}
if output == nil || output.Attributes == nil {
return fmt.Errorf("error getting SNS Platform Application (%s) attributes: empty response", d.Id())
}

if v, ok := output.Attributes["EventDeliveryFailure"]; ok {
d.Set("event_delivery_failure_topic_arn", v)
}

if v, ok := output.Attributes["EventEndpointCreated"]; ok {
d.Set("event_endpoint_created_topic_arn", v)
}

if v, ok := output.Attributes["EventEndpointDeleted"]; ok {
d.Set("event_endpoint_deleted_topic_arn", v)
}

if v, ok := output.Attributes["EventEndpointUpdated"]; ok {
d.Set("event_endpoint_updated_topic_arn", v)
}

if v, ok := output.Attributes["FailureFeedbackRoleArn"]; ok {
d.Set("failure_feedback_role_arn", v)
}

if v, ok := output.Attributes["PlatformPrincipal"]; ok {
d.Set("platform_principal", v)
}

if v, ok := output.Attributes["SuccessFeedbackRoleArn"]; ok {
d.Set("success_feedback_role_arn", v)
}

if v, ok := output.Attributes["SuccessFeedbackSampleRate"]; ok {
d.Set("success_feedback_sample_rate", v)
}

return nil
Expand Down Expand Up @@ -276,6 +306,22 @@ func decodeResourceAwsSnsPlatformApplicationID(input string) (arnS, name, platfo
return
}

func isChangeSha256Removal(oldRaw, newRaw interface{}) bool {
old, ok := oldRaw.(string)

if !ok {
return false
}

new, ok := newRaw.(string)

if !ok {
return false
}

return fmt.Sprintf("%x", sha256.Sum256([]byte(new))) == old
}

func validateAwsSnsPlatformApplication(d *schema.ResourceDiff) error {
platform := d.Get("platform").(string)
if snsPlatformRequiresPlatformPrincipal[platform] {
Expand Down
7 changes: 7 additions & 0 deletions website/docs/guides/version-3-upgrade.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Upgrade topics:
- [Resource: aws_emr_cluster](#resource-aws_emr_cluster)
- [Resource: aws_lb_listener_rule](#resource-aws_lb_listener_rule)
- [Resource: aws_s3_bucket](#resource-aws_s3_bucket)
- [Resource: aws_sns_platform_application](#resource-aws_sns_platform_application)
- [Resource: aws_spot_fleet_request](#resource-aws_spot_fleet_request)

<!-- /TOC -->
Expand Down Expand Up @@ -344,6 +345,12 @@ resource "aws_lb_listener_rule" "example" {

Previously when importing the `aws_s3_bucket` resource with the [`terraform import` command](/docs/commands/import.html), the Terraform AWS Provider would automatically attempt to import an associated `aws_s3_bucket_policy` resource as well. This automatic resource import has been removed. Use the [`aws_s3_bucket_policy` resource import](/docs/providers/aws/r/s3_bucket_policy.html#import) to import that resource separately.

## Resource: aws_sns_platform_application

### platform_credential and platform_principal Arguments No Longer Stored as SHA256 Hash

Previously when the `platform_credential` and `platform_principal` arguments were stored in state, they were stored as a SHA256 hash of the actual value. This prevented Terraform from properly updating the resource when necessary and the hashing has been removed. The Terraform AWS Provider will show an update to these arguments on the first apply after upgrading to version 3.0.0, which is fixing the Terraform state to remove the hash. Since the attributes are marked as sensitive, the values in the update will not be visible in the Terraform output. If the non-hashed values have not changed, then no update is occurring other than the Terraform state update. If these arguments are the only two updates and they both match the SHA256 removal, the apply will occur without submitting an actual `SetPlatformApplicationAttributes` API call.

## Resource: aws_spot_fleet_request

### valid_until Argument No Longer Uses 24 Hour Default
Expand Down

0 comments on commit a05aa9e

Please sign in to comment.