diff --git a/.changelog/44232.txt b/.changelog/44232.txt new file mode 100644 index 000000000000..e00988ef1c85 --- /dev/null +++ b/.changelog/44232.txt @@ -0,0 +1,3 @@ +```release-note:new-action +aws_sns_publish +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 026dbb81080e..ae13c0637878 100644 --- a/go.mod +++ b/go.mod @@ -289,7 +289,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.7.0 - github.com/hashicorp/hcl/v2 v2.23.0 + github.com/hashicorp/hcl/v2 v2.24.0 github.com/hashicorp/terraform-json v0.27.2 github.com/hashicorp/terraform-plugin-framework v1.16.0 github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 @@ -300,7 +300,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-mux v0.21.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 - github.com/hashicorp/terraform-plugin-testing v1.13.3 + github.com/hashicorp/terraform-plugin-testing v1.13.3-0.20250909115916-1a2eeae85247 github.com/jaswdr/faker/v2 v2.8.0 github.com/jmespath/go-jmespath v0.4.0 github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 @@ -349,7 +349,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/hc-install v0.9.2 // indirect github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-exec v0.23.0 // indirect + github.com/hashicorp/terraform-exec v0.23.1-0.20250717072919-061a850a52d2 // indirect github.com/hashicorp/terraform-registry-address v0.4.0 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.2 // indirect @@ -368,7 +368,7 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/zclconf/go-cty v1.16.4 // indirect + github.com/zclconf/go-cty v1.17.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.63.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect diff --git a/go.sum b/go.sum index 45428faddc78..fa03a69bd1c7 100644 --- a/go.sum +++ b/go.sum @@ -661,12 +661,12 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24= github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I= -github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= -github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform-exec v0.23.0 h1:MUiBM1s0CNlRFsCLJuM5wXZrzA3MnPYEsiXmzATMW/I= -github.com/hashicorp/terraform-exec v0.23.0/go.mod h1:mA+qnx1R8eePycfwKkCRk3Wy65mwInvlpAeOwmA7vlY= +github.com/hashicorp/terraform-exec v0.23.1-0.20250717072919-061a850a52d2 h1:90fcAqw0Qmv4vY7zL4jEKgKarHmOnNN6SjTY68eLKGA= +github.com/hashicorp/terraform-exec v0.23.1-0.20250717072919-061a850a52d2/go.mod h1:8D3RLLpzAZdhT9jvALYz1KHyGU4OvI73I1o0+01QJxA= github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU= github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= github.com/hashicorp/terraform-plugin-framework v1.16.0 h1:tP0f+yJg0Z672e7levixDe5EpWwrTrNryPM9kDMYIpE= @@ -685,8 +685,8 @@ github.com/hashicorp/terraform-plugin-mux v0.21.0 h1:QsEYnzSD2c3zT8zUrUGqaFGhV/Z github.com/hashicorp/terraform-plugin-mux v0.21.0/go.mod h1:Qpt8+6AD7NmL0DS7ASkN0EXpDQ2J/FnnIgeUr1tzr5A= github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsore1ZaRWU9cnB6jFoBnIM= github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA= -github.com/hashicorp/terraform-plugin-testing v1.13.3 h1:QLi/khB8Z0a5L54AfPrHukFpnwsGL8cwwswj4RZduCo= -github.com/hashicorp/terraform-plugin-testing v1.13.3/go.mod h1:WHQ9FDdiLoneey2/QHpGM/6SAYf4A7AZazVg7230pLE= +github.com/hashicorp/terraform-plugin-testing v1.13.3-0.20250909115916-1a2eeae85247 h1:lA6ofPwmCXAX7J7kVP9t/WMU5+eA4e9YvJUiRLPdENw= +github.com/hashicorp/terraform-plugin-testing v1.13.3-0.20250909115916-1a2eeae85247/go.mod h1:4r/7cxl1mpskfALcq58Iyu5aPiTSco8SVrKkcLyP5g4= github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk= github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -790,8 +790,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.16.4 h1:QGXaag7/7dCzb+odlGrgr+YmYZFaOCMW6DEpS+UD1eE= -github.com/zclconf/go-cty v1.16.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0= +github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= diff --git a/internal/service/sns/publish_action.go b/internal/service/sns/publish_action.go new file mode 100644 index 000000000000..811b0ef47baf --- /dev/null +++ b/internal/service/sns/publish_action.go @@ -0,0 +1,155 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package sns + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sns" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/action" + "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @Action(aws_sns_publish, name="Publish") +func newPublishAction(_ context.Context) (action.ActionWithConfigure, error) { + return &publishAction{}, nil +} + +var ( + _ action.Action = (*publishAction)(nil) +) + +type publishAction struct { + framework.ActionWithModel[publishActionModel] +} + +type publishActionModel struct { + framework.WithRegionModel + TopicArn types.String `tfsdk:"topic_arn"` + Message types.String `tfsdk:"message"` + Subject types.String `tfsdk:"subject"` + MessageStructure types.String `tfsdk:"message_structure"` + MessageAttributes fwtypes.ListNestedObjectValueOf[messageAttributeModel] `tfsdk:"message_attributes"` +} + +type messageAttributeModel struct { + MapBlockKey types.String `tfsdk:"map_block_key"` + DataType types.String `tfsdk:"data_type"` + StringValue types.String `tfsdk:"string_value"` +} + +func (a *publishAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Publishes a message to an Amazon SNS topic. This action allows for imperative message publishing with full control over message attributes and structure.", + Attributes: map[string]schema.Attribute{ + names.AttrMessage: schema.StringAttribute{ + Description: "The message to publish. For JSON message structure, this should be a JSON object with protocol-specific messages.", + Required: true, + }, + names.AttrTopicARN: schema.StringAttribute{ + Description: "The ARN of the SNS topic to publish the message to.", + Required: true, + }, + "message_structure": schema.StringAttribute{ + Description: "Set to 'json' if you want to send different messages for each protocol. If not specified, the message will be sent as-is to all protocols.", + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf(names.AttrJSON), + }, + }, + "subject": schema.StringAttribute{ + Description: "Optional subject for the message. Only used for email and email-json protocols.", + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "message_attributes": schema.ListNestedBlock{ + Description: "Message attributes to include with the message. Each block represents one attribute where map_block_key becomes the attribute name.", + CustomType: fwtypes.NewListNestedObjectTypeOf[messageAttributeModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ // nosemgrep:ci.semgrep.framework.map_block_key-meaningful-names + "data_type": schema.StringAttribute{ + Description: "The data type of the message attribute. Valid values are String, Number, and Binary.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("String", "Number", "Binary"), + }, + }, + "map_block_key": schema.StringAttribute{ + Description: "The name of the message attribute (used as map key).", + Required: true, + }, + "string_value": schema.StringAttribute{ + Description: "The value of the message attribute.", + Required: true, + }, + }, + }, + }, + }, + } +} + +func (a *publishAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) { + var config publishActionModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + conn := a.Meta().SNSClient(ctx) + + topicArn := config.TopicArn.ValueString() + message := config.Message.ValueString() + + tflog.Info(ctx, "Starting SNS publish message action", map[string]any{ + names.AttrTopicARN: topicArn, + "message_length": len(message), + "has_subject": !config.Subject.IsNull(), + }) + + resp.SendProgress(action.InvokeProgressEvent{ + Message: fmt.Sprintf("Publishing message to SNS topic %s...", topicArn), + }) + + input := &sns.PublishInput{} + resp.Diagnostics.Append(fwflex.Expand(ctx, config, input)...) + if resp.Diagnostics.HasError() { + return + } + + // Ensure required fields are set (AutoFlex should handle these, but being explicit) + input.TopicArn = aws.String(topicArn) + input.Message = aws.String(message) + + output, err := conn.Publish(ctx, input) + if err != nil { + resp.Diagnostics.AddError( + "Failed to Publish SNS Message", + fmt.Sprintf("Could not publish message to SNS topic %s: %s", topicArn, err), + ) + return + } + + messageId := aws.ToString(output.MessageId) + resp.SendProgress(action.InvokeProgressEvent{ + Message: fmt.Sprintf("Message published successfully to SNS topic %s (Message ID: %s)", topicArn, messageId), + }) + + tflog.Info(ctx, "SNS publish message action completed successfully", map[string]any{ + names.AttrTopicARN: topicArn, + "message_id": messageId, + }) +} diff --git a/internal/service/sns/publish_action_test.go b/internal/service/sns/publish_action_test.go new file mode 100644 index 000000000000..7eeda1afc8d3 --- /dev/null +++ b/internal/service/sns/publish_action_test.go @@ -0,0 +1,245 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package sns_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/service/sqs" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccSNSPublishAction_basic(t *testing.T) { + ctx := acctest.Context(t) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, "sns"), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccPublishActionConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPublishActionDelivered(ctx, "aws_sqs_queue.test"), + ), + }, + }, + }) +} + +func TestAccSNSPublishAction_withAttributes(t *testing.T) { + ctx := acctest.Context(t) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, "sns"), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccPublishActionConfig_withAttributes(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPublishActionDelivered(ctx, "aws_sqs_queue.test"), + ), + }, + }, + }) +} + +func testAccCheckPublishActionDelivered(ctx context.Context, queueResourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[queueResourceName] + if !ok { + return fmt.Errorf("Not found: %s", queueResourceName) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SQSClient(ctx) + queueURL := rs.Primary.Attributes[names.AttrURL] + + // Poll for message with timeout - adjusted for 20min test + timeout := time.After(18 * time.Minute) // Leave 2min buffer for test cleanup + ticker := time.NewTicker(5 * time.Second) // Poll every 5 seconds + debugTicker := time.NewTicker(30 * time.Second) // Debug output every 30 seconds + defer ticker.Stop() + defer debugTicker.Stop() + + pollCount := 0 + startTime := time.Now() + + for { + select { + case <-timeout: + elapsed := time.Since(startTime) + return fmt.Errorf("timeout after %v waiting for SNS message in SQS queue (polled %d times)", elapsed, pollCount) + + case <-ticker.C: + pollCount++ + output, err := conn.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{ + QueueUrl: &queueURL, + MaxNumberOfMessages: 1, + WaitTimeSeconds: 3, // Short poll with wait + }) + if err != nil { + continue + } + + if len(output.Messages) > 0 { + // Clean up message + _, err := conn.DeleteMessage(ctx, &sqs.DeleteMessageInput{ + QueueUrl: &queueURL, + ReceiptHandle: output.Messages[0].ReceiptHandle, + }) + if err != nil { + return fmt.Errorf("error deleting message from SQS: %w", err) + } + return nil + } + } + } + } +} + +func testAccPublishActionConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_sns_topic" "test" { + name = %[1]q +} + +resource "aws_sqs_queue" "test" { + name = %[1]q +} + +resource "aws_sns_topic_subscription" "test" { + topic_arn = aws_sns_topic.test.arn + protocol = "sqs" + endpoint = aws_sqs_queue.test.arn +} + +resource "aws_sqs_queue_policy" "test" { + queue_url = aws_sqs_queue.test.id + policy = jsonencode({ + Version = "2008-10-17" + Statement = [{ + Effect = "Allow" + Principal = "*" + Action = "sqs:SendMessage" + Resource = aws_sqs_queue.test.arn + Condition = { + ArnEquals = { + "aws:SourceArn" = aws_sns_topic.test.arn + } + } + }] + }) +} + +action "aws_sns_publish" "test" { + config { + topic_arn = aws_sns_topic.test.arn + message = "Test message from Terraform" + } +} + +resource "terraform_data" "trigger" { + lifecycle { + action_trigger { + events = [after_create] + actions = [action.aws_sns_publish.test] + } + } + + depends_on = [ + aws_sns_topic_subscription.test, + aws_sqs_queue_policy.test + ] +} +`, rName) +} + +func testAccPublishActionConfig_withAttributes(rName string) string { + return fmt.Sprintf(` +resource "aws_sns_topic" "test" { + name = %[1]q +} + +resource "aws_sqs_queue" "test" { + name = %[1]q +} + +resource "aws_sns_topic_subscription" "test" { + topic_arn = aws_sns_topic.test.arn + protocol = "sqs" + endpoint = aws_sqs_queue.test.arn +} + +resource "aws_sqs_queue_policy" "test" { + queue_url = aws_sqs_queue.test.id + policy = jsonencode({ + Version = "2008-10-17" + Statement = [{ + Effect = "Allow" + Principal = "*" + Action = "sqs:SendMessage" + Resource = aws_sqs_queue.test.arn + Condition = { + ArnEquals = { + "aws:SourceArn" = aws_sns_topic.test.arn + } + } + }] + }) +} + +action "aws_sns_publish" "test" { + config { + topic_arn = aws_sns_topic.test.arn + subject = "Test Subject" + message = "Test message with attributes" + + message_attributes { + map_block_key = "priority" + data_type = "String" + string_value = "high" + } + + message_attributes { + map_block_key = "source" + data_type = "String" + string_value = "terraform" + } + } +} + +resource "terraform_data" "trigger" { + lifecycle { + action_trigger { + events = [after_create] + actions = [action.aws_sns_publish.test] + } + } + + depends_on = [ + aws_sns_topic_subscription.test, + aws_sqs_queue_policy.test + ] +} +`, rName) +} diff --git a/internal/service/sns/service_package_gen.go b/internal/service/sns/service_package_gen.go index 5d0ac06a75a2..c11ebeb455d8 100644 --- a/internal/service/sns/service_package_gen.go +++ b/internal/service/sns/service_package_gen.go @@ -17,6 +17,17 @@ import ( type servicePackage struct{} +func (p *servicePackage) Actions(ctx context.Context) []*inttypes.ServicePackageAction { + return []*inttypes.ServicePackageAction{ + { + Factory: newPublishAction, + TypeName: "aws_sns_publish", + Name: "Publish", + Region: unique.Make(inttypes.ResourceRegionDefault()), + }, + } +} + func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*inttypes.ServicePackageFrameworkDataSource { return []*inttypes.ServicePackageFrameworkDataSource{} } diff --git a/website/docs/actions/sns_publish.html.markdown b/website/docs/actions/sns_publish.html.markdown new file mode 100644 index 000000000000..dfc2206e68b2 --- /dev/null +++ b/website/docs/actions/sns_publish.html.markdown @@ -0,0 +1,150 @@ +--- +subcategory: "SNS (Simple Notification)" +layout: "aws" +page_title: "AWS: aws_sns_publish" +description: |- + Publishes a message to an Amazon SNS topic. +--- + +# Action: aws_sns_publish + +~> **Note:** `aws_sns_publish` is in beta. Its interface and behavior may change as the feature evolves, and breaking changes are possible. It is offered as a technical preview without compatibility guarantees until Terraform 1.14 is generally available. + +Publishes a message to an Amazon SNS topic. This action allows for imperative message publishing with full control over message attributes and structure. + +For information about Amazon SNS, see the [Amazon SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/). For specific information about publishing messages, see the [Publish](https://docs.aws.amazon.com/sns/latest/api/API_Publish.html) page in the Amazon SNS API Reference. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_sns_topic" "example" { + name = "example-topic" +} + +action "aws_sns_publish" "example" { + config { + topic_arn = aws_sns_topic.example.arn + message = "Hello from Terraform!" + } +} + +resource "terraform_data" "example" { + input = "trigger-message" + + lifecycle { + action_trigger { + events = [before_create, before_update] + actions = [action.aws_sns_publish.example] + } + } +} +``` + +### Message with Subject + +```terraform +action "aws_sns_publish" "notification" { + config { + topic_arn = aws_sns_topic.alerts.arn + subject = "System Alert" + message = "Critical system event detected at ${timestamp()}" + } +} +``` + +### JSON Message Structure + +```terraform +action "aws_sns_publish" "structured" { + config { + topic_arn = aws_sns_topic.mobile.arn + message_structure = "json" + message = jsonencode({ + default = "Default message" + email = "Email version of the message" + sms = "SMS version" + GCM = jsonencode({ + data = { + message = "Push notification message" + } + }) + }) + } +} +``` + +### Message with Attributes + +```terraform +action "aws_sns_publish" "with_attributes" { + config { + topic_arn = aws_sns_topic.processing.arn + message = "Process this data" + + message_attributes { + map_block_key = "priority" + data_type = "String" + string_value = "high" + } + + message_attributes { + map_block_key = "source" + data_type = "String" + string_value = "terraform" + } + } +} +``` + +### Deployment Notification + +```terraform +action "aws_sns_publish" "deploy_complete" { + config { + topic_arn = aws_sns_topic.deployments.arn + subject = "Deployment Complete" + message = jsonencode({ + environment = var.environment + version = var.app_version + timestamp = timestamp() + resources = { + instances = length(aws_instance.app) + databases = length(aws_db_instance.main) + } + }) + } +} + +resource "terraform_data" "deploy_trigger" { + input = var.deployment_id + + lifecycle { + action_trigger { + events = [before_create, before_update] + actions = [action.aws_sns_publish.deploy_complete] + } + } + + depends_on = [aws_instance.app, aws_db_instance.main] +} +``` + +## Argument Reference + +This action supports the following arguments: + +* `message` - (Required) Message to publish. For JSON message structure, this should be a JSON object with protocol-specific messages. Maximum size is 256 KB. +* `message_attributes` - (Optional) Message attributes to include with the message. Each attribute consists of a name, data type, and value. Up to 10 attributes are allowed. [See below.](#message-attributes) +* `message_structure` - (Optional) Set to `json` if you want to send different messages for each protocol. If not specified, the message will be sent as-is to all protocols. +* `subject` - (Optional) Optional subject for the message. Only used for email and email-json protocols. Maximum length is 100 characters. +* `topic_arn` - (Required) ARN of the SNS topic to publish the message to. + +### Message Attributes + +The `message_attributes` block supports: + +* `data_type` - (Required) Data type of the message attribute. Valid values are `String`, `Number`, and `Binary`. +* `map_block_key` - (Required) Name of the message attribute (used as map key). Must be unique within the message. +* `string_value` - (Required) Value of the message attribute.