diff --git a/.changelog/24765.txt b/.changelog/24765.txt new file mode 100644 index 00000000000..d192c96cef6 --- /dev/null +++ b/.changelog/24765.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_iot_domain_configuration +``` \ No newline at end of file diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index 25e0073293b..b4674aaa9d0 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -2442,6 +2442,16 @@ func CheckResourceAttrIsJSONString(n, key string) resource.TestCheckFunc { }) } +// SkipIfEnvVarNotSet skips the current test if the specified environment variable is not set. +// The variable's value is returned. +func SkipIfEnvVarNotSet(t *testing.T, key string) string { + v := os.Getenv(key) + if v == "" { + t.Skipf("Environment variable %s is not set, skipping test", key) + } + return v +} + // RunSerialTests1Level runs test cases in parallel, optionally sleeping between each. func RunSerialTests1Level(t *testing.T, testCases map[string]func(t *testing.T), d time.Duration) { t.Helper() diff --git a/internal/service/acm/sweep.go b/internal/service/acm/sweep.go index 079d56bd9bc..a33e72f960d 100644 --- a/internal/service/acm/sweep.go +++ b/internal/service/acm/sweep.go @@ -32,6 +32,7 @@ func RegisterSweepers() { "aws_elb", "aws_iam_server_certificate", "aws_iam_signing_certificate", + "aws_iot_domain_configuration", "aws_lb", "aws_lb_listener", }, diff --git a/internal/service/iot/authorizer_test.go b/internal/service/iot/authorizer_test.go index 68794778d68..bc0781043b0 100644 --- a/internal/service/iot/authorizer_test.go +++ b/internal/service/iot/authorizer_test.go @@ -206,7 +206,7 @@ func testAccCheckAuthorizerDestroy(ctx context.Context) resource.TestCheckFunc { } } -func testAccAuthorizerBaseConfig(rName string) string { +func testAccAuthorizerConfig_base(rName string) string { return fmt.Sprintf(` resource "aws_iam_role" "test" { name = %[1]q @@ -240,7 +240,7 @@ resource "aws_lambda_function" "test" { } func testAccAuthorizerConfig_basic(rName string) string { - return acctest.ConfigCompose(testAccAuthorizerBaseConfig(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccAuthorizerConfig_base(rName), fmt.Sprintf(` resource "aws_iot_authorizer" "test" { name = %[1]q authorizer_function_arn = aws_lambda_function.test.arn @@ -254,7 +254,7 @@ resource "aws_iot_authorizer" "test" { } func testAccAuthorizerConfig_updated(rName string) string { - return acctest.ConfigCompose(testAccAuthorizerBaseConfig(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccAuthorizerConfig_base(rName), fmt.Sprintf(` resource "aws_iot_authorizer" "test" { name = %[1]q authorizer_function_arn = aws_lambda_function.test.arn @@ -272,7 +272,7 @@ resource "aws_iot_authorizer" "test" { } func testAccAuthorizerConfig_signingDisabled(rName string) string { - return acctest.ConfigCompose(testAccAuthorizerBaseConfig(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccAuthorizerConfig_base(rName), fmt.Sprintf(` resource "aws_iot_authorizer" "test" { name = %[1]q authorizer_function_arn = aws_lambda_function.test.arn diff --git a/internal/service/iot/domain_configuration.go b/internal/service/iot/domain_configuration.go new file mode 100644 index 00000000000..6d9ffa0fd5a --- /dev/null +++ b/internal/service/iot/domain_configuration.go @@ -0,0 +1,368 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package iot + +import ( + "context" + "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/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "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/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_iot_domain_configuration", name="Domain Configuration") +// @Tags(identifierAttribute="arn") +func ResourceDomainConfiguration() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceDomainConfigurationCreate, + ReadWithoutTimeout: resourceDomainConfigurationRead, + UpdateWithoutTimeout: resourceDomainConfigurationUpdate, + DeleteWithoutTimeout: resourceDomainConfigurationDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + CustomizeDiff: verify.SetTagsDiff, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "authorizer_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allow_authorizer_override": { + Type: schema.TypeBool, + Optional: true, + }, + "default_authorizer_name": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "domain_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "domain_type": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "server_certificate_arns": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: verify.ValidARN, + }, + }, + "service_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: iot.ServiceTypeData, + ValidateFunc: validation.StringInSlice(iot.ServiceType_Values(), false), + }, + "status": { + Type: schema.TypeString, + Optional: true, + Default: iot.DomainConfigurationStatusEnabled, + ValidateFunc: validation.StringInSlice(iot.DomainConfigurationStatus_Values(), false), + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + "tls_config": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "security_policy": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + "validation_certificate_arn": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + }, + } +} + +func resourceDomainConfigurationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).IoTConn(ctx) + + name := d.Get("name").(string) + input := &iot.CreateDomainConfigurationInput{ + DomainConfigurationName: aws.String(name), + Tags: getTagsIn(ctx), + } + + if v, ok := d.GetOk("authorizer_config"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.AuthorizerConfig = expandAuthorizerConfig(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("domain_name"); ok { + input.DomainName = aws.String(v.(string)) + } + + if v, ok := d.GetOk("server_certificate_arns"); ok && v.(*schema.Set).Len() > 0 { + input.ServerCertificateArns = flex.ExpandStringSet(v.(*schema.Set)) + } + + if v, ok := d.GetOk("service_type"); ok { + input.ServiceType = aws.String(v.(string)) + } + + if v, ok := d.GetOk("tls_config"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.TlsConfig = expandTlsConfig(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("validation_certificate_arn"); ok { + input.ValidationCertificateArn = aws.String(v.(string)) + } + + output, err := conn.CreateDomainConfigurationWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating IoT Domain Configuration (%s): %s", name, err) + } + + d.SetId(aws.StringValue(output.DomainConfigurationName)) + + return append(diags, resourceDomainConfigurationRead(ctx, d, meta)...) +} + +func resourceDomainConfigurationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).IoTConn(ctx) + + output, err := FindDomainConfigurationByName(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] IoT Domain Configuration (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading IoT Domain Configuration (%s): %s", d.Id(), err) + } + + d.Set("arn", output.DomainConfigurationArn) + if output.AuthorizerConfig != nil { + if err := d.Set("authorizer_config", []interface{}{flattenAuthorizerConfig(output.AuthorizerConfig)}); err != nil { + return diag.Errorf("setting authorizer_config: %s", err) + } + } else { + d.Set("authorizer_config", nil) + } + d.Set("domain_name", output.DomainName) + d.Set("domain_type", output.DomainType) + d.Set("name", output.DomainConfigurationName) + d.Set("server_certificate_arns", tfslices.ApplyToAll(output.ServerCertificates, func(v *iot.ServerCertificateSummary) string { + return aws.StringValue(v.ServerCertificateArn) + })) + d.Set("service_type", output.ServiceType) + d.Set("status", output.DomainConfigurationStatus) + if output.TlsConfig != nil { + if err := d.Set("tls_config", []interface{}{flattenTlsConfig(output.TlsConfig)}); err != nil { + return diag.Errorf("setting tls_config: %s", err) + } + } else { + d.Set("tls_config", nil) + } + d.Set("validation_certificate_arn", d.Get("validation_certificate_arn")) + + return nil +} + +func resourceDomainConfigurationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).IoTConn(ctx) + + if d.HasChangesExcept("tags", "tags_all") { + input := &iot.UpdateDomainConfigurationInput{ + DomainConfigurationName: aws.String(d.Id()), + } + + if d.HasChange("authorizer_config") { + if v, ok := d.GetOk("authorizer_config"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.AuthorizerConfig = expandAuthorizerConfig(v.([]interface{})[0].(map[string]interface{})) + } else { + input.RemoveAuthorizerConfig = aws.Bool(true) + } + } + + if d.HasChange("status") { + input.DomainConfigurationStatus = aws.String(d.Get("status").(string)) + } + + if d.HasChange("tls_config") { + if v, ok := d.GetOk("tls_config"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.TlsConfig = expandTlsConfig(v.([]interface{})[0].(map[string]interface{})) + } + } + + _, err := conn.UpdateDomainConfigurationWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating IoT Domain Configuration (%s): %s", d.Id(), err) + } + } + + return append(diags, resourceDomainConfigurationRead(ctx, d, meta)...) +} + +func resourceDomainConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).IoTConn(ctx) + + if d.Get("status").(string) == iot.DomainConfigurationStatusEnabled { + log.Printf("[DEBUG] Disabling IoT Domain Configuration: %s", d.Id()) + _, err := conn.UpdateDomainConfigurationWithContext(ctx, &iot.UpdateDomainConfigurationInput{ + DomainConfigurationName: aws.String(d.Id()), + DomainConfigurationStatus: aws.String(iot.DomainConfigurationStatusDisabled), + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "disabling IoT Domain Configuration (%s): %s", d.Id(), err) + } + } + + log.Printf("[DEBUG] Deleting IoT Domain Configuration: %s", d.Id()) + _, err := conn.DeleteDomainConfigurationWithContext(ctx, &iot.DeleteDomainConfigurationInput{ + DomainConfigurationName: aws.String(d.Id()), + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting IoT Domain Configuration (%s): %s", d.Id(), err) + } + + return diags +} + +func FindDomainConfigurationByName(ctx context.Context, conn *iot.IoT, name string) (*iot.DescribeDomainConfigurationOutput, error) { + input := &iot.DescribeDomainConfigurationInput{ + DomainConfigurationName: aws.String(name), + } + + output, err := conn.DescribeDomainConfigurationWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, iot.ErrCodeResourceNotFoundException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func expandAuthorizerConfig(tfMap map[string]interface{}) *iot.AuthorizerConfig { + if tfMap == nil { + return nil + } + + apiObject := &iot.AuthorizerConfig{} + + if v, ok := tfMap["allow_authorizer_override"].(bool); ok { + apiObject.AllowAuthorizerOverride = aws.Bool(v) + } + + if v, ok := tfMap["default_authorizer_name"].(string); ok && v != "" { + apiObject.DefaultAuthorizerName = aws.String(v) + } + + return apiObject +} + +func expandTlsConfig(tfMap map[string]interface{}) *iot.TlsConfig { // nosemgrep:ci.caps5-in-func-name + if tfMap == nil { + return nil + } + + apiObject := &iot.TlsConfig{} + + if v, ok := tfMap["security_policy"].(string); ok && v != "" { + apiObject.SecurityPolicy = aws.String(v) + } + + return apiObject +} + +func flattenAuthorizerConfig(apiObject *iot.AuthorizerConfig) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.AllowAuthorizerOverride; v != nil { + tfMap["allow_authorizer_override"] = aws.BoolValue(v) + } + + if v := apiObject.DefaultAuthorizerName; v != nil { + tfMap["default_authorizer_name"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenTlsConfig(apiObject *iot.TlsConfig) map[string]interface{} { // nosemgrep:ci.caps5-in-func-name + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.SecurityPolicy; v != nil { + tfMap["security_policy"] = aws.StringValue(v) + } + + return tfMap +} diff --git a/internal/service/iot/domain_configuration_test.go b/internal/service/iot/domain_configuration_test.go new file mode 100644 index 00000000000..bc0b1754316 --- /dev/null +++ b/internal/service/iot/domain_configuration_test.go @@ -0,0 +1,353 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package iot_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/iot" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/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 TestAccIoTDomainConfiguration_basic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rootDomain := acctest.ACMCertificateDomainFromEnv(t) + domain := acctest.ACMCertificateRandomSubDomain(rootDomain) + resourceName := "aws_iot_domain_configuration.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDomainConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDomainConfigurationConfig_basic(rName, rootDomain, domain), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDomainConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "authorizer_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "domain_name", domain), + resource.TestCheckResourceAttr(resourceName, "domain_type", "CUSTOMER_MANAGED"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "server_certificate_arns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "service_type", "DATA"), + resource.TestCheckResourceAttr(resourceName, "status", "ENABLED"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tls_config.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "tls_config.0.security_policy"), + resource.TestCheckResourceAttr(resourceName, "validation_certificate_arn", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccIoTDomainConfiguration_disappears(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rootDomain := acctest.ACMCertificateDomainFromEnv(t) + domain := acctest.ACMCertificateRandomSubDomain(rootDomain) + resourceName := "aws_iot_domain_configuration.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDomainConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDomainConfigurationConfig_basic(rName, rootDomain, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfiot.ResourceDomainConfiguration(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccIoTDomainConfiguration_tags(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rootDomain := acctest.ACMCertificateDomainFromEnv(t) + domain := acctest.ACMCertificateRandomSubDomain(rootDomain) + resourceName := "aws_iot_domain_configuration.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDomainConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDomainConfigurationConfig_tags1(rName, rootDomain, domain, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDomainConfigurationConfig_tags2(rName, rootDomain, domain, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccDomainConfigurationConfig_tags1(rName, rootDomain, domain, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccIoTDomainConfiguration_update(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rootDomain := acctest.ACMCertificateDomainFromEnv(t) + domain := acctest.ACMCertificateRandomSubDomain(rootDomain) + resourceName := "aws_iot_domain_configuration.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDomainConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDomainConfigurationConfig_securityPolicy(rName, rootDomain, domain, "IoTSecurityPolicy_TLS13_1_3_2022_10", true), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "authorizer_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "authorizer_config.0.allow_authorizer_override", "true"), + resource.TestCheckResourceAttr(resourceName, "tls_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tls_config.0.security_policy", "IoTSecurityPolicy_TLS13_1_3_2022_10"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDomainConfigurationConfig_securityPolicy(rName, rootDomain, domain, "IoTSecurityPolicy_TLS13_1_2_2022_10", false), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "authorizer_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "authorizer_config.0.allow_authorizer_override", "false"), + resource.TestCheckResourceAttr(resourceName, "tls_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tls_config.0.security_policy", "IoTSecurityPolicy_TLS13_1_2_2022_10"), + ), + }, + }, + }) +} + +func TestAccIoTDomainConfiguration_awsManaged(t *testing.T) { // nosemgrep:ci.aws-in-func-name + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_iot_domain_configuration.test" + + acctest.SkipIfEnvVarNotSet(t, "IOT_DOMAIN_CONFIGURATION_TEST_AWS_MANAGED") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccDomainConfigurationConfig_awsManaged(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDomainConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "authorizer_config.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "domain_name"), + resource.TestCheckResourceAttr(resourceName, "domain_type", "AWS_MANAGED"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "server_certificate_arns.#", "0"), + resource.TestCheckResourceAttr(resourceName, "service_type", "DATA"), + resource.TestCheckResourceAttr(resourceName, "status", "ENABLED"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tls_config.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "tls_config.0.security_policy"), + resource.TestCheckResourceAttr(resourceName, "validation_certificate_arn", ""), + ), + }, + }, + }) +} + +func testAccCheckDomainConfigurationExists(ctx context.Context, 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) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).IoTConn(ctx) + + _, err := tfiot.FindDomainConfigurationByName(ctx, conn, rs.Primary.ID) + + return err + } +} + +func testAccCheckDomainConfigurationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).IoTConn(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iot_domain_configuration" { + continue + } + + _, err := tfiot.FindDomainConfigurationByName(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("IoT Domain Configuration %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccDomainConfigurationConfig_base(rootDomain, domain string) string { + return fmt.Sprintf(` +resource "aws_acm_certificate" "test" { + domain_name = %[2]q + validation_method = "DNS" +} + +data "aws_route53_zone" "test" { + name = %[1]q + private_zone = false +} + +resource "aws_route53_record" "test" { + allow_overwrite = true + name = tolist(aws_acm_certificate.test.domain_validation_options)[0].resource_record_name + records = [tolist(aws_acm_certificate.test.domain_validation_options)[0].resource_record_value] + ttl = 60 + type = tolist(aws_acm_certificate.test.domain_validation_options)[0].resource_record_type + zone_id = data.aws_route53_zone.test.zone_id +} + +resource "aws_acm_certificate_validation" "test" { + depends_on = [aws_route53_record.test] + + certificate_arn = aws_acm_certificate.test.arn +} +`, rootDomain, domain) +} + +func testAccDomainConfigurationConfig_basic(rName, rootDomain, domain string) string { + return acctest.ConfigCompose(testAccDomainConfigurationConfig_base(rootDomain, domain), fmt.Sprintf(` +resource "aws_iot_domain_configuration" "test" { + depends_on = [aws_acm_certificate_validation.test] + + name = %[1]q + domain_name = %[2]q + server_certificate_arns = [aws_acm_certificate.test.arn] +} +`, rName, domain)) +} + +func testAccDomainConfigurationConfig_tags1(rName, rootDomain, domain, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccDomainConfigurationConfig_base(rootDomain, domain), fmt.Sprintf(` +resource "aws_iot_domain_configuration" "test" { + depends_on = [aws_acm_certificate_validation.test] + + name = %[1]q + domain_name = %[2]q + server_certificate_arns = [aws_acm_certificate.test.arn] + + tags = { + %[3]q = %[4]q + } +} +`, rName, domain, tagKey1, tagValue1)) +} + +func testAccDomainConfigurationConfig_tags2(rName, rootDomain, domain, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccDomainConfigurationConfig_base(rootDomain, domain), fmt.Sprintf(` +resource "aws_iot_domain_configuration" "test" { + depends_on = [aws_acm_certificate_validation.test] + + name = %[1]q + domain_name = %[2]q + server_certificate_arns = [aws_acm_certificate.test.arn] + + tags = { + %[3]q = %[4]q + %[5]q = %[6]q + } +} +`, rName, domain, tagKey1, tagValue1, tagKey2, tagValue2)) +} + +func testAccDomainConfigurationConfig_securityPolicy(rName, rootDomain, domain, securityPolicy string, allowAuthorizerOverride bool) string { + return acctest.ConfigCompose(testAccAuthorizerConfig_basic(rName), testAccDomainConfigurationConfig_base(rootDomain, domain), fmt.Sprintf(` +resource "aws_iot_domain_configuration" "test" { + depends_on = [aws_acm_certificate_validation.test] + + authorizer_config { + allow_authorizer_override = %[4]t + default_authorizer_name = aws_iot_authorizer.test.name + } + + name = %[1]q + domain_name = %[2]q + server_certificate_arns = [aws_acm_certificate.test.arn] + + tls_config { + security_policy = %[3]q + } +} +`, rName, domain, securityPolicy, allowAuthorizerOverride)) +} + +func testAccDomainConfigurationConfig_awsManaged(rName string) string { // nosemgrep:ci.aws-in-func-name + return fmt.Sprintf(` +resource "aws_iot_domain_configuration" "test" { + name = %[1]q +} +`, rName) +} diff --git a/internal/service/iot/service_package_gen.go b/internal/service/iot/service_package_gen.go index 2577b9f4945..a8613b1ab5e 100644 --- a/internal/service/iot/service_package_gen.go +++ b/internal/service/iot/service_package_gen.go @@ -42,6 +42,14 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceCertificate, TypeName: "aws_iot_certificate", }, + { + Factory: ResourceDomainConfiguration, + TypeName: "aws_iot_domain_configuration", + Name: "Domain Configuration", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "arn", + }, + }, { Factory: ResourceIndexingConfiguration, TypeName: "aws_iot_indexing_configuration", diff --git a/internal/service/iot/sweep.go b/internal/service/iot/sweep.go index 2b6159d80dd..5fe51dedf78 100644 --- a/internal/service/iot/sweep.go +++ b/internal/service/iot/sweep.go @@ -6,10 +6,11 @@ package iot import ( "fmt" "log" + "strings" + "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/go-multierror" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/sweep" @@ -19,7 +20,7 @@ import ( func RegisterSweepers() { resource.AddTestSweepers("aws_iot_certificate", &resource.Sweeper{ Name: "aws_iot_certificate", - F: sweepCertifcates, + F: sweepCertificates, Dependencies: []string{ "aws_iot_policy_attachment", "aws_iot_thing_principal_attachment", @@ -76,32 +77,38 @@ func RegisterSweepers() { Name: "aws_iot_topic_rule_destination", F: sweepTopicRuleDestinations, }) + + resource.AddTestSweepers("aws_iot_authorizer", &resource.Sweeper{ + Name: "aws_iot_authorizer", + F: sweepAuthorizers, + Dependencies: []string{"aws_iot_domain_configuration"}, + }) + + resource.AddTestSweepers("aws_iot_domain_configuration", &resource.Sweeper{ + Name: "aws_iot_domain_configuration", + F: sweepDomainConfigurations, + }) } -func sweepCertifcates(region string) error { +func sweepCertificates(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) - if err != nil { return fmt.Errorf("error getting client: %w", err) } - conn := client.IoTConn(ctx) - sweepResources := make([]sweep.Sweepable, 0) - var errs *multierror.Error - input := &iot.ListCertificatesInput{} + sweepResources := make([]sweep.Sweepable, 0) err = conn.ListCertificatesPagesWithContext(ctx, input, func(page *iot.ListCertificatesOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, certificate := range page.Certificates { + for _, v := range page.Certificates { r := ResourceCertificate() d := r.Data(nil) - - d.SetId(aws.StringValue(certificate.CertificateId)) + d.SetId(aws.StringValue(v.CertificateId)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -109,44 +116,44 @@ func sweepCertifcates(region string) error { return !lastPage }) - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing IoT Certificate for %s: %w", region, err)) + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping IoT Certificate sweep for %s: %s", region, err) + return nil } - if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("error sweeping IoT Certificate for %s: %w", region, err)) + if err != nil { + return fmt.Errorf("error listing IoT Certificates (%s): %w", region, err) } - if awsv1.SkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping IoT Certificate sweep for %s: %s", region, errs) - return nil + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping IoT Certificates (%s): %w", region, err) } - return errs.ErrorOrNil() + return nil } func sweepPolicyAttachments(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) - if err != nil { return fmt.Errorf("error getting client: %w", err) } - conn := client.IoTConn(ctx) - sweepResources := make([]sweep.Sweepable, 0) - var errs *multierror.Error - input := &iot.ListPoliciesInput{} + sweepResources := make([]sweep.Sweepable, 0) + var sweeperErrs *multierror.Error err = conn.ListPoliciesPagesWithContext(ctx, input, func(page *iot.ListPoliciesOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, policy := range page.Policies { + for _, v := range page.Policies { + policyName := aws.StringValue(v.PolicyName) input := &iot.ListTargetsForPolicyInput{ - PolicyName: policy.PolicyName, + PolicyName: aws.String(policyName), } err := conn.ListTargetsForPolicyPagesWithContext(ctx, input, func(page *iot.ListTargetsForPolicyOutput, lastPage bool) bool { @@ -154,13 +161,12 @@ func sweepPolicyAttachments(region string) error { return !lastPage } - for _, target := range page.Targets { + for _, v := range page.Targets { r := ResourcePolicyAttachment() d := r.Data(nil) - - d.SetId(fmt.Sprintf("%s|%s", aws.StringValue(policy.PolicyName), aws.StringValue(target))) - d.Set("policy", policy.PolicyName) - d.Set("target", target) + d.SetId(fmt.Sprintf("%s|%s", policyName, aws.StringValue(v))) + d.Set("policy", policyName) + d.Set("target", v) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -168,54 +174,55 @@ func sweepPolicyAttachments(region string) error { return !lastPage }) + if awsv1.SkipSweepError(err) { + continue + } + if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing IoT Policy Attachment for %s: %w", region, err)) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing IoT Targets For Policy (%s): %w", region, err)) } } return !lastPage }) - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing IoT Policy Attachment for %s: %w", region, err)) + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping IoT Policy Attachment sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors } - if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("error sweeping IoT Policy Attachment for %s: %w", region, err)) + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing IoT Policies (%s): %w", region, err)) } - if awsv1.SkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping IoT Policy Attachment sweep for %s: %s", region, errs) - return nil + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping IoT Policy Attachments (%s): %w", region, err)) } - return errs.ErrorOrNil() + return sweeperErrs.ErrorOrNil() } func sweepPolicies(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) - if err != nil { return fmt.Errorf("error getting client: %w", err) } - conn := client.IoTConn(ctx) - sweepResources := make([]sweep.Sweepable, 0) - var errs *multierror.Error - input := &iot.ListPoliciesInput{} + sweepResources := make([]sweep.Sweepable, 0) err = conn.ListPoliciesPagesWithContext(ctx, input, func(page *iot.ListPoliciesOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, policy := range page.Policies { + for _, v := range page.Policies { r := ResourcePolicy() d := r.Data(nil) - - d.SetId(aws.StringValue(policy.PolicyName)) + d.SetId(aws.StringValue(v.PolicyName)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -223,46 +230,43 @@ func sweepPolicies(region string) error { return !lastPage }) - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing IoT Policy for %s: %w", region, err)) + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping IoT Policy sweep for %s: %s", region, err) + return nil } - if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("error sweeping IoT Policy for %s: %w", region, err)) + if err != nil { + return fmt.Errorf("error listing IoT Policies (%s): %w", region, err) } - if awsv1.SkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping IoT Policy sweep for %s: %s", region, errs) - return nil + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping IoT Policies (%s): %w", region, err) } - return errs.ErrorOrNil() + return nil } func sweepRoleAliases(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) - if err != nil { return fmt.Errorf("error getting client: %w", err) } - conn := client.IoTConn(ctx) - sweepResources := make([]sweep.Sweepable, 0) - var errs *multierror.Error - input := &iot.ListRoleAliasesInput{} + sweepResources := make([]sweep.Sweepable, 0) err = conn.ListRoleAliasesPagesWithContext(ctx, input, func(page *iot.ListRoleAliasesOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, roleAlias := range page.RoleAliases { + for _, v := range page.RoleAliases { r := ResourceRoleAlias() d := r.Data(nil) - - d.SetId(aws.StringValue(roleAlias)) + d.SetId(aws.StringValue(v)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -270,44 +274,44 @@ func sweepRoleAliases(region string) error { return !lastPage }) - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing IoT Role Alias for %s: %w", region, err)) + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping Role Alias sweep for %s: %s", region, err) + return nil } - if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("error sweeping IoT Role Alias for %s: %w", region, err)) + if err != nil { + return fmt.Errorf("error listing Role Aliases (%s): %w", region, err) } - if awsv1.SkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping IoT Role Alias sweep for %s: %s", region, errs) - return nil + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping IoT Role Aliases (%s): %w", region, err) } - return errs.ErrorOrNil() + return nil } func sweepThingPrincipalAttachments(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) - if err != nil { return fmt.Errorf("error getting client: %w", err) } - conn := client.IoTConn(ctx) - sweepResources := make([]sweep.Sweepable, 0) - var errs *multierror.Error - input := &iot.ListThingsInput{} + sweepResources := make([]sweep.Sweepable, 0) + var sweeperErrs *multierror.Error err = conn.ListThingsPagesWithContext(ctx, input, func(page *iot.ListThingsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, thing := range page.Things { + for _, v := range page.Things { + thingName := aws.StringValue(v.ThingName) input := &iot.ListThingPrincipalsInput{ - ThingName: thing.ThingName, + ThingName: aws.String(thingName), } err := conn.ListThingPrincipalsPagesWithContext(ctx, input, func(page *iot.ListThingPrincipalsOutput, lastPage bool) bool { @@ -315,13 +319,12 @@ func sweepThingPrincipalAttachments(region string) error { return !lastPage } - for _, principal := range page.Principals { + for _, v := range page.Principals { r := ResourceThingPrincipalAttachment() d := r.Data(nil) - - d.SetId(fmt.Sprintf("%s|%s", aws.StringValue(thing.ThingName), aws.StringValue(principal))) - d.Set("principal", principal) - d.Set("thing", thing.ThingName) + d.SetId(fmt.Sprintf("%s|%s", thingName, aws.StringValue(v))) + d.Set("principal", v) + d.Set("thing", thingName) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -329,54 +332,55 @@ func sweepThingPrincipalAttachments(region string) error { return !lastPage }) + if awsv1.SkipSweepError(err) { + continue + } + if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing IoT Thing Principal Attachment for %s: %w", region, err)) + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing IoT Thing Principals (%s): %w", region, err)) } } return !lastPage }) - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing IoT Thing Principal Attachment for %s: %w", region, err)) + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping IoT Thing Principal Attachment sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors } - if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("error sweeping IoT Thing Principal Attachment for %s: %w", region, err)) + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing IoT Things (%s): %w", region, err)) } - if awsv1.SkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping IoT Thing Principal Attachment sweep for %s: %s", region, errs) - return nil + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping IoT Thing Principal Attachments (%s): %w", region, err)) } - return errs.ErrorOrNil() + return sweeperErrs.ErrorOrNil() } func sweepThings(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) - if err != nil { return fmt.Errorf("error getting client: %w", err) } - conn := client.IoTConn(ctx) - sweepResources := make([]sweep.Sweepable, 0) - var errs *multierror.Error - input := &iot.ListThingsInput{} + sweepResources := make([]sweep.Sweepable, 0) err = conn.ListThingsPagesWithContext(ctx, input, func(page *iot.ListThingsOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, thing := range page.Things { + for _, v := range page.Things { r := ResourceThing() d := r.Data(nil) - - d.SetId(aws.StringValue(thing.ThingName)) + d.SetId(aws.StringValue(v.ThingName)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -384,46 +388,43 @@ func sweepThings(region string) error { return !lastPage }) - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing IoT Thing for %s: %w", region, err)) + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping IoT Thing sweep for %s: %s", region, err) + return nil } - if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("error sweeping IoT Thing for %s: %w", region, err)) + if err != nil { + return fmt.Errorf("error listing IoT Things (%s): %w", region, err) } - if awsv1.SkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping IoT Thing sweep for %s: %s", region, errs) - return nil + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping IoT Things (%s): %w", region, err) } - return errs.ErrorOrNil() + return nil } func sweepThingTypes(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) - if err != nil { return fmt.Errorf("error getting client: %w", err) } - conn := client.IoTConn(ctx) - sweepResources := make([]sweep.Sweepable, 0) - var errs *multierror.Error - input := &iot.ListThingTypesInput{} + sweepResources := make([]sweep.Sweepable, 0) err = conn.ListThingTypesPagesWithContext(ctx, input, func(page *iot.ListThingTypesOutput, lastPage bool) bool { if page == nil { return !lastPage } - for _, thingTypes := range page.ThingTypes { + for _, v := range page.ThingTypes { r := ResourceThingType() d := r.Data(nil) - - d.SetId(aws.StringValue(thingTypes.ThingTypeName)) + d.SetId(aws.StringValue(v.ThingTypeName)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -431,20 +432,22 @@ func sweepThingTypes(region string) error { return !lastPage }) - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("error listing IoT Thing Type for %s: %w", region, err)) + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping IoT Thing Type sweep for %s: %s", region, err) + return nil } - if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("error sweeping IoT Thing Type for %s: %w", region, err)) + if err != nil { + return fmt.Errorf("error listing IoT Thing Types (%s): %w", region, err) } - if awsv1.SkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping IoT Thing Type sweep for %s: %s", region, errs) - return nil + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping IoT Thing Types (%s): %w", region, err) } - return errs.ErrorOrNil() + return nil } func sweepTopicRules(region string) error { @@ -455,44 +458,40 @@ func sweepTopicRules(region string) error { } conn := client.IoTConn(ctx) input := &iot.ListTopicRulesInput{} - var sweeperErrs *multierror.Error + sweepResources := make([]sweep.Sweepable, 0) - for { - output, err := conn.ListTopicRulesWithContext(ctx, input) - if awsv1.SkipSweepError(err) { - log.Printf("[WARN] Skipping IoT Topic Rules sweep for %s: %s", region, err) - return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors - } - if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving IoT Topic Rules: %w", err)) - return sweeperErrs + err = conn.ListTopicRulesPagesWithContext(ctx, input, func(page *iot.ListTopicRulesOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - for _, rule := range output.Rules { - name := aws.StringValue(rule.RuleName) + for _, v := range page.Rules { + r := ResourceTopicRule() + d := r.Data(nil) + d.SetId(aws.StringValue(v.RuleName)) - log.Printf("[INFO] Deleting IoT Topic Rule: %s", name) - _, err := conn.DeleteTopicRuleWithContext(ctx, &iot.DeleteTopicRuleInput{ - RuleName: aws.String(name), - }) - if tfawserr.ErrCodeEquals(err, iot.ErrCodeUnauthorizedException) { - continue - } - if err != nil { - sweeperErr := fmt.Errorf("error deleting IoT Topic Rule (%s): %w", name, err) - log.Printf("[ERROR] %s", sweeperErr) - sweeperErrs = multierror.Append(sweeperErrs, sweeperErr) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } - if aws.StringValue(output.NextToken) == "" { - break - } - input.NextToken = output.NextToken + return !lastPage + }) + + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping IoT Topic Rule sweep for %s: %s", region, err) + return nil } - return sweeperErrs.ErrorOrNil() + if err != nil { + return fmt.Errorf("error listing IoT Topic Rules (%s): %w", region, err) + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping IoT Topic Rules (%s): %w", region, err) + } + + return nil } func sweepThingGroups(region string) error { @@ -510,10 +509,10 @@ func sweepThingGroups(region string) error { return !lastPage } - for _, group := range page.ThingGroups { + for _, v := range page.ThingGroups { r := ResourceThingGroup() d := r.Data(nil) - d.SetId(aws.StringValue(group.GroupName)) + d.SetId(aws.StringValue(v.GroupName)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } @@ -582,3 +581,115 @@ func sweepTopicRuleDestinations(region string) error { return nil } + +func sweepAuthorizers(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.IoTConn(ctx) + input := &iot.ListAuthorizersInput{} + sweepResources := make([]sweep.Sweepable, 0) + + err = conn.ListAuthorizersPagesWithContext(ctx, input, func(page *iot.ListAuthorizersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Authorizers { + r := ResourceAuthorizer() + d := r.Data(nil) + d.SetId(aws.StringValue(v.AuthorizerName)) + d.Set("status", iot.AuthorizerStatusActive) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping IoT Authorizer sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing IoT Authorizers (%s): %w", region, err) + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping IoT Authorizers (%s): %w", region, err) + } + + return nil +} + +func sweepDomainConfigurations(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.IoTConn(ctx) + input := &iot.ListDomainConfigurationsInput{} + sweepResources := make([]sweep.Sweepable, 0) + + err = conn.ListDomainConfigurationsPagesWithContext(ctx, input, func(page *iot.ListDomainConfigurationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.DomainConfigurations { + name := aws.StringValue(v.DomainConfigurationName) + + if strings.HasPrefix(name, "iot:") { + log.Printf("[INFO] Skipping IoT Domain Configuration %s", name) + continue + } + + output, err := FindDomainConfigurationByName(ctx, conn, name) + + if err != nil { + log.Printf("[WARN] IoT Domain Configuration (%s): %s", name, err) + continue + } + + if aws.StringValue(output.DomainType) == iot.DomainTypeAwsManaged && aws.StringValue(output.DomainConfigurationStatus) == iot.DomainConfigurationStatusDisabled { + // AWS Managed Domain Configuration must be disabled for at least 7 days before it can be deleted. + if output.LastStatusChangeDate.After(time.Now().AddDate(0, 0, -7)) { + log.Printf("[INFO] Skipping IoT Domain Configuration %s", name) + continue + } + } + + r := ResourceDomainConfiguration() + d := r.Data(nil) + d.SetId(name) + d.Set("status", output.DomainConfigurationStatus) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if awsv1.SkipSweepError(err) { + log.Printf("[WARN] Skipping IoT Domain Configuration sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing IoT Domain Configurations (%s): %w", region, err) + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping IoT Domain Configurations (%s): %w", region, err) + } + + return nil +} diff --git a/website/docs/r/iot_domain_configuration.html.markdown b/website/docs/r/iot_domain_configuration.html.markdown new file mode 100644 index 00000000000..bc92dbc89d1 --- /dev/null +++ b/website/docs/r/iot_domain_configuration.html.markdown @@ -0,0 +1,71 @@ +--- +subcategory: "IoT Core" +layout: "aws" +page_title: "AWS: aws_iot_domain_configuration" +description: |- + Creates and manages an AWS IoT domain configuration. +--- + +# Resource: aws_iot_domain_configuration + +Creates and manages an AWS IoT domain configuration. + +## Example Usage + +```terraform +resource "aws_iot_domain_configuration" "iot" { + name = "iot-" + domain_name = "iot.example.com" + service_type = "DATA" + + server_certificate_arns = [ + aws_acm_certificate.cert.arn + ] +} +``` + +## Argument Reference + +* `authorizer_config` - (Optional) An object that specifies the authorization service for a domain. See below. +* `domain_name` - (Optional) Fully-qualified domain name. +* `name` - (Required) The name of the domain configuration. This value must be unique to a region. +* `server_certificate_arns` - (Optional) The ARNs of the certificates that IoT passes to the device during the TLS handshake. Currently you can specify only one certificate ARN. This value is not required for Amazon Web Services-managed domains. When using a custom `domain_name`, the cert must include it. +* `service_type` - (Optional) The type of service delivered by the endpoint. Note: Amazon Web Services IoT Core currently supports only the `DATA` service type. +* `tags` - (Optional) Map of tags to assign to this resource. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +* `tls_config` - (Optional) An object that specifies the TLS configuration for a domain. See below. +* `validation_certificate_arn` - (Optional) The certificate used to validate the server certificate and prove domain name ownership. This certificate must be signed by a public certificate authority. This value is not required for Amazon Web Services-managed domains. + +### authorizer_config + +* `allow_authorizer_override` - (Optional) A Boolean that specifies whether the domain configuration's authorization service can be overridden. +* `default_authorizer_name` - (Optional) The name of the authorization service for a domain configuration. + +### tls_config + +* `security_policy` - (Optional) The security policy for a domain configuration. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - The ARN of the domain configuration. +* `domain_type` - The type of the domain. +* `id` - The name of the created domain configuration. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import IOT domain configurations using the name. For example: + +```terraform +import { + to = aws_iot_domain_configuration.example + id = "example" +} +``` + +Using `terraform import`, import domain configurations using the name. For example: + +```console +% terraform import aws_iot_domain_configuration.example example +```