diff --git a/aws/resource_aws_sns_platform_application.go b/aws/resource_aws_sns_platform_application.go index 217b32f221b..f380704bfc3 100644 --- a/aws/resource_aws_sns_platform_application.go +++ b/aws/resource_aws_sns_platform_application.go @@ -1,6 +1,7 @@ package aws import ( + "crypto/sha256" "fmt" "log" "strings" @@ -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, @@ -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 @@ -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()), @@ -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 @@ -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] { diff --git a/website/docs/guides/version-3-upgrade.html.md b/website/docs/guides/version-3-upgrade.html.md index 6e6ba2374c7..da4b8197954 100644 --- a/website/docs/guides/version-3-upgrade.html.md +++ b/website/docs/guides/version-3-upgrade.html.md @@ -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) @@ -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