diff --git a/aws/provider.go b/aws/provider.go index bf3e513131b..fb31a0b0a78 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -505,6 +505,7 @@ func Provider() terraform.ResourceProvider { "aws_sqs_queue": resourceAwsSqsQueue(), "aws_sqs_queue_policy": resourceAwsSqsQueuePolicy(), "aws_snapshot_create_volume_permission": resourceAwsSnapshotCreateVolumePermission(), + "aws_sns_platform_application": resourceAwsSnsPlatformApplication(), "aws_sns_topic": resourceAwsSnsTopic(), "aws_sns_topic_policy": resourceAwsSnsTopicPolicy(), "aws_sns_topic_subscription": resourceAwsSnsTopicSubscription(), diff --git a/aws/resource_aws_sns_platform_application.go b/aws/resource_aws_sns_platform_application.go new file mode 100644 index 00000000000..4fe82d8503d --- /dev/null +++ b/aws/resource_aws_sns_platform_application.go @@ -0,0 +1,286 @@ +package aws + +import ( + "crypto/sha256" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/sns" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +var snsPlatformRequiresPlatformPrincipal = map[string]bool{ + "APNS": true, + "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, + Read: resourceAwsSnsPlatformApplicationRead, + Update: resourceAwsSnsPlatformApplicationUpdate, + Delete: resourceAwsSnsPlatformApplicationDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + CustomizeDiff: func(diff *schema.ResourceDiff, v interface{}) error { + return validateAwsSnsPlatformApplication(diff) + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "platform": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "platform_credential": { + Type: schema.TypeString, + Required: true, + StateFunc: hashSum, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "event_delivery_failure_topic_arn": { + Type: schema.TypeString, + Optional: true, + }, + "event_endpoint_created_topic_arn": { + Type: schema.TypeString, + Optional: true, + }, + "event_endpoint_deleted_topic_arn": { + Type: schema.TypeString, + Optional: true, + }, + "event_endpoint_updated_topic_arn": { + Type: schema.TypeString, + Optional: true, + }, + "failure_feedback_role_arn": { + Type: schema.TypeString, + Optional: true, + }, + "platform_principal": { + Type: schema.TypeString, + Optional: true, + StateFunc: hashSum, + }, + "success_feedback_role_arn": { + Type: schema.TypeString, + Optional: true, + }, + "success_feedback_sample_rate": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceAwsSnsPlatformApplicationCreate(d *schema.ResourceData, meta interface{}) error { + snsconn := meta.(*AWSClient).snsconn + + attributes := make(map[string]*string) + name := d.Get("name").(string) + platform := d.Get("platform").(string) + + attributes["PlatformCredential"] = aws.String(d.Get("platform_credential").(string)) + if v, ok := d.GetOk("platform_principal"); ok { + attributes["PlatformPrincipal"] = aws.String(v.(string)) + } + + req := &sns.CreatePlatformApplicationInput{ + Name: aws.String(name), + Platform: aws.String(platform), + Attributes: attributes, + } + + log.Printf("[DEBUG] SNS create application: %s", req) + + output, err := snsconn.CreatePlatformApplication(req) + if err != nil { + return fmt.Errorf("Error creating SNS platform application: %s", err) + } + + d.SetId(*output.PlatformApplicationArn) + + return resourceAwsSnsPlatformApplicationUpdate(d, meta) +} + +func resourceAwsSnsPlatformApplicationUpdate(d *schema.ResourceData, meta interface{}) error { + snsconn := meta.(*AWSClient).snsconn + + 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("platform_credential") { + 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 + // as they must be specified together in the request. + if v, ok := d.GetOk("platform_principal"); ok { + attributes["PlatformPrincipal"] = aws.String(v.(string)) + } + } + + // Make API call to update attributes + req := &sns.SetPlatformApplicationAttributesInput{ + PlatformApplicationArn: aws.String(d.Id()), + Attributes: attributes, + } + + err := resource.Retry(1*time.Minute, func() *resource.RetryError { + _, err := snsconn.SetPlatformApplicationAttributes(req) + if err != nil { + if isAWSErr(err, sns.ErrCodeInvalidParameterException, "is not a valid role to allow SNS to write to Cloudwatch Logs") { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + + if err != nil { + return fmt.Errorf("Error updating SNS platform application: %s", err) + } + + return resourceAwsSnsPlatformApplicationRead(d, meta) +} + +func resourceAwsSnsPlatformApplicationRead(d *schema.ResourceData, meta interface{}) error { + snsconn := meta.(*AWSClient).snsconn + + // There is no SNS Describe/GetPlatformApplication to fetch attributes like name and platform + // We will use the ID, which should be a platform application ARN, to: + // * Validate its an appropriate ARN on import + // * Parse out the name and platform + arn, name, platform, err := decodeResourceAwsSnsPlatformApplicationID(d.Id()) + if err != nil { + return err + } + + d.Set("arn", arn) + d.Set("name", name) + d.Set("platform", platform) + + attributeOutput, err := snsconn.GetPlatformApplicationAttributes(&sns.GetPlatformApplicationAttributesInput{ + PlatformApplicationArn: aws.String(arn), + }) + + if err != nil { + return 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 := *attrmap[oKey] + log.Printf("[DEBUG] Updating %s => %s -> %s", iKey, oKey, value) + d.Set(iKey, *attrmap[oKey]) + } + } + } + } + + return nil +} + +func resourceAwsSnsPlatformApplicationDelete(d *schema.ResourceData, meta interface{}) error { + snsconn := meta.(*AWSClient).snsconn + + log.Printf("[DEBUG] SNS Delete Application: %s", d.Id()) + _, err := snsconn.DeletePlatformApplication(&sns.DeletePlatformApplicationInput{ + PlatformApplicationArn: aws.String(d.Id()), + }) + if err != nil { + return err + } + return nil +} + +func decodeResourceAwsSnsPlatformApplicationID(input string) (arnS, name, platform string, err error) { + platformApplicationArn, err := arn.Parse(input) + if err != nil { + err = fmt.Errorf( + "SNS Platform Application ID must be of the form "+ + "arn:PARTITION:sns:REGION:ACCOUNTID:app/PLATFORM/NAME, "+ + "was provided %q and received error: %s", input, err) + return + } + + platformApplicationArnResourceParts := strings.Split(platformApplicationArn.Resource, "/") + if len(platformApplicationArnResourceParts) != 3 || platformApplicationArnResourceParts[0] != "app" { + err = fmt.Errorf( + "SNS Platform Application ID must be of the form "+ + "arn:PARTITION:sns:REGION:ACCOUNTID:app/PLATFORM/NAME, "+ + "was provided: %s", input) + return + } + + arnS = platformApplicationArn.String() + name = platformApplicationArnResourceParts[2] + platform = platformApplicationArnResourceParts[1] + return +} + +func hashSum(contents interface{}) string { + return fmt.Sprintf("%x", sha256.Sum256([]byte(contents.(string)))) +} + +func validateAwsSnsPlatformApplication(d *schema.ResourceDiff) error { + platform := d.Get("platform").(string) + if snsPlatformRequiresPlatformPrincipal[platform] { + if v, ok := d.GetOk("platform_principal"); ok { + value := v.(string) + if len(value) == 0 { + return fmt.Errorf("platform_principal must be non-empty when platform = %s", platform) + } + return nil + } + return fmt.Errorf("platform_principal is required when platform = %s", platform) + } + return nil +} diff --git a/aws/resource_aws_sns_platform_application_test.go b/aws/resource_aws_sns_platform_application_test.go new file mode 100644 index 00000000000..c26eb6192f3 --- /dev/null +++ b/aws/resource_aws_sns_platform_application_test.go @@ -0,0 +1,503 @@ +package aws + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + homedir "github.com/mitchellh/go-homedir" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sns" +) + +/** + Before running this test, at least one of these ENV variables combinations must be set: + + GCM_API_KEY - Google Cloud Messaging API Key + + APNS_SANDBOX_CREDENTIAL_PATH - Apple Push Notification Sandbox Private Key file location + APNS_SANDBOX_PRINCIPAL_PATH - Apple Push Notification Sandbox Certificate file location +**/ + +type testAccAwsSnsPlatformApplicationPlatform struct { + Name string + Credential string + CredentialHash string + Principal string + PrincipalHash string +} + +func testAccAwsSnsPlatformApplicationPlatformFromEnv(t *testing.T) []*testAccAwsSnsPlatformApplicationPlatform { + platforms := make([]*testAccAwsSnsPlatformApplicationPlatform, 0, 2) + + if os.Getenv("APNS_SANDBOX_CREDENTIAL_PATH") != "" { + if os.Getenv("APNS_SANDBOX_PRINCIPAL_PATH") == "" { + t.Fatalf("APNS_SANDBOX_CREDENTIAL_PATH set but missing APNS_SANDBOX_PRINCIPAL_PATH") + } + credentialHash, err := testAccHashSumPath(os.Getenv("APNS_SANDBOX_CREDENTIAL_PATH")) + if err != nil { + t.Fatal(err) + } + principalHash, err := testAccHashSumPath(os.Getenv("APNS_SANDBOX_PRINCIPAL_PATH")) + if err != nil { + t.Fatal(err) + } + + platform := &testAccAwsSnsPlatformApplicationPlatform{ + Name: "APNS_SANDBOX", + Credential: fmt.Sprintf("${file(pathexpand(%q))}", os.Getenv("APNS_SANDBOX_CREDENTIAL_PATH")), + CredentialHash: credentialHash, + Principal: fmt.Sprintf("${file(pathexpand(%q))}", os.Getenv("APNS_SANDBOX_PRINCIPAL_PATH")), + PrincipalHash: principalHash, + } + platforms = append(platforms, platform) + } + + if os.Getenv("GCM_API_KEY") != "" { + platform := &testAccAwsSnsPlatformApplicationPlatform{ + Name: "GCM", + Credential: os.Getenv("GCM_API_KEY"), + CredentialHash: hashSum(os.Getenv("GCM_API_KEY")), + } + platforms = append(platforms, platform) + } + + if len(platforms) == 0 { + t.Skipf("no SNS Platform Application environment variables found") + } + return platforms +} + +func testAccHashSumPath(path string) (string, error) { + path, err := homedir.Expand(path) + if err != nil { + return "", err + } + data, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } + return hashSum(string(data)), nil +} + +func TestDecodeResourceAwsSnsPlatformApplicationID(t *testing.T) { + + var testCases = []struct { + Input string + ExpectedArn string + ExpectedName string + ExpectedPlatform string + ErrCount int + }{ + { + Input: "arn:aws:sns:us-east-1:123456789012:app/APNS_SANDBOX/myAppName", + ExpectedArn: "arn:aws:sns:us-east-1:123456789012:app/APNS_SANDBOX/myAppName", + ExpectedName: "myAppName", + ExpectedPlatform: "APNS_SANDBOX", + ErrCount: 0, + }, + { + Input: "arn:aws:sns:us-east-1:123456789012:app/APNS_SANDBOX/myAppName/extra", + ExpectedArn: "", + ExpectedName: "", + ExpectedPlatform: "", + ErrCount: 1, + }, + { + Input: "arn:aws:sns:us-east-1:123456789012:endpoint/APNS_SANDBOX/myAppName/someID", + ExpectedArn: "", + ExpectedName: "", + ExpectedPlatform: "", + ErrCount: 1, + }, + { + Input: "arn:aws:sns:us-east-1:123456789012:APNS_SANDBOX/myAppName", + ExpectedArn: "", + ExpectedName: "", + ExpectedPlatform: "", + ErrCount: 1, + }, + { + Input: "arn:aws:sns:us-east-1:123456789012:app", + ExpectedArn: "", + ExpectedName: "", + ExpectedPlatform: "", + ErrCount: 1, + }, + { + Input: "myAppName", + ExpectedArn: "", + ExpectedName: "", + ExpectedPlatform: "", + ErrCount: 1, + }, + } + + for _, tc := range testCases { + arn, name, platform, err := decodeResourceAwsSnsPlatformApplicationID(tc.Input) + if tc.ErrCount == 0 && err != nil { + t.Fatalf("expected %q not to trigger an error, received: %s", tc.Input, err) + } + if tc.ErrCount > 0 && err == nil { + t.Fatalf("expected %q to trigger an error", tc.Input) + } + if arn != tc.ExpectedArn { + t.Fatalf("expected %q to return arn: %s", tc.Input, arn) + } + if name != tc.ExpectedName { + t.Fatalf("expected %q to return name: %s", tc.Input, name) + } + if platform != tc.ExpectedPlatform { + t.Fatalf("expected %q to return platform: %s", tc.Input, platform) + } + } +} + +func TestAccAwsSnsPlatformApplication_basic(t *testing.T) { + platforms := testAccAwsSnsPlatformApplicationPlatformFromEnv(t) + resourceName := "aws_sns_platform_application.test" + + for _, platform := range platforms { + name := fmt.Sprintf("tf-acc-%d", acctest.RandInt()) + platformPrincipalCheck := resource.TestCheckNoResourceAttr(resourceName, "platform_principal") + if platform.Principal != "" { + platformPrincipalCheck = resource.TestCheckResourceAttr(resourceName, "platform_principal", platform.PrincipalHash) + } + + t.Run(platform.Name, func(*testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSNSPlatformApplicationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSnsPlatformApplicationConfig_basic(name, &testAccAwsSnsPlatformApplicationPlatform{ + Name: "APNS", + Credential: "NOTEMPTY", + Principal: "", + }), + ExpectError: regexp.MustCompile(`platform_principal is required when platform =`), + }, + { + Config: testAccAwsSnsPlatformApplicationConfig_basic(name, &testAccAwsSnsPlatformApplicationPlatform{ + Name: "APNS_SANDBOX", + Credential: "NOTEMPTY", + Principal: "", + }), + ExpectError: regexp.MustCompile(`platform_principal is required when platform =`), + }, + { + Config: testAccAwsSnsPlatformApplicationConfig_basic(name, platform), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSnsPlatformApplicationExists(resourceName), + resource.TestMatchResourceAttr(resourceName, "arn", regexp.MustCompile(fmt.Sprintf("^arn:[^:]+:sns:[^:]+:[^:]+:app/%s/%s$", platform.Name, name))), + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "platform", platform.Name), + resource.TestCheckResourceAttr(resourceName, "platform_credential", platform.CredentialHash), + platformPrincipalCheck, + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"platform_credential", "platform_principal"}, + }, + }, + }) + }) + } +} + +func TestAccAwsSnsPlatformApplication_basicAttributes(t *testing.T) { + platforms := testAccAwsSnsPlatformApplicationPlatformFromEnv(t) + resourceName := "aws_sns_platform_application.test" + + var testCases = []struct { + AttributeKey string + AttributeValue string + AttributeValueUpdate string + }{ + { + AttributeKey: "success_feedback_sample_rate", + AttributeValue: "100", + AttributeValueUpdate: "99", + }, + } + + for _, platform := range platforms { + t.Run(platform.Name, func(*testing.T) { + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s/%s", platform.Name, tc.AttributeKey), func(*testing.T) { + name := fmt.Sprintf("tf-acc-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSNSPlatformApplicationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSnsPlatformApplicationConfig_basicAttribute(name, platform, tc.AttributeKey, tc.AttributeValue), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSnsPlatformApplicationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, tc.AttributeKey, tc.AttributeValue), + ), + }, + { + Config: testAccAwsSnsPlatformApplicationConfig_basicAttribute(name, platform, tc.AttributeKey, tc.AttributeValueUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSnsPlatformApplicationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, tc.AttributeKey, tc.AttributeValueUpdate), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"platform_credential", "platform_principal"}, + }, + }, + }) + }) + } + }) + } +} + +func TestAccAwsSnsPlatformApplication_iamRoleAttributes(t *testing.T) { + platforms := testAccAwsSnsPlatformApplicationPlatformFromEnv(t) + resourceName := "aws_sns_platform_application.test" + + var testCases = []string{ + "failure_feedback_role_arn", + "success_feedback_role_arn", + } + + for _, platform := range platforms { + t.Run(platform.Name, func(*testing.T) { + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s/%s", platform.Name, tc), func(*testing.T) { + iamRoleName1 := fmt.Sprintf("tf-acc-%d", acctest.RandInt()) + iamRoleName2 := fmt.Sprintf("tf-acc-%d", acctest.RandInt()) + name := fmt.Sprintf("tf-acc-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSNSPlatformApplicationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSnsPlatformApplicationConfig_iamRoleAttribute(name, platform, tc, iamRoleName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSnsPlatformApplicationExists(resourceName), + resource.TestMatchResourceAttr(resourceName, tc, regexp.MustCompile(fmt.Sprintf("^arn:[^:]+:iam::[^:]+:role/%s$", iamRoleName1))), + ), + }, + { + Config: testAccAwsSnsPlatformApplicationConfig_iamRoleAttribute(name, platform, tc, iamRoleName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSnsPlatformApplicationExists(resourceName), + resource.TestMatchResourceAttr(resourceName, tc, regexp.MustCompile(fmt.Sprintf("^arn:[^:]+:iam::[^:]+:role/%s$", iamRoleName2))), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"platform_credential", "platform_principal"}, + }, + }, + }) + }) + } + }) + } +} + +func TestAccAwsSnsPlatformApplication_snsTopicAttributes(t *testing.T) { + platforms := testAccAwsSnsPlatformApplicationPlatformFromEnv(t) + resourceName := "aws_sns_platform_application.test" + + var testCases = []string{ + "event_delivery_failure_topic_arn", + "event_endpoint_created_topic_arn", + "event_endpoint_deleted_topic_arn", + "event_endpoint_updated_topic_arn", + } + + for _, platform := range platforms { + t.Run(platform.Name, func(*testing.T) { + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s/%s", platform.Name, tc), func(*testing.T) { + snsTopicName1 := fmt.Sprintf("tf-acc-%d", acctest.RandInt()) + snsTopicName2 := fmt.Sprintf("tf-acc-%d", acctest.RandInt()) + name := fmt.Sprintf("tf-acc-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSNSPlatformApplicationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSnsPlatformApplicationConfig_snsTopicAttribute(name, platform, tc, snsTopicName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSnsPlatformApplicationExists(resourceName), + resource.TestMatchResourceAttr(resourceName, tc, regexp.MustCompile(fmt.Sprintf("^arn:[^:]+:sns:[^:]+:[^:]+:%s$", snsTopicName1))), + ), + }, + { + Config: testAccAwsSnsPlatformApplicationConfig_snsTopicAttribute(name, platform, tc, snsTopicName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSnsPlatformApplicationExists(resourceName), + resource.TestMatchResourceAttr(resourceName, tc, regexp.MustCompile(fmt.Sprintf("^arn:[^:]+:sns:[^:]+:[^:]+:%s$", snsTopicName2))), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"platform_credential", "platform_principal"}, + }, + }, + }) + }) + } + }) + } +} + +func testAccCheckAwsSnsPlatformApplicationExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("missing ID: %s", name) + } + + conn := testAccProvider.Meta().(*AWSClient).snsconn + + input := &sns.GetPlatformApplicationAttributesInput{ + PlatformApplicationArn: aws.String(rs.Primary.ID), + } + + log.Printf("[DEBUG] Reading SNS Platform Application attributes: %s", input) + _, err := conn.GetPlatformApplicationAttributes(input) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckAWSSNSPlatformApplicationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).snsconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_sns_platform_application" { + continue + } + + input := &sns.GetPlatformApplicationAttributesInput{ + PlatformApplicationArn: aws.String(rs.Primary.ID), + } + + log.Printf("[DEBUG] Reading SNS Platform Application attributes: %s", input) + _, err := conn.GetPlatformApplicationAttributes(input) + if err != nil { + if isAWSErr(err, sns.ErrCodeNotFoundException, "") { + return nil + } + return err + } + } + return nil +} + +func testAccAwsSnsPlatformApplicationConfig_basic(name string, platform *testAccAwsSnsPlatformApplicationPlatform) string { + if platform.Principal == "" { + return fmt.Sprintf(` +resource "aws_sns_platform_application" "test" { + name = "%s" + platform = "%s" + platform_credential = "%s" +} +`, name, platform.Name, platform.Credential) + } + return fmt.Sprintf(` +resource "aws_sns_platform_application" "test" { + name = "%s" + platform = "%s" + platform_credential = "%s" + platform_principal = "%s" +} +`, name, platform.Name, platform.Credential, platform.Principal) +} + +func testAccAwsSnsPlatformApplicationConfig_basicAttribute(name string, platform *testAccAwsSnsPlatformApplicationPlatform, attributeKey, attributeValue string) string { + if platform.Principal == "" { + return fmt.Sprintf(` +resource "aws_sns_platform_application" "test" { + name = "%s" + platform = "%s" + platform_credential = "%s" + %s = "%s" +} +`, name, platform.Name, platform.Credential, attributeKey, attributeValue) + } + return fmt.Sprintf(` +resource "aws_sns_platform_application" "test" { + name = "%s" + platform = "%s" + platform_credential = "%s" + platform_principal = "%s" + %s = "%s" +} +`, name, platform.Name, platform.Credential, platform.Principal, attributeKey, attributeValue) +} + +func testAccAwsSnsPlatformApplicationConfig_iamRoleAttribute(name string, platform *testAccAwsSnsPlatformApplicationPlatform, attributeKey, iamRoleName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "test" { + assume_role_policy = <SNS Resources