diff --git a/.changelog/16087.txt b/.changelog/16087.txt new file mode 100644 index 000000000000..999db16791eb --- /dev/null +++ b/.changelog/16087.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_iot_topic_rule: Add `http` and `error_action.http` arguments +``` \ No newline at end of file diff --git a/.changelog/19175.txt b/.changelog/19175.txt new file mode 100644 index 000000000000..01b745b8141b --- /dev/null +++ b/.changelog/19175.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_iot_topic_rule: Add `s3.canned_acl` and `error_action.s3.canned_acl` arguments +``` \ No newline at end of file diff --git a/.changelog/22337.txt b/.changelog/22337.txt new file mode 100644 index 000000000000..375ae5a0304b --- /dev/null +++ b/.changelog/22337.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_iot_topic_rule: Add `timestream` and `error_action.timestream` arguments +``` \ No newline at end of file diff --git a/.changelog/24395.txt b/.changelog/24395.txt new file mode 100644 index 000000000000..53e26f57ab39 --- /dev/null +++ b/.changelog/24395.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_iot_topic_rule: Add `kafka` and `error_action.kafka` arguments +``` + +```release-note:new-resource +aws_iot_topic_rule_destination +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 42c9e97e4998..06d4094666c1 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1530,6 +1530,7 @@ func Provider() *schema.Provider { "aws_iot_thing_principal_attachment": iot.ResourceThingPrincipalAttachment(), "aws_iot_thing_type": iot.ResourceThingType(), "aws_iot_topic_rule": iot.ResourceTopicRule(), + "aws_iot_topic_rule_destination": iot.ResourceTopicRuleDestination(), "aws_msk_cluster": kafka.ResourceCluster(), "aws_msk_configuration": kafka.ResourceConfiguration(), diff --git a/internal/service/ec2/sweep.go b/internal/service/ec2/sweep.go index e7883e8f60a4..3cfdd29981d4 100644 --- a/internal/service/ec2/sweep.go +++ b/internal/service/ec2/sweep.go @@ -208,6 +208,7 @@ func init() { "aws_fsx_ontap_file_system", "aws_fsx_openzfs_file_system", "aws_fsx_windows_file_system", + "aws_iot_topic_rule_destination", "aws_lambda_function", "aws_lb", "aws_memorydb_subnet_group", diff --git a/internal/service/iot/find.go b/internal/service/iot/find.go index 3920659acbe3..0def8f4c2442 100644 --- a/internal/service/iot/find.go +++ b/internal/service/iot/find.go @@ -1,6 +1,8 @@ package iot import ( + "context" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iot" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" @@ -119,3 +121,105 @@ func FindThingGroupMembership(conn *iot.IoT, thingGroupName, thingName string) e return nil } + +func FindTopicRuleByName(conn *iot.IoT, name string) (*iot.GetTopicRuleOutput, error) { + // GetTopicRule returns unhelpful errors such as + // "An error occurred (UnauthorizedException) when calling the GetTopicRule operation: Access to topic rule 'xxxxxxxx' was denied" + // when querying for a rule that doesn't exist. + var rule *iot.TopicRuleListItem + + err := conn.ListTopicRulesPages(&iot.ListTopicRulesInput{}, func(page *iot.ListTopicRulesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Rules { + if v == nil { + continue + } + + if aws.StringValue(v.RuleName) == name { + rule = v + + return false + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + if rule == nil { + return nil, tfresource.NewEmptyResultError(name) + } + + input := &iot.GetTopicRuleInput{ + RuleName: aws.String(name), + } + + output, err := conn.GetTopicRule(input) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func FindTopicRuleDestinationByARN(ctx context.Context, conn *iot.IoT, arn string) (*iot.TopicRuleDestination, error) { + // GetTopicRuleDestination returns unhelpful errors such as + // "UnauthorizedException: Access to TopicRuleDestination 'arn:aws:iot:us-west-2:123456789012:ruledestination/vpc/f267138a-7383-4670-9e44-a7fe2f48af5e' was denied" + // when querying for a rule destination that doesn't exist. + var destination *iot.TopicRuleDestinationSummary + + err := conn.ListTopicRuleDestinationsPages(&iot.ListTopicRuleDestinationsInput{}, func(page *iot.ListTopicRuleDestinationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.DestinationSummaries { + if v == nil { + continue + } + + if aws.StringValue(v.Arn) == arn { + destination = v + + return false + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + if destination == nil { + return nil, tfresource.NewEmptyResultError(destination) + } + + input := &iot.GetTopicRuleDestinationInput{ + Arn: aws.String(arn), + } + + output, err := conn.GetTopicRuleDestinationWithContext(ctx, input) + + if err != nil { + return nil, err + } + + if output == nil || output.TopicRuleDestination == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.TopicRuleDestination, nil +} diff --git a/internal/service/iot/sweep.go b/internal/service/iot/sweep.go index 42336aaee459..bda0ec3434c5 100644 --- a/internal/service/iot/sweep.go +++ b/internal/service/iot/sweep.go @@ -67,8 +67,14 @@ func init() { }) resource.AddTestSweepers("aws_iot_topic_rule", &resource.Sweeper{ - Name: "aws_iot_topic_rule", - F: sweepTopicRules, + Name: "aws_iot_topic_rule", + F: sweepTopicRules, + Dependencies: []string{"aws_iot_topic_rule_destination"}, + }) + + resource.AddTestSweepers("aws_iot_topic_rule_destination", &resource.Sweeper{ + Name: "aws_iot_topic_rule_destination", + F: sweepTopicRuleDestinations, }) } @@ -523,3 +529,46 @@ func sweepThingGroups(region string) error { return nil } + +func sweepTopicRuleDestinations(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.(*conns.AWSClient).IoTConn + input := &iot.ListTopicRuleDestinationsInput{} + sweepResources := make([]*sweep.SweepResource, 0) + + err = conn.ListTopicRuleDestinationsPages(input, func(page *iot.ListTopicRuleDestinationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.DestinationSummaries { + r := ResourceTopicRuleDestination() + d := r.Data(nil) + d.SetId(aws.StringValue(v.Arn)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping IoT Topic Rule Destination sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing IoT Topic Rule Destinations (%s): %w", region, err) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping IoT Topic Rule Destinations (%s): %w", region, err) + } + + return nil +} diff --git a/internal/service/iot/topic_rule.go b/internal/service/iot/topic_rule.go index 78cfce7af8e0..405d7e4c99e7 100644 --- a/internal/service/iot/topic_rule.go +++ b/internal/service/iot/topic_rule.go @@ -2,14 +2,14 @@ package iot import ( "fmt" + "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iot" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/flex" tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -227,225 +227,6 @@ func ResourceTopicRule() *schema.Resource { Type: schema.TypeBool, Required: true, }, - "firehose": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "delivery_stream_name": { - Type: schema.TypeString, - Required: true, - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - "separator": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validTopicRuleFirehoseSeparator, - }, - }, - }, - }, - "iot_analytics": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "channel_name": { - Type: schema.TypeString, - Required: true, - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - }, - }, - }, - "iot_events": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "input_name": { - Type: schema.TypeString, - Required: true, - }, - "message_id": { - Type: schema.TypeString, - Optional: true, - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - }, - }, - }, - "kinesis": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "partition_key": { - Type: schema.TypeString, - Optional: true, - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - "stream_name": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - "lambda": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "function_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - }, - }, - }, - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validTopicRuleName, - }, - "republish": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "qos": { - Type: schema.TypeInt, - Optional: true, - Default: 0, - ValidateFunc: validation.IntBetween(0, 1), - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - "topic": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - "s3": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "bucket_name": { - Type: schema.TypeString, - Required: true, - }, - "key": { - Type: schema.TypeString, - Required: true, - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - }, - }, - }, - "step_functions": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "execution_name_prefix": { - Type: schema.TypeString, - Optional: true, - }, - "state_machine_name": { - Type: schema.TypeString, - Required: true, - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - }, - }, - }, - "sns": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "message_format": { - Type: schema.TypeString, - Default: iot.MessageFormatRaw, - Optional: true, - }, - "target_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - }, - }, - }, - "sql": { - Type: schema.TypeString, - Required: true, - }, - "sql_version": { - Type: schema.TypeString, - Required: true, - }, - "sqs": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "queue_url": { - Type: schema.TypeString, - Required: true, - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - "use_base64": { - Type: schema.TypeBool, - Required: true, - }, - }, - }, - }, - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), "error_action": { Type: schema.TypeList, Optional: true, @@ -478,24 +259,7 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "cloudwatch_logs": { Type: schema.TypeList, @@ -514,24 +278,7 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "cloudwatch_metric": { Type: schema.TypeList, @@ -567,24 +314,7 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "dynamodb": { Type: schema.TypeList, @@ -640,24 +370,7 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "dynamodbv2": { Type: schema.TypeList, @@ -685,24 +398,7 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "elasticsearch": { Type: schema.TypeList, @@ -734,24 +430,7 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "firehose": { Type: schema.TypeList, @@ -775,24 +454,43 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, + }, + "http": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "confirmation_url": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsURLWithHTTPS, + }, + "http_header": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "url": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsURLWithHTTPS, + }, + }, }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "iot_analytics": { Type: schema.TypeList, @@ -811,24 +509,7 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "iot_events": { Type: schema.TypeList, @@ -851,33 +532,48 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, - "kinesis": { + "kafka": { Type: schema.TypeList, Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "partition_key": { - Type: schema.TypeString, + "client_properties": { + Type: schema.TypeMap, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "destination_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "key": { + Type: schema.TypeString, + Optional: true, + }, + "partition": { + Type: schema.TypeString, + Optional: true, + }, + "topic": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, + }, + "kinesis": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "partition_key": { + Type: schema.TypeString, Optional: true, }, "role_arn": { @@ -891,24 +587,7 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "lambda": { Type: schema.TypeList, @@ -923,24 +602,7 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "republish": { Type: schema.TypeList, @@ -965,24 +627,7 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, }, "s3": { Type: schema.TypeList, @@ -994,6 +639,11 @@ func ResourceTopicRule() *schema.Resource { Type: schema.TypeString, Required: true, }, + "canned_acl": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(iot.CannedAccessControlList_Values(), false), + }, "key": { Type: schema.TypeString, Required: true, @@ -1005,146 +655,466 @@ func ResourceTopicRule() *schema.Resource { }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, + }, + "sns": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "message_format": { + Type: schema.TypeString, + Default: iot.MessageFormatRaw, + Optional: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "target_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, + }, + "sqs": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "queue_url": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "use_base64": { + Type: schema.TypeBool, + Required: true, + }, + }, + }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, + }, + "step_functions": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "execution_name_prefix": { + Type: schema.TypeString, + Optional: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "state_machine_name": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, + }, + "timestream": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database_name": { + Type: schema.TypeString, + Required: true, + }, + "dimension": { + Type: schema.TypeSet, + Required: true, + Elem: timestreamDimensionResource, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "table_name": { + Type: schema.TypeString, + Required: true, + }, + "timestamp": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "unit": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "SECONDS", + "MILLISECONDS", + "MICROSECONDS", + "NANOSECONDS", + }, false), + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + ExactlyOneOf: topicRuleErrorActionExactlyOneOf, + }, + }, + }, + }, + "firehose": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "delivery_stream_name": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "separator": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validTopicRuleFirehoseSeparator, + }, + }, + }, + }, + "http": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "confirmation_url": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsURLWithHTTPS, + }, + "http_header": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, }, }, - "step_functions": { - Type: schema.TypeList, + "url": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsURLWithHTTPS, + }, + }, + }, + }, + "iot_analytics": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "channel_name": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "iot_events": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "input_name": { + Type: schema.TypeString, + Required: true, + }, + "message_id": { + Type: schema.TypeString, + Optional: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "kafka": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_properties": { + Type: schema.TypeMap, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "destination_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "key": { + Type: schema.TypeString, + Optional: true, + }, + "partition": { + Type: schema.TypeString, + Optional: true, + }, + "topic": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "kinesis": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "partition_key": { + Type: schema.TypeString, + Optional: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "stream_name": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "lambda": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "function_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validTopicRuleName, + }, + "republish": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "qos": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.IntBetween(0, 1), + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "topic": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "s3": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bucket_name": { + Type: schema.TypeString, + Required: true, + }, + "canned_acl": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(iot.CannedAccessControlList_Values(), false), + }, + "key": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "sns": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "message_format": { + Type: schema.TypeString, + Optional: true, + Default: iot.MessageFormatRaw, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "target_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "sql": { + Type: schema.TypeString, + Required: true, + }, + "sql_version": { + Type: schema.TypeString, + Required: true, + }, + "sqs": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "queue_url": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "use_base64": { + Type: schema.TypeBool, + Required: true, + }, + }, + }, + }, + "step_functions": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "execution_name_prefix": { + Type: schema.TypeString, Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "execution_name_prefix": { - Type: schema.TypeString, - Optional: true, - }, - "state_machine_name": { - Type: schema.TypeString, - Required: true, - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - }, - }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, }, - "sns": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "message_format": { - Type: schema.TypeString, - Default: iot.MessageFormatRaw, - Optional: true, - }, - "target_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - }, - }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, }, - "sqs": { + "state_machine_name": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "timestream": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database_name": { + Type: schema.TypeString, + Required: true, + }, + "dimension": { + Type: schema.TypeSet, + Required: true, + Elem: timestreamDimensionResource, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "table_name": { + Type: schema.TypeString, + Required: true, + }, + "timestamp": { Type: schema.TypeList, Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "queue_url": { + "unit": { Type: schema.TypeString, Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "SECONDS", + "MILLISECONDS", + "MICROSECONDS", + "NANOSECONDS", + }, false), }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, - }, - "use_base64": { - Type: schema.TypeBool, + "value": { + Type: schema.TypeString, Required: true, }, }, }, - ExactlyOneOf: []string{ - "error_action.0.cloudwatch_alarm", - "error_action.0.cloudwatch_logs", - "error_action.0.cloudwatch_metric", - "error_action.0.dynamodb", - "error_action.0.dynamodbv2", - "error_action.0.elasticsearch", - "error_action.0.firehose", - "error_action.0.iot_analytics", - "error_action.0.iot_events", - "error_action.0.kinesis", - "error_action.0.lambda", - "error_action.0.republish", - "error_action.0.s3", - "error_action.0.step_functions", - "error_action.0.sns", - "error_action.0.sqs", - }, }, }, }, @@ -1155,44 +1125,62 @@ func ResourceTopicRule() *schema.Resource { } } +var topicRuleErrorActionExactlyOneOf = []string{ + "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", + "error_action.0.cloudwatch_metric", + "error_action.0.dynamodb", + "error_action.0.dynamodbv2", + "error_action.0.elasticsearch", + "error_action.0.firehose", + "error_action.0.http", + "error_action.0.iot_analytics", + "error_action.0.iot_events", + "error_action.0.kafka", + "error_action.0.kinesis", + "error_action.0.lambda", + "error_action.0.republish", + "error_action.0.s3", + "error_action.0.sns", + "error_action.0.sqs", + "error_action.0.step_functions", + "error_action.0.timestream", +} + +var timestreamDimensionResource *schema.Resource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, +} + func resourceTopicRuleCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).IoTConn defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) ruleName := d.Get("name").(string) - input := &iot.CreateTopicRuleInput{ RuleName: aws.String(ruleName), Tags: aws.String(tags.IgnoreAWS().UrlQueryString()), TopicRulePayload: expandIotTopicRulePayload(d), } - err := resource.Retry(tfiam.PropagationTimeout, func() *resource.RetryError { - var err error - _, err = conn.CreateTopicRule(input) - - if tfawserr.ErrMessageContains(err, iot.ErrCodeInvalidRequestException, "unable to perform: sts:AssumeRole on resource") { - return resource.RetryableError(err) - } - - if tfawserr.ErrMessageContains(err, iot.ErrCodeInvalidRequestException, "unable to assume role (sts:AssumeRole) on resource") { - return resource.RetryableError(err) - } - - if err != nil { - return resource.NonRetryableError(err) - } - - return nil - }) - - if tfresource.TimedOut(err) { - _, err = conn.CreateTopicRule(input) - } + log.Printf("[INFO] Creating IoT Topic Rule: %s", input) + _, err := tfresource.RetryWhenAWSErrMessageContains(tfiam.PropagationTimeout, + func() (interface{}, error) { + return conn.CreateTopicRule(input) + }, + iot.ErrCodeInvalidRequestException, "sts:AssumeRole") if err != nil { - return fmt.Errorf("error creating IoT Topic Rule (%s): %w", ruleName, err) + return fmt.Errorf("creating IoT Topic Rule (%s): %w", ruleName, err) } d.SetId(ruleName) @@ -1205,106 +1193,120 @@ func resourceTopicRuleRead(d *schema.ResourceData, meta interface{}) error { defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - input := &iot.GetTopicRuleInput{ - RuleName: aws.String(d.Id()), - } + output, err := FindTopicRuleByName(conn, d.Id()) - out, err := conn.GetTopicRule(input) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] IoT Topic Rule %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } if err != nil { - return fmt.Errorf("error getting IoT Topic Rule (%s): %w", d.Id(), err) + return fmt.Errorf("reading IoT Topic Rule (%s): %w", d.Id(), err) } - d.Set("arn", out.RuleArn) - d.Set("name", out.Rule.RuleName) - d.Set("description", out.Rule.Description) - d.Set("enabled", !aws.BoolValue(out.Rule.RuleDisabled)) - d.Set("sql", out.Rule.Sql) - d.Set("sql_version", out.Rule.AwsIotSqlVersion) + d.Set("arn", output.RuleArn) + d.Set("name", output.Rule.RuleName) + d.Set("description", output.Rule.Description) + d.Set("enabled", !aws.BoolValue(output.Rule.RuleDisabled)) + d.Set("sql", output.Rule.Sql) + d.Set("sql_version", output.Rule.AwsIotSqlVersion) - tags, err := ListTags(conn, aws.StringValue(out.RuleArn)) + if err := d.Set("cloudwatch_alarm", flattenIotCloudWatchAlarmActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting cloudwatch_alarm: %w", err) + } - if err != nil { - return fmt.Errorf("error listing tags for IoT Topic Rule (%s): %w", aws.StringValue(out.RuleArn), err) + if err := d.Set("cloudwatch_logs", flattenIotCloudWatchLogsActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting cloudwatch_logs: %w", err) } - tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + if err := d.Set("cloudwatch_metric", flattenIotCloudwatchMetricActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting cloudwatch_metric: %w", err) + } - //lintignore:AWSR002 - if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) + if err := d.Set("dynamodb", flattenIotDynamoDbActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting dynamodb: %w", err) } - if err := d.Set("tags_all", tags.Map()); err != nil { - return fmt.Errorf("error setting tags_all: %w", err) + if err := d.Set("dynamodbv2", flattenIotDynamoDbv2Actions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting dynamodbv2: %w", err) + } + + if err := d.Set("elasticsearch", flattenIotElasticsearchActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting elasticsearch: %w", err) } - if err := d.Set("cloudwatch_alarm", flattenIotCloudWatchAlarmActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting cloudwatch_alarm: %w", err) + if err := d.Set("firehose", flattenIotFirehoseActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting firehose: %w", err) } - if err := d.Set("cloudwatch_logs", flattenIotCloudWatchLogsActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting cloudwatch_logs: %w", err) + if err := d.Set("http", flattenIotHttpActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting http: %w", err) } - if err := d.Set("cloudwatch_metric", flattenIotCloudwatchMetricActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting cloudwatch_metric: %w", err) + if err := d.Set("iot_analytics", flattenIotIotAnalyticsActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting iot_analytics: %w", err) } - if err := d.Set("dynamodb", flattenIotDynamoDbActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting dynamodb: %w", err) + if err := d.Set("iot_events", flattenIotIotEventsActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting iot_events: %w", err) } - if err := d.Set("dynamodbv2", flattenIotDynamoDbv2Actions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting dynamodbv2: %w", err) + if err := d.Set("kafka", flattenIotKafkaActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting kafka: %w", err) } - if err := d.Set("elasticsearch", flattenIotElasticsearchActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting elasticsearch: %w", err) + if err := d.Set("kinesis", flattenIotKinesisActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting kinesis: %w", err) } - if err := d.Set("firehose", flattenIotFirehoseActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting firehose: %w", err) + if err := d.Set("lambda", flattenIotLambdaActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting lambda: %w", err) } - if err := d.Set("iot_analytics", flattenIotIotAnalyticsActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting iot_analytics: %w", err) + if err := d.Set("republish", flattenIotRepublishActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting republish: %w", err) } - if err := d.Set("iot_events", flattenIotIotEventsActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting iot_events: %w", err) + if err := d.Set("s3", flattenIotS3Actions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting s3: %w", err) } - if err := d.Set("kinesis", flattenIotKinesisActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting kinesis: %w", err) + if err := d.Set("sns", flattenIotSnsActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting sns: %w", err) } - if err := d.Set("lambda", flattenIotLambdaActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting lambda: %w", err) + if err := d.Set("sqs", flattenIotSqsActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting sqs: %w", err) } - if err := d.Set("republish", flattenIotRepublishActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting republish: %w", err) + if err := d.Set("step_functions", flattenIotStepFunctionsActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting step_functions: %w", err) } - if err := d.Set("s3", flattenIotS3Actions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting s3: %w", err) + if err := d.Set("timestream", flattenIotTimestreamActions(output.Rule.Actions)); err != nil { + return fmt.Errorf("setting timestream: %w", err) } - if err := d.Set("sns", flattenIotSnsActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting sns: %w", err) + if err := d.Set("error_action", flattenIotErrorAction(output.Rule.ErrorAction)); err != nil { + return fmt.Errorf("setting error_action: %w", err) } - if err := d.Set("sqs", flattenIotSqsActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting sqs: %w", err) + tags, err := ListTags(conn, aws.StringValue(output.RuleArn)) + + if err != nil { + return fmt.Errorf("listing tags for IoT Topic Rule (%s): %w", aws.StringValue(output.RuleArn), err) } - if err := d.Set("step_functions", flattenIotStepFunctionsActions(out.Rule.Actions)); err != nil { - return fmt.Errorf("error setting step_functions: %w", err) + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("setting tags: %w", err) } - if err := d.Set("error_action", flattenIotErrorAction(out.Rule.ErrorAction)); err != nil { - return fmt.Errorf("error setting error_action: %w", err) + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("setting tags_all: %w", err) } return nil @@ -1313,38 +1315,17 @@ func resourceTopicRuleRead(d *schema.ResourceData, meta interface{}) error { func resourceTopicRuleUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).IoTConn - if d.HasChanges( - "cloudwatch_alarm", - "cloudwatch_logs", - "cloudwatch_metric", - "description", - "dynamodb", - "dynamodbv2", - "elasticsearch", - "enabled", - "error_action", - "firehose", - "iot_analytics", - "iot_events", - "kinesis", - "lambda", - "republish", - "s3", - "step_functions", - "sns", - "sql", - "sql_version", - "sqs", - ) { + if d.HasChangesExcept("tags", "tags_all") { input := &iot.ReplaceTopicRuleInput{ RuleName: aws.String(d.Get("name").(string)), TopicRulePayload: expandIotTopicRulePayload(d), } + log.Printf("[INFO] Replacing IoT Topic Rule: %s", input) _, err := conn.ReplaceTopicRule(input) if err != nil { - return fmt.Errorf("error updating IoT Topic Rule (%s): %w", d.Id(), err) + return fmt.Errorf("replacing IoT Topic Rule (%s): %w", d.Id(), err) } } @@ -1352,7 +1333,7 @@ func resourceTopicRuleUpdate(d *schema.ResourceData, meta interface{}) error { o, n := d.GetChange("tags_all") if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating tags: %s", err) + return fmt.Errorf("updating tags: %w", err) } } @@ -1362,14 +1343,13 @@ func resourceTopicRuleUpdate(d *schema.ResourceData, meta interface{}) error { func resourceTopicRuleDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).IoTConn - input := &iot.DeleteTopicRuleInput{ + log.Printf("[INFO] Deleting IoT Topic Rule: %s", d.Id()) + _, err := conn.DeleteTopicRule(&iot.DeleteTopicRuleInput{ RuleName: aws.String(d.Id()), - } - - _, err := conn.DeleteTopicRule(input) + }) if err != nil { - return fmt.Errorf("error deleting IoT Topic Rule (%s): %w", d.Id(), err) + return fmt.Errorf("deleting IoT Topic Rule (%s): %w", d.Id(), err) } return nil @@ -1595,6 +1575,42 @@ func expandIotFirehoseAction(tfList []interface{}) *iot.FirehoseAction { return apiObject } +func expandIotHttpAction(tfList []interface{}) *iot.HttpAction { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + apiObject := &iot.HttpAction{} + tfMap := tfList[0].(map[string]interface{}) + + if v, ok := tfMap["url"].(string); ok && v != "" { + apiObject.Url = aws.String(v) + } + + if v, ok := tfMap["confirmation_url"].(string); ok && v != "" { + apiObject.ConfirmationUrl = aws.String(v) + } + + if v, ok := tfMap["http_header"].([]interface{}); ok { + headerObjs := []*iot.HttpActionHeader{} + for _, val := range v { + if m, ok := val.(map[string]interface{}); ok { + headerObj := &iot.HttpActionHeader{} + if v, ok := m["key"].(string); ok && v != "" { + headerObj.Key = aws.String(v) + } + if v, ok := m["value"].(string); ok && v != "" { + headerObj.Value = aws.String(v) + } + headerObjs = append(headerObjs, headerObj) + } + } + apiObject.Headers = headerObjs + } + + return apiObject +} + func expandIotIotAnalyticsAction(tfList []interface{}) *iot.IotAnalyticsAction { if len(tfList) == 0 || tfList[0] == nil { return nil @@ -1637,6 +1653,37 @@ func expandIotIotEventsAction(tfList []interface{}) *iot.IotEventsAction { return apiObject } +func expandIotKafkaAction(tfList []interface{}) *iot.KafkaAction { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + apiObject := &iot.KafkaAction{} + tfMap := tfList[0].(map[string]interface{}) + + if v, ok := tfMap["client_properties"].(map[string]interface{}); ok && len(v) > 0 { + apiObject.ClientProperties = flex.ExpandStringMap(v) + } + + if v, ok := tfMap["destination_arn"].(string); ok && v != "" { + apiObject.DestinationArn = aws.String(v) + } + + if v, ok := tfMap["key"].(string); ok && v != "" { + apiObject.Key = aws.String(v) + } + + if v, ok := tfMap["partition"].(string); ok && v != "" { + apiObject.Partition = aws.String(v) + } + + if v, ok := tfMap["topic"].(string); ok && v != "" { + apiObject.Topic = aws.String(v) + } + + return apiObject +} + func expandIotKinesisAction(tfList []interface{}) *iot.KinesisAction { if len(tfList) == 0 || tfList[0] == nil { return nil @@ -1710,6 +1757,10 @@ func expandIotS3Action(tfList []interface{}) *iot.S3Action { apiObject.BucketName = aws.String(v) } + if v, ok := tfMap["canned_acl"].(string); ok && v != "" { + apiObject.CannedAcl = aws.String(v) + } + if v, ok := tfMap["key"].(string); ok && v != "" { apiObject.Key = aws.String(v) } @@ -1749,42 +1800,117 @@ func expandIotSqsAction(tfList []interface{}) *iot.SqsAction { return nil } - apiObject := &iot.SqsAction{} - tfMap := tfList[0].(map[string]interface{}) + apiObject := &iot.SqsAction{} + tfMap := tfList[0].(map[string]interface{}) + + if v, ok := tfMap["queue_url"].(string); ok && v != "" { + apiObject.QueueUrl = aws.String(v) + } + + if v, ok := tfMap["role_arn"].(string); ok && v != "" { + apiObject.RoleArn = aws.String(v) + } + + if v, ok := tfMap["use_base64"].(bool); ok { + apiObject.UseBase64 = aws.Bool(v) + } + + return apiObject +} + +func expandIotStepFunctionsAction(tfList []interface{}) *iot.StepFunctionsAction { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + apiObject := &iot.StepFunctionsAction{} + tfMap := tfList[0].(map[string]interface{}) + + if v, ok := tfMap["execution_name_prefix"].(string); ok && v != "" { + apiObject.ExecutionNamePrefix = aws.String(v) + } + + if v, ok := tfMap["state_machine_name"].(string); ok && v != "" { + apiObject.StateMachineName = aws.String(v) + } + + if v, ok := tfMap["role_arn"].(string); ok && v != "" { + apiObject.RoleArn = aws.String(v) + } + + return apiObject +} + +func expandIotTimestreamAction(tfList []interface{}) *iot.TimestreamAction { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + apiObject := &iot.TimestreamAction{} + tfMap := tfList[0].(map[string]interface{}) + + if v, ok := tfMap["database_name"].(string); ok && v != "" { + apiObject.DatabaseName = aws.String(v) + } + + if v, ok := tfMap["dimension"].(*schema.Set); ok { + apiObject.Dimensions = expandIotTimestreamDimensions(v) + } + + if v, ok := tfMap["role_arn"].(string); ok && v != "" { + apiObject.RoleArn = aws.String(v) + } + + if v, ok := tfMap["table_name"].(string); ok && v != "" { + apiObject.TableName = aws.String(v) + } + + if v, ok := tfMap["timestamp"].([]interface{}); ok { + apiObject.Timestamp = expandIotTimestreamTimestamp(v) + } + + return apiObject +} + +func expandIotTimestreamDimensions(tfSet *schema.Set) []*iot.TimestreamDimension { + if tfSet == nil || tfSet.Len() == 0 { + return nil + } + + apiObjects := make([]*iot.TimestreamDimension, tfSet.Len()) + for i, elem := range tfSet.List() { + if tfMap, ok := elem.(map[string]interface{}); ok { + apiObject := &iot.TimestreamDimension{} - if v, ok := tfMap["queue_url"].(string); ok && v != "" { - apiObject.QueueUrl = aws.String(v) - } + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.Name = aws.String(v) + } - if v, ok := tfMap["role_arn"].(string); ok && v != "" { - apiObject.RoleArn = aws.String(v) - } + if v, ok := tfMap["value"].(string); ok && v != "" { + apiObject.Value = aws.String(v) + } - if v, ok := tfMap["use_base64"].(bool); ok { - apiObject.UseBase64 = aws.Bool(v) + apiObjects[i] = apiObject + } } - return apiObject + return apiObjects } -func expandIotStepFunctionsAction(tfList []interface{}) *iot.StepFunctionsAction { +func expandIotTimestreamTimestamp(tfList []interface{}) *iot.TimestreamTimestamp { if len(tfList) == 0 || tfList[0] == nil { return nil } - apiObject := &iot.StepFunctionsAction{} + apiObject := &iot.TimestreamTimestamp{} tfMap := tfList[0].(map[string]interface{}) - if v, ok := tfMap["execution_name_prefix"].(string); ok && v != "" { - apiObject.ExecutionNamePrefix = aws.String(v) - } - - if v, ok := tfMap["state_machine_name"].(string); ok && v != "" { - apiObject.StateMachineName = aws.String(v) + if v, ok := tfMap["unit"].(string); ok && v != "" { + apiObject.Unit = aws.String(v) } - if v, ok := tfMap["role_arn"].(string); ok && v != "" { - apiObject.RoleArn = aws.String(v) + if v, ok := tfMap["value"].(string); ok && v != "" { + apiObject.Value = aws.String(v) } return apiObject @@ -1870,6 +1996,17 @@ func expandIotTopicRulePayload(d *schema.ResourceData) *iot.TopicRulePayload { actions = append(actions, &iot.Action{Firehose: action}) } + // Legacy root attribute handling + for _, tfMapRaw := range d.Get("http").(*schema.Set).List() { + action := expandIotHttpAction([]interface{}{tfMapRaw}) + + if action == nil { + continue + } + + actions = append(actions, &iot.Action{Http: action}) + } + // Legacy root attribute handling for _, tfMapRaw := range d.Get("iot_analytics").(*schema.Set).List() { action := expandIotIotAnalyticsAction([]interface{}{tfMapRaw}) @@ -1892,6 +2029,17 @@ func expandIotTopicRulePayload(d *schema.ResourceData) *iot.TopicRulePayload { actions = append(actions, &iot.Action{IotEvents: action}) } + // Legacy root attribute handling + for _, tfMapRaw := range d.Get("kafka").(*schema.Set).List() { + action := expandIotKafkaAction([]interface{}{tfMapRaw}) + + if action == nil { + continue + } + + actions = append(actions, &iot.Action{Kafka: action}) + } + // Legacy root attribute handling for _, tfMapRaw := range d.Get("kinesis").(*schema.Set).List() { action := expandIotKinesisAction([]interface{}{tfMapRaw}) @@ -1969,6 +2117,17 @@ func expandIotTopicRulePayload(d *schema.ResourceData) *iot.TopicRulePayload { actions = append(actions, &iot.Action{StepFunctions: action}) } + // Legacy root attribute handling + for _, tfMapRaw := range d.Get("timestream").(*schema.Set).List() { + action := expandIotTimestreamAction([]interface{}{tfMapRaw}) + + if action == nil { + continue + } + + actions = append(actions, &iot.Action{Timestream: action}) + } + // Prevent sending empty Actions: // - missing required field, CreateTopicRuleInput.TopicRulePayload.Actions if len(actions) == 0 { @@ -2050,6 +2209,16 @@ func expandIotTopicRulePayload(d *schema.ResourceData) *iot.TopicRulePayload { iotErrorAction = &iot.Action{Firehose: action} } + case "http": + for _, tfMapRaw := range v.([]interface{}) { + action := expandIotHttpAction([]interface{}{tfMapRaw}) + + if action == nil { + continue + } + + iotErrorAction = &iot.Action{Http: action} + } case "iot_analytics": for _, tfMapRaw := range v.([]interface{}) { action := expandIotIotAnalyticsAction([]interface{}{tfMapRaw}) @@ -2070,6 +2239,16 @@ func expandIotTopicRulePayload(d *schema.ResourceData) *iot.TopicRulePayload { iotErrorAction = &iot.Action{IotEvents: action} } + case "kafka": + for _, tfMapRaw := range v.([]interface{}) { + action := expandIotKafkaAction([]interface{}{tfMapRaw}) + + if action == nil { + continue + } + + iotErrorAction = &iot.Action{Kafka: action} + } case "kinesis": for _, tfMapRaw := range v.([]interface{}) { action := expandIotKinesisAction([]interface{}{tfMapRaw}) @@ -2140,6 +2319,16 @@ func expandIotTopicRulePayload(d *schema.ResourceData) *iot.TopicRulePayload { iotErrorAction = &iot.Action{StepFunctions: action} } + case "timestream": + for _, tfMapRaw := range v.([]interface{}) { + action := expandIotTimestreamAction([]interface{}{tfMapRaw}) + + if action == nil { + continue + } + + iotErrorAction = &iot.Action{Timestream: action} + } } } } @@ -2148,9 +2337,9 @@ func expandIotTopicRulePayload(d *schema.ResourceData) *iot.TopicRulePayload { Actions: actions, AwsIotSqlVersion: aws.String(d.Get("sql_version").(string)), Description: aws.String(d.Get("description").(string)), + ErrorAction: iotErrorAction, RuleDisabled: aws.Bool(!d.Get("enabled").(bool)), Sql: aws.String(d.Get("sql").(string)), - ErrorAction: iotErrorAction, } } @@ -2471,6 +2660,54 @@ func flattenIotFirehoseAction(apiObject *iot.FirehoseAction) []interface{} { return []interface{}{tfMap} } +// Legacy root attribute handling +func flattenIotHttpActions(actions []*iot.Action) []interface{} { + results := make([]interface{}, 0) + + for _, action := range actions { + if action == nil { + continue + } + + if v := action.Http; v != nil { + results = append(results, flattenIotHttpAction(v)...) + } + } + + return results +} + +func flattenIotHttpAction(apiObject *iot.HttpAction) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := make(map[string]interface{}) + + if v := apiObject.Url; v != nil { + tfMap["url"] = aws.StringValue(v) + } + + if v := apiObject.ConfirmationUrl; v != nil { + tfMap["confirmation_url"] = aws.StringValue(v) + } + + if v := apiObject.Headers; v != nil { + headers := []map[string]string{} + + for _, h := range v { + m := map[string]string{ + "key": aws.StringValue(h.Key), + "value": aws.StringValue(h.Value), + } + headers = append(headers, m) + } + tfMap["http_header"] = headers + } + + return []interface{}{tfMap} +} + // Legacy root attribute handling func flattenIotIotAnalyticsActions(actions []*iot.Action) []interface{} { results := make([]interface{}, 0) @@ -2545,6 +2782,53 @@ func flattenIotIotEventsAction(apiObject *iot.IotEventsAction) []interface{} { return []interface{}{tfMap} } +// Legacy root attribute handling +func flattenIotKafkaActions(actions []*iot.Action) []interface{} { + results := make([]interface{}, 0) + + for _, action := range actions { + if action == nil { + continue + } + + if v := action.Kafka; v != nil { + results = append(results, flattenIotKafkaAction(v)...) + } + } + + return results +} + +func flattenIotKafkaAction(apiObject *iot.KafkaAction) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := make(map[string]interface{}) + + if v := apiObject.ClientProperties; v != nil { + tfMap["client_properties"] = aws.StringValueMap(v) + } + + if v := apiObject.DestinationArn; v != nil { + tfMap["destination_arn"] = aws.StringValue(v) + } + + if v := apiObject.Key; v != nil { + tfMap["key"] = aws.StringValue(v) + } + + if v := apiObject.Partition; v != nil { + tfMap["partition"] = aws.StringValue(v) + } + + if v := apiObject.Topic; v != nil { + tfMap["topic"] = aws.StringValue(v) + } + + return []interface{}{tfMap} +} + // Legacy root attribute handling func flattenIotKinesisActions(actions []*iot.Action) []interface{} { results := make([]interface{}, 0) @@ -2696,6 +2980,10 @@ func flattenIotS3Action(apiObject *iot.S3Action) []interface{} { tfMap["bucket_name"] = aws.StringValue(v) } + if v := apiObject.CannedAcl; v != nil { + tfMap["canned_acl"] = aws.StringValue(v) + } + if v := apiObject.Key; v != nil { tfMap["key"] = aws.StringValue(v) } @@ -2802,6 +3090,119 @@ func flattenIotStepFunctionsActions(actions []*iot.Action) []interface{} { return results } +func flattenIotStepFunctionsAction(apiObject *iot.StepFunctionsAction) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := make(map[string]interface{}) + + if v := apiObject.ExecutionNamePrefix; v != nil { + tfMap["execution_name_prefix"] = aws.StringValue(v) + } + + if v := apiObject.StateMachineName; v != nil { + tfMap["state_machine_name"] = aws.StringValue(v) + } + + if v := apiObject.RoleArn; v != nil { + tfMap["role_arn"] = aws.StringValue(v) + } + + return []interface{}{tfMap} +} + +// Legacy root attribute handling +func flattenIotTimestreamActions(actions []*iot.Action) []interface{} { + results := make([]interface{}, 0) + + for _, action := range actions { + if action == nil { + continue + } + + if v := action.Timestream; v != nil { + results = append(results, flattenIotTimestreamAction(v)...) + } + } + + return results +} + +func flattenIotTimestreamAction(apiObject *iot.TimestreamAction) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := make(map[string]interface{}) + + if v := apiObject.DatabaseName; v != nil { + tfMap["database_name"] = aws.StringValue(v) + } + + if v := apiObject.Dimensions; v != nil { + tfMap["dimension"] = flattenIotTimestreamDimensions(v) + } + + if v := apiObject.RoleArn; v != nil { + tfMap["role_arn"] = aws.StringValue(v) + } + + if v := apiObject.TableName; v != nil { + tfMap["table_name"] = aws.StringValue(v) + } + + if v := apiObject.Timestamp; v != nil { + tfMap["timestamp"] = flattenIotTimestreamTimestamp(v) + } + + return []interface{}{tfMap} +} + +func flattenIotTimestreamDimensions(apiObjects []*iot.TimestreamDimension) *schema.Set { + if apiObjects == nil { + return nil + } + + tfSet := schema.NewSet(schema.HashResource(timestreamDimensionResource), []interface{}{}) + + for _, apiObject := range apiObjects { + if apiObject != nil { + tfMap := make(map[string]interface{}) + + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := apiObject.Value; v != nil { + tfMap["value"] = aws.StringValue(v) + } + + tfSet.Add(tfMap) + } + } + + return tfSet +} + +func flattenIotTimestreamTimestamp(apiObject *iot.TimestreamTimestamp) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := make(map[string]interface{}) + + if v := apiObject.Unit; v != nil { + tfMap["unit"] = aws.StringValue(v) + } + + if v := apiObject.Value; v != nil { + tfMap["value"] = aws.StringValue(v) + } + + return []interface{}{tfMap} +} + func flattenIotErrorAction(errorAction *iot.Action) []map[string]interface{} { results := make([]map[string]interface{}, 0) @@ -2837,6 +3238,10 @@ func flattenIotErrorAction(errorAction *iot.Action) []map[string]interface{} { results = append(results, map[string]interface{}{"firehose": flattenIotFirehoseActions(input)}) return results } + if errorAction.Http != nil { + results = append(results, map[string]interface{}{"http": flattenIotHttpActions(input)}) + return results + } if errorAction.IotAnalytics != nil { results = append(results, map[string]interface{}{"iot_analytics": flattenIotIotAnalyticsActions(input)}) return results @@ -2845,6 +3250,10 @@ func flattenIotErrorAction(errorAction *iot.Action) []map[string]interface{} { results = append(results, map[string]interface{}{"iot_events": flattenIotIotEventsActions(input)}) return results } + if errorAction.Kafka != nil { + results = append(results, map[string]interface{}{"kafka": flattenIotKafkaActions(input)}) + return results + } if errorAction.Kinesis != nil { results = append(results, map[string]interface{}{"kinesis": flattenIotKinesisActions(input)}) return results @@ -2873,28 +3282,10 @@ func flattenIotErrorAction(errorAction *iot.Action) []map[string]interface{} { results = append(results, map[string]interface{}{"step_functions": flattenIotStepFunctionsActions(input)}) return results } - - return results -} - -func flattenIotStepFunctionsAction(apiObject *iot.StepFunctionsAction) []interface{} { - if apiObject == nil { - return nil - } - - tfMap := make(map[string]interface{}) - - if v := apiObject.ExecutionNamePrefix; v != nil { - tfMap["execution_name_prefix"] = aws.StringValue(v) - } - - if v := apiObject.StateMachineName; v != nil { - tfMap["state_machine_name"] = aws.StringValue(v) - } - - if v := apiObject.RoleArn; v != nil { - tfMap["role_arn"] = aws.StringValue(v) + if errorAction.Timestream != nil { + results = append(results, map[string]interface{}{"timestream": flattenIotTimestreamActions(input)}) + return results } - return []interface{}{tfMap} + return results } diff --git a/internal/service/iot/topic_rule_destination.go b/internal/service/iot/topic_rule_destination.go new file mode 100644 index 000000000000..c5b206baa9da --- /dev/null +++ b/internal/service/iot/topic_rule_destination.go @@ -0,0 +1,358 @@ +package iot + +import ( + "context" + "errors" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iot" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceTopicRuleDestination() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceTopicRuleDestinationCreate, + ReadWithoutTimeout: resourceTopicRuleDestinationRead, + UpdateWithoutTimeout: resourceTopicRuleDestinationUpdate, + DeleteWithoutTimeout: resourceTopicRuleDestinationDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "vpc_configuration": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "role_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + "security_groups": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "subnet_ids": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "vpc_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + }, + }, + } +} + +func resourceTopicRuleDestinationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IoTConn + + input := &iot.CreateTopicRuleDestinationInput{ + DestinationConfiguration: &iot.TopicRuleDestinationConfiguration{}, + } + + if v, ok := d.GetOk("vpc_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.DestinationConfiguration.VpcConfiguration = expandVpcDestinationConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + + log.Printf("[INFO] Creating IoT Topic Rule Destination: %s", input) + outputRaw, err := tfresource.RetryWhen(tfiam.PropagationTimeout, + func() (interface{}, error) { + return conn.CreateTopicRuleDestinationWithContext(ctx, input) + }, + func(err error) (bool, error) { + if tfawserr.ErrMessageContains(err, iot.ErrCodeInvalidRequestException, "sts:AssumeRole") || + tfawserr.ErrMessageContains(err, iot.ErrCodeInvalidRequestException, "Missing permission") { + return true, err + } + + return false, err + }, + ) + + if err != nil { + return diag.Errorf("creating IoT Topic Rule Destination: %s", err) + } + + d.SetId(aws.StringValue(outputRaw.(*iot.CreateTopicRuleDestinationOutput).TopicRuleDestination.Arn)) + + if _, err := waitTopicRuleDestinationCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for IoT Topic Rule Destination (%s) create: %s", d.Id(), err) + } + + if _, ok := d.GetOk("enabled"); !ok { + _, err := conn.UpdateTopicRuleDestinationWithContext(ctx, &iot.UpdateTopicRuleDestinationInput{ + Arn: aws.String(d.Id()), + Status: aws.String(iot.TopicRuleDestinationStatusDisabled), + }) + + if err != nil { + return diag.Errorf("disabling IoT Topic Rule Destination (%s): %s", d.Id(), err) + } + + if _, err := waitTopicRuleDestinationDisabled(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for IoT Topic Rule Destination (%s) disable: %s", d.Id(), err) + } + } + + return resourceTopicRuleDestinationRead(ctx, d, meta) +} + +func resourceTopicRuleDestinationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IoTConn + + output, err := FindTopicRuleDestinationByARN(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] IoT Topic Rule Destination %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("reading IoT Topic Rule Destination (%s): %s", d.Id(), err) + } + + d.Set("arn", output.Arn) + d.Set("enabled", aws.StringValue(output.Status) == iot.TopicRuleDestinationStatusEnabled) + if output.VpcProperties != nil { + if err := d.Set("vpc_configuration", []interface{}{flattenVpcDestinationProperties(output.VpcProperties)}); err != nil { + return diag.Errorf("setting vpc_configuration: %s", err) + } + } else { + d.Set("vpc_configuration", nil) + } + + return nil +} + +func resourceTopicRuleDestinationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IoTConn + + if d.HasChange("enabled") { + input := &iot.UpdateTopicRuleDestinationInput{ + Arn: aws.String(d.Id()), + Status: aws.String(iot.TopicRuleDestinationStatusEnabled), + } + waiter := waitTopicRuleDestinationEnabled + + if _, ok := d.GetOk("enabled"); !ok { + input.Status = aws.String(iot.TopicRuleDestinationStatusDisabled) + waiter = waitTopicRuleDestinationDisabled + } + + _, err := conn.UpdateTopicRuleDestinationWithContext(ctx, input) + + if err != nil { + return diag.Errorf("updating IoT Topic Rule Destination (%s): %s", d.Id(), err) + } + + if _, err := waiter(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for IoT Topic Rule Destination (%s) update: %s", d.Id(), err) + } + } + + return resourceTopicRuleDestinationRead(ctx, d, meta) +} + +func resourceTopicRuleDestinationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IoTConn + + log.Printf("[INFO] Deleting IoT Topic Rule Destination: %s", d.Id()) + _, err := conn.DeleteTopicRuleDestinationWithContext(ctx, &iot.DeleteTopicRuleDestinationInput{ + Arn: aws.String(d.Id()), + }) + + if err != nil { + return diag.Errorf("deleting IoT Topic Rule Destination: %s", err) + } + + if _, err := waitTopicRuleDestinationDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("waiting for IoT Topic Rule Destination (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func expandVpcDestinationConfiguration(tfMap map[string]interface{}) *iot.VpcDestinationConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &iot.VpcDestinationConfiguration{} + + if v, ok := tfMap["role_arn"].(string); ok && v != "" { + apiObject.RoleArn = aws.String(v) + } + + if v, ok := tfMap["security_groups"].(*schema.Set); ok && v.Len() > 0 { + apiObject.SecurityGroups = flex.ExpandStringSet(v) + } + + if v, ok := tfMap["subnet_ids"].(*schema.Set); ok && v.Len() > 0 { + apiObject.SubnetIds = flex.ExpandStringSet(v) + } + + if v, ok := tfMap["vpc_id"].(string); ok && v != "" { + apiObject.VpcId = aws.String(v) + } + + return apiObject +} + +func flattenVpcDestinationProperties(apiObject *iot.VpcDestinationProperties) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.RoleArn; v != nil { + tfMap["role_arn"] = aws.StringValue(v) + } + + if v := apiObject.SecurityGroups; v != nil { + tfMap["security_groups"] = aws.StringValueSlice(v) + } + + if v := apiObject.SubnetIds; v != nil { + tfMap["subnet_ids"] = aws.StringValueSlice(v) + } + + if v := apiObject.VpcId; v != nil { + tfMap["vpc_id"] = aws.StringValue(v) + } + + return tfMap +} + +func statusTopicRuleDestination(ctx context.Context, conn *iot.IoT, arn string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindTopicRuleDestinationByARN(ctx, conn, arn) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + +func waitTopicRuleDestinationCreated(ctx context.Context, conn *iot.IoT, arn string, timeout time.Duration) (*iot.TopicRuleDestination, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{iot.TopicRuleDestinationStatusInProgress}, + Target: []string{iot.TopicRuleDestinationStatusEnabled}, + Refresh: statusTopicRuleDestination(ctx, conn, arn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*iot.TopicRuleDestination); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusReason))) + + return output, err + } + + return nil, err +} + +func waitTopicRuleDestinationDeleted(ctx context.Context, conn *iot.IoT, arn string, timeout time.Duration) (*iot.TopicRuleDestination, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{iot.TopicRuleDestinationStatusDeleting}, + Target: []string{}, + Refresh: statusTopicRuleDestination(ctx, conn, arn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*iot.TopicRuleDestination); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusReason))) + + return output, err + } + + return nil, err +} + +func waitTopicRuleDestinationDisabled(ctx context.Context, conn *iot.IoT, arn string, timeout time.Duration) (*iot.TopicRuleDestination, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{iot.TopicRuleDestinationStatusEnabled}, + Target: []string{iot.TopicRuleDestinationStatusDisabled}, + Refresh: statusTopicRuleDestination(ctx, conn, arn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*iot.TopicRuleDestination); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusReason))) + + return output, err + } + + return nil, err +} + +func waitTopicRuleDestinationEnabled(ctx context.Context, conn *iot.IoT, arn string, timeout time.Duration) (*iot.TopicRuleDestination, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{iot.TopicRuleDestinationStatusDisabled}, + Target: []string{iot.TopicRuleDestinationStatusEnabled}, + Refresh: statusTopicRuleDestination(ctx, conn, arn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*iot.TopicRuleDestination); ok { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusReason))) + + return output, err + } + + return nil, err +} diff --git a/internal/service/iot/topic_rule_destination_test.go b/internal/service/iot/topic_rule_destination_test.go new file mode 100644 index 000000000000..e533d9d0bd08 --- /dev/null +++ b/internal/service/iot/topic_rule_destination_test.go @@ -0,0 +1,202 @@ +package iot_test + +import ( + "context" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/service/iot" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfiot "github.com/hashicorp/terraform-provider-aws/internal/service/iot" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccIoTTopicRuleDestination_basic(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_iot_topic_rule_destination.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTopicRuleDestinationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTopicRuleDestinationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleDestinationExists(resourceName), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "iot", regexp.MustCompile(`ruledestination/vpc/.+`)), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "vpc_configuration.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "vpc_configuration.0.role_arn"), + resource.TestCheckResourceAttr(resourceName, "vpc_configuration.0.security_groups.#", "1"), + resource.TestCheckResourceAttr(resourceName, "vpc_configuration.0.subnet_ids.#", "2"), + resource.TestCheckResourceAttrSet(resourceName, "vpc_configuration.0.vpc_id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccIoTTopicRuleDestination_disappears(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_iot_topic_rule_destination.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTopicRuleDestinationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTopicRuleDestinationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleDestinationExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfiot.ResourceTopicRuleDestination(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccIoTTopicRuleDestination_enabled(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_iot_topic_rule_destination.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTopicRuleDestinationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTopicRuleDestinationEnabledConfig(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "enabled", "false"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTopicRuleDestinationEnabledConfig(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + ), + }, + { + Config: testAccTopicRuleDestinationEnabledConfig(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleDestinationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "enabled", "false"), + ), + }, + }, + }) +} + +func testAccCheckTopicRuleDestinationDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).IoTConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iot_topic_rule_destination" { + continue + } + + _, err := tfiot.FindTopicRuleDestinationByARN(context.TODO(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("IoT Topic Rule Destination %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckTopicRuleDestinationExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No IoT Topic Rule Destination ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).IoTConn + + _, err := tfiot.FindTopicRuleDestinationByARN(context.TODO(), conn, rs.Primary.ID) + + if err != nil { + return err + } + + return nil + } +} + +func testAccTopicRuleDestinationBaseConfig(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigVpcWithSubnets(2), + testAccTopicRuleRoleConfig(rName), + fmt.Sprintf(` +resource "aws_security_group" "test" { + name = %[1]q + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} +`, rName)) +} + +func testAccTopicRuleDestinationConfig(rName string) string { + return acctest.ConfigCompose(testAccTopicRuleDestinationBaseConfig(rName), ` +resource "aws_iot_topic_rule_destination" "test" { + vpc_configuration { + role_arn = aws_iam_role.test.arn + security_groups = [aws_security_group.test.id] + subnet_ids = aws_subnet.test[*].id + vpc_id = aws_vpc.test.id + } +} +`) +} + +func testAccTopicRuleDestinationEnabledConfig(rName string, enabled bool) string { + return acctest.ConfigCompose(testAccTopicRuleDestinationBaseConfig(rName), fmt.Sprintf(` +resource "aws_iot_topic_rule_destination" "test" { + enabled = %[1]t + + vpc_configuration { + role_arn = aws_iam_role.test.arn + security_groups = [aws_security_group.test.id] + subnet_ids = aws_subnet.test[*].id + vpc_id = aws_vpc.test.id + } +} +`, enabled)) +} diff --git a/internal/service/iot/topic_rule_test.go b/internal/service/iot/topic_rule_test.go index b2f3833312fe..7b5463f8c109 100644 --- a/internal/service/iot/topic_rule_test.go +++ b/internal/service/iot/topic_rule_test.go @@ -4,18 +4,29 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iot" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfiot "github.com/hashicorp/terraform-provider-aws/internal/service/iot" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) +func init() { + acctest.RegisterServiceErrorCheckFunc(iot.EndpointsID, testAccErrorCheckSkip) +} + +func testAccErrorCheckSkip(t *testing.T) resource.ErrorCheckFunc { + return acctest.ErrorCheckSkipMessagesContaining(t, + "not been supported in region", + ) +} + func TestAccIoTTopicRule_basic(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -24,15 +35,36 @@ func TestAccIoTTopicRule_basic(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_basic(rName), + Config: testAccTopicRuleConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), - resource.TestCheckResourceAttr("aws_iot_topic_rule.rule", "name", fmt.Sprintf("test_rule_%s", rName)), - resource.TestCheckResourceAttr("aws_iot_topic_rule.rule", "description", "Example rule"), - resource.TestCheckResourceAttr("aws_iot_topic_rule.rule", "enabled", "true"), - resource.TestCheckResourceAttr("aws_iot_topic_rule.rule", "sql", "SELECT * FROM 'topic/test'"), - resource.TestCheckResourceAttr("aws_iot_topic_rule.rule", "sql_version", "2015-10-08"), - resource.TestCheckResourceAttr("aws_iot_topic_rule.rule", "tags.%", "0"), + testAccCheckTopicRuleExists(resourceName), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "iot", fmt.Sprintf("rule/%s", rName)), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sql", "SELECT * FROM 'topic/test'"), + resource.TestCheckResourceAttr(resourceName, "sql_version", "2015-10-08"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -44,9 +76,75 @@ func TestAccIoTTopicRule_basic(t *testing.T) { }) } +func TestAccIoTTopicRule_disappears(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTopicRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTopicRuleConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfiot.ResourceTopicRule(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccIoTTopicRule_tags(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTopicRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTopicRuleConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTopicRuleConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccTopicRuleConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + func TestAccIoTTopicRule_cloudWatchAlarm(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -55,9 +153,35 @@ func TestAccIoTTopicRule_cloudWatchAlarm(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_cloudWatchalarm(rName), + Config: testAccTopicRuleCloudWatchAlarmConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "cloudwatch_alarm.*", map[string]string{ + "alarm_name": "myalarm", + "state_reason": "test", + "state_value": "OK", + }), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "description", "Example rule"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -70,8 +194,8 @@ func TestAccIoTTopicRule_cloudWatchAlarm(t *testing.T) { } func TestAccIoTTopicRule_cloudWatchLogs(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -80,9 +204,36 @@ func TestAccIoTTopicRule_cloudWatchLogs(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccIoTTopicRule_cloudWatchLogs(rName), + Config: testAccTopicRuleCloudWatchLogsConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "cloudwatch_logs.*", map[string]string{ + "log_group_name": "mylogs1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "cloudwatch_logs.*", map[string]string{ + "log_group_name": "mylogs2", + }), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -95,8 +246,8 @@ func TestAccIoTTopicRule_cloudWatchLogs(t *testing.T) { } func TestAccIoTTopicRule_cloudWatchMetric(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -105,9 +256,35 @@ func TestAccIoTTopicRule_cloudWatchMetric(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_cloudWatchmetric(rName), + Config: testAccTopicRuleCloudWatchMetricConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "cloudwatch_metric.*", map[string]string{ + "metric_name": "TestName", + "metric_namespace": "TestNS", + "metric_unit": "s", + "metric_value": "10", + }), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -120,8 +297,8 @@ func TestAccIoTTopicRule_cloudWatchMetric(t *testing.T) { } func TestAccIoTTopicRule_dynamoDB(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -130,9 +307,36 @@ func TestAccIoTTopicRule_dynamoDB(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_dynamoDB(rName), + Config: testAccTopicRuleDynamoDBConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "description", "Description1"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "dynamodb.*", map[string]string{ + "hash_key_field": "hkf", + "hash_key_value": "hkv", + "payload_field": "pf", + "table_name": "tn", + }), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -141,9 +345,40 @@ func TestAccIoTTopicRule_dynamoDB(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccTopicRule_dynamoDB_rangekeys(rName), + Config: testAccTopicRuleDynamoDBRangeKeyConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "description", "Description2"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "dynamodb.*", map[string]string{ + "hash_key_field": "hkf", + "hash_key_value": "hkv", + "operation": "INSERT", + "payload_field": "pf", + "range_key_field": "rkf", + "range_key_type": "STRING", + "range_key_value": "rkv", + "table_name": "tn", + }), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, }, @@ -151,7 +386,8 @@ func TestAccIoTTopicRule_dynamoDB(t *testing.T) { } func TestAccIoTTopicRule_dynamoDBv2(t *testing.T) { - rName := sdkacctest.RandString(5) + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -160,9 +396,33 @@ func TestAccIoTTopicRule_dynamoDBv2(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_dynamoDBv2(rName), + Config: testAccTopicRuleDynamoDBv2Config(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "dynamodbv2.*", map[string]string{ + "put_item.#": "1", + "put_item.0.table_name": "test", + }), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, }, @@ -170,8 +430,8 @@ func TestAccIoTTopicRule_dynamoDBv2(t *testing.T) { } func TestAccIoTTopicRule_elasticSearch(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -180,9 +440,34 @@ func TestAccIoTTopicRule_elasticSearch(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_elasticSearch(rName), + Config: testAccTopicRuleElasticsearchConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "elasticsearch.*", map[string]string{ + "id": "myIdentifier", + "index": "myindex", + "type": "mydocument", + }), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -195,8 +480,8 @@ func TestAccIoTTopicRule_elasticSearch(t *testing.T) { } func TestAccIoTTopicRule_firehose(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -205,9 +490,38 @@ func TestAccIoTTopicRule_firehose(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_firehose(rName), + Config: testAccTopicRuleFirehoseConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "firehose.*", map[string]string{ + "delivery_stream_name": "mystream1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "firehose.*", map[string]string{ + "delivery_stream_name": "mystream2", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "firehose.*", map[string]string{ + "delivery_stream_name": "mystream3", + }), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -220,8 +534,8 @@ func TestAccIoTTopicRule_firehose(t *testing.T) { } func TestAccIoTTopicRule_Firehose_separator(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -230,9 +544,33 @@ func TestAccIoTTopicRule_Firehose_separator(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_firehose_separator(rName, "\n"), + Config: testAccTopicRuleFirehoseSeparatorConfig(rName, "\n"), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "firehose.*", map[string]string{ + "delivery_stream_name": "mystream", + "separator": "\n", + }), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -241,18 +579,42 @@ func TestAccIoTTopicRule_Firehose_separator(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccTopicRule_firehose_separator(rName, ","), + Config: testAccTopicRuleFirehoseSeparatorConfig(rName, ","), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "firehose.*", map[string]string{ + "delivery_stream_name": "mystream", + "separator": ",", + }), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, }, }) } -func TestAccIoTTopicRule_kinesis(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" +func TestAccIoTTopicRule_http(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -261,9 +623,34 @@ func TestAccIoTTopicRule_kinesis(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_kinesis(rName), + Config: testAccTopicRuleHTTPConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "http.*", map[string]string{ + "confirmation_url": "", + "http_header.#": "0", + "url": "https://example.com/ingress", + }), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -271,13 +658,130 @@ func TestAccIoTTopicRule_kinesis(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccTopicRuleHTTPConfirmationURLConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "http.*", map[string]string{ + "confirmation_url": "https://example.com/", + "http_header.#": "0", + "url": "https://example.com/ingress", + }), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), + ), + }, + { + Config: testAccTopicRuleHTTPHeadersConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "http.*", map[string]string{ + "confirmation_url": "", + "http_header.#": "2", + "http_header.0.key": "X-Header-1", + "http_header.0.value": "v1", + "http_header.1.key": "X-Header-2", + "http_header.1.value": "v2", + "url": "https://example.com/ingress", + }), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), + ), + }, + { + Config: testAccTopicRuleHTTPErrorActionConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.http.#", "1"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.http.0.url", "https://example.com/error-ingress"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.timestream.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "http.*", map[string]string{ + "confirmation_url": "", + "http_header.#": "0", + "url": "https://example.com/ingress", + }), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), + ), + }, }, }) } -func TestAccIoTTopicRule_lambda(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" +func TestAccIoTTopicRule_IoT_analytics(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -286,23 +790,89 @@ func TestAccIoTTopicRule_lambda(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_lambda(rName), + Config: testAccTopicRuleIoTAnalyticsConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "iot_analytics.*", map[string]string{ + "channel_name": "fakedata", + }), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, + }, + }) +} + +func TestAccIoTTopicRule_IoT_events(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTopicRuleDestroy, + Steps: []resource.TestStep{ { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + Config: testAccTopicRuleIoTEventsConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "iot_events.*", map[string]string{ + "input_name": "fake_input_name", + "message_id": "fake_message_id", + }), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), + ), }, }, }) } -func TestAccIoTTopicRule_republish(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" +func TestAccIoTTopicRule_kafka(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -311,9 +881,40 @@ func TestAccIoTTopicRule_republish(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_republish(rName), + Config: testAccTopicRuleKafkaConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "kafka.*", map[string]string{ + "client_properties.%": "8", + "client_properties.acks": "1", + "client_properties.bootstrap.servers": "b-1.localhost:9094", + "client_properties.compression.type": "none", + "client_properties.key.serializer": "org.apache.kafka.common.serialization.StringSerializer", + "client_properties.security.protocol": "SSL", + "client_properties.ssl.keystore.password": "password", + "client_properties.value.serializer": "org.apache.kafka.common.serialization.ByteBufferSerializer", + "topic": "fake_topic", + }), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -325,9 +926,9 @@ func TestAccIoTTopicRule_republish(t *testing.T) { }) } -func TestAccIoTTopicRule_republishWithQos(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" +func TestAccIoTTopicRule_kinesis(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -336,9 +937,32 @@ func TestAccIoTTopicRule_republishWithQos(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_republish_with_qos(rName), + Config: testAccTopicRuleKinesisConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "kinesis.*", map[string]string{ + "stream_name": "mystream", + }), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -350,9 +974,9 @@ func TestAccIoTTopicRule_republishWithQos(t *testing.T) { }) } -func TestAccIoTTopicRule_s3(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" +func TestAccIoTTopicRule_lambda(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -361,9 +985,29 @@ func TestAccIoTTopicRule_s3(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_s3(rName), + Config: testAccTopicRuleLambdaConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "1"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -375,9 +1019,9 @@ func TestAccIoTTopicRule_s3(t *testing.T) { }) } -func TestAccIoTTopicRule_sns(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" +func TestAccIoTTopicRule_republish(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -386,9 +1030,33 @@ func TestAccIoTTopicRule_sns(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_sns(rName), + Config: testAccTopicRuleRepublishConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "republish.*", map[string]string{ + "qos": "0", + "topic": "mytopic", + }), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -400,9 +1068,9 @@ func TestAccIoTTopicRule_sns(t *testing.T) { }) } -func TestAccIoTTopicRule_sqs(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" +func TestAccIoTTopicRule_republishWithQos(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -411,9 +1079,33 @@ func TestAccIoTTopicRule_sqs(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_sqs(rName), + Config: testAccTopicRuleRepublishWithQoSConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "republish.*", map[string]string{ + "qos": "1", + "topic": "mytopic", + }), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -425,9 +1117,9 @@ func TestAccIoTTopicRule_sqs(t *testing.T) { }) } -func TestAccIoTTopicRule_Step_functions(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" +func TestAccIoTTopicRule_s3(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -436,9 +1128,34 @@ func TestAccIoTTopicRule_Step_functions(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_step_functions(rName), + Config: testAccTopicRuleS3Config(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "s3.*", map[string]string{ + "bucket_name": "mybucket", + "canned_acl": "private", + "key": "mykey", + }), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -450,8 +1167,9 @@ func TestAccIoTTopicRule_Step_functions(t *testing.T) { }) } -func TestAccIoTTopicRule_IoT_analytics(t *testing.T) { - rName := sdkacctest.RandString(5) +func TestAccIoTTopicRule_sns(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -460,17 +1178,43 @@ func TestAccIoTTopicRule_IoT_analytics(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_iot_analytics(rName), + Config: testAccTopicRuleSNSConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } -func TestAccIoTTopicRule_IoT_events(t *testing.T) { - rName := sdkacctest.RandString(5) +func TestAccIoTTopicRule_sqs(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -479,17 +1223,46 @@ func TestAccIoTTopicRule_IoT_events(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_iot_events(rName), + Config: testAccTopicRuleSQSConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "sqs.*", map[string]string{ + "queue_url": "fakedata", + "use_base64": "false", + }), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } -func TestAccIoTTopicRule_tags(t *testing.T) { - rName := sdkacctest.RandString(5) +func TestAccIoTTopicRule_Step_functions(t *testing.T) { + rName := testAccTopicRuleName() resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ @@ -499,11 +1272,33 @@ func TestAccIoTTopicRule_tags(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRuleTags1(rName, "key1", "user@example"), + Config: testAccTopicRuleStepFunctionsConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckTopicRuleExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "user@example"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "step_functions.*", map[string]string{ + "execution_name_prefix": "myprefix", + "state_machine_name": "mystatemachine", + }), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -511,30 +1306,70 @@ func TestAccIoTTopicRule_tags(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + }, + }) +} + +func TestAccIoTTopicRule_Timestream(t *testing.T) { + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTopicRuleDestroy, + Steps: []resource.TestStep{ { - Config: testAccTopicRuleTags2(rName, "key1", "user@example", "key2", "value2"), + Config: testAccTopicRuleTimestreamConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckTopicRuleExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "user@example"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "0"), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "timestream.*", map[string]string{ + "database_name": "TestDB", + "dimension.#": "1", + "table_name": "test_table", + "timestamp.#": "1", + "timestamp.0.unit": "MILLISECONDS", + "timestamp.0.value": "${time}", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "timestream.*.dimension.*", map[string]string{ + "name": "dim", + "value": "${dim}", + }), ), }, { - Config: testAccTopicRuleTags1(rName, "key2", "value2"), - Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), - ), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) } func TestAccIoTTopicRule_errorAction(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -543,9 +1378,52 @@ func TestAccIoTTopicRule_errorAction(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_errorAction(rName), + Config: testAccTopicRuleErrorActionConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.kinesis.#", "1"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.kinesis.0.stream_name", "mystream2"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.timestream.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "kinesis.*", map[string]string{ + "stream_name": "mystream1", + }), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -559,8 +1437,8 @@ func TestAccIoTTopicRule_errorAction(t *testing.T) { // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/16115 func TestAccIoTTopicRule_updateKinesisErrorAction(t *testing.T) { - rName := sdkacctest.RandString(5) - resourceName := "aws_iot_topic_rule.rule" + rName := testAccTopicRuleName() + resourceName := "aws_iot_topic_rule.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -569,18 +1447,81 @@ func TestAccIoTTopicRule_updateKinesisErrorAction(t *testing.T) { CheckDestroy: testAccCheckTopicRuleDestroy, Steps: []resource.TestStep{ { - Config: testAccTopicRule_kinesis(rName), + Config: testAccTopicRuleKinesisConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), resource.TestCheckResourceAttr(resourceName, "error_action.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "kinesis.*", map[string]string{ + "stream_name": "mystream", + }), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, - { - Config: testAccTopicRule_errorAction(rName), + Config: testAccTopicRuleErrorActionConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + testAccCheckTopicRuleExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch.#", "0"), resource.TestCheckResourceAttr(resourceName, "error_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.cloudwatch_alarm.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.cloudwatch_logs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.cloudwatch_metric.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.dynamodb.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.dynamodbv2.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.elasticsearch.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.kinesis.#", "1"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.kinesis.0.stream_name", "mystream2"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "error_action.0.timestream.#", "0"), + resource.TestCheckResourceAttr(resourceName, "firehose.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_analytics.#", "0"), + resource.TestCheckResourceAttr(resourceName, "iot_events.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kafka.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "kinesis.*", map[string]string{ + "stream_name": "mystream1", + }), + resource.TestCheckResourceAttr(resourceName, "lambda.#", "0"), + resource.TestCheckResourceAttr(resourceName, "republish.#", "0"), + resource.TestCheckResourceAttr(resourceName, "s3.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "sqs.#", "0"), + resource.TestCheckResourceAttr(resourceName, "step_functions.#", "0"), + resource.TestCheckResourceAttr(resourceName, "timestream.#", "0"), ), }, { @@ -600,108 +1541,99 @@ func testAccCheckTopicRuleDestroy(s *terraform.State) error { continue } - input := &iot.ListTopicRulesInput{} + _, err := tfiot.FindTopicRuleByName(conn, rs.Primary.ID) - out, err := conn.ListTopicRules(input) + if tfresource.NotFound(err) { + continue + } if err != nil { return err } - for _, r := range out.Rules { - if *r.RuleName == rs.Primary.ID { - return fmt.Errorf("IoT topic rule still exists:\n%s", r) - } - } - + return fmt.Errorf("IoT Topic Rule %s still exists", rs.Primary.ID) } return nil } -func testAccCheckTopicRuleExists(name string) resource.TestCheckFunc { +func testAccCheckTopicRuleExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", name) + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No IoT Topic Rule ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).IoTConn - input := &iot.ListTopicRulesInput{} - output, err := conn.ListTopicRules(input) + _, err := tfiot.FindTopicRuleByName(conn, rs.Primary.ID) if err != nil { return err } - for _, rule := range output.Rules { - if aws.StringValue(rule.RuleName) == rs.Primary.ID { - return nil - } - } - - return fmt.Errorf("IoT Topic Rule (%s) not found", rs.Primary.ID) + return nil } } -func testAccTopicRuleRole(rName string) string { +func testAccTopicRuleName() string { + return fmt.Sprintf("tf_acc_test_%[1]s", sdkacctest.RandString(20)) +} + +func testAccTopicRuleRoleConfig(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} -resource "aws_iam_role" "iot_role" { - name = "test_role_%[1]s" +resource "aws_iam_role" "test" { + name = %[1]q assume_role_policy = <