diff --git a/GNUmakefile b/GNUmakefile index 0e0c99864bd..ecb7cc5b88c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -191,4 +191,4 @@ semgrep: @echo "==> Running Semgrep static analysis..." @docker run --rm --volume "${PWD}:/src" returntocorp/semgrep --config .semgrep.yml -.PHONY: providerlint build gen generate-changelog gh-workflows-lint golangci-lint sweep test testacc fmt fmtcheck lint tools test-compile website-link-check website-lint website-lint-fix depscheck docscheck semgrep +.PHONY: providerlint build gen generate-changelog gh-workflows-lint golangci-lint sweep test testacc fmt fmtcheck lint tools test-compile website-link-check website-lint website-lint-fix depscheck docscheck semgrep \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e83cee508f3..d4464fe2e95 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1550,6 +1550,7 @@ func Provider() *schema.Provider { "aws_iot_authorizer": iot.ResourceAuthorizer(), "aws_iot_certificate": iot.ResourceCertificate(), + "aws_iot_domain_name_configuration": iot.ResourceDomainNameConfiguration(), "aws_iot_indexing_configuration": iot.ResourceIndexingConfiguration(), "aws_iot_logging_options": iot.ResourceLoggingOptions(), "aws_iot_policy": iot.ResourcePolicy(), diff --git a/internal/service/iot/domain_name_configuration.go b/internal/service/iot/domain_name_configuration.go new file mode 100644 index 00000000000..9fbb54662ad --- /dev/null +++ b/internal/service/iot/domain_name_configuration.go @@ -0,0 +1,234 @@ +package iot + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iot" + "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" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceDomainNameConfiguration() *schema.Resource { + return &schema.Resource{ + Create: resourceDomainNameConfigurationCreate, + Read: resourceDomainNameConfigurationRead, + Update: resourceDomainNameConfigurationUpdate, + Delete: resourceDomainNameConfigurationDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "domain_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "server_certificate_arns": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: verify.ValidARN, + }, + Optional: true, + ForceNew: true, + }, + "service_type": { + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "DATA", + "CREDENTIAL_PROVIDER", + "JOBS", + }, false), + Optional: true, + ForceNew: true, + Default: "DATA", + }, + "validation_certificate_arn": { + Type: schema.TypeString, + ValidateFunc: verify.ValidARN, + Optional: true, + ForceNew: 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, + }, + }, + }, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "status": { + Type: schema.TypeString, + Optional: true, + Default: "ENABLED", + ValidateFunc: validation.StringInSlice([]string{ + "ENABLED", + "DISABLED", + }, false), + }, + }, + } +} + +func resourceDomainNameConfigurationCreate(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{}))) + + name := d.Get("name").(string) + + apiObject := &iot.CreateDomainConfigurationInput{ + DomainConfigurationName: aws.String(name), + } + + if v, ok := d.GetOk("domain_name"); ok { + apiObject.DomainName = aws.String(v.(string)) + } + + if v, ok := d.GetOk("server_certificate_arns"); ok { + apiObject.ServerCertificateArns = flex.ExpandStringList(v.([]interface{})) + } + + if v, ok := d.GetOk("service_type"); ok { + apiObject.ServiceType = aws.String(v.(string)) + } + + if v, ok := d.GetOk("validation_certificate_arn"); ok { + apiObject.ValidationCertificateArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("authorizer_config"); ok { + apiObject.AuthorizerConfig = expandIotDomainNameConfigurationAuthorizerConfig(v.([]interface{})) + } + + if len(tags) > 0 { + apiObject.Tags = Tags(tags.IgnoreAWS()) + } + + log.Printf("[DEBUG] Creating IoT Domain Configuration: %s", name) + output, err := conn.CreateDomainConfiguration(apiObject) + + if err != nil { + return fmt.Errorf("error creating IoT Domain Configuration (%s): %s", name, err) + } + + d.SetId(aws.StringValue(output.DomainConfigurationName)) + + return resourceDomainNameConfigurationRead(d, meta) +} + +func expandIotDomainNameConfigurationAuthorizerConfig(l []interface{}) *iot.AuthorizerConfig { + if len(l) < 1 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + iotAuthorizerConfig := &iot.AuthorizerConfig{ + AllowAuthorizerOverride: aws.Bool(m["allow_authorizer_override"].(bool)), + DefaultAuthorizerName: aws.String(m["allow_authorizer_override"].(string)), + } + + return iotAuthorizerConfig +} + +func resourceDomainNameConfigurationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).IoTConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + + out, err := conn.DescribeDomainConfiguration(&iot.DescribeDomainConfigurationInput{ + DomainConfigurationName: aws.String(d.Id()), + }) + if err != nil { + return fmt.Errorf("error reading domain details: %v", err) + } + + d.Set("arn", out.DomainConfigurationArn) + + tags, err := ListTags(conn, d.Get("arn").(string)) + + //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("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + + return nil +} + +func resourceDomainNameConfigurationUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).IoTConn + + input := iot.UpdateDomainConfigurationInput{ + DomainConfigurationName: aws.String(d.Id()), + } + + if d.HasChange("authorizer_config") { + input.AuthorizerConfig = expandIotDomainNameConfigurationAuthorizerConfig(d.Get("authorizer_config").([]interface{})) + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating IoT Domain Name Configuration (%s) tags: %w", d.Id(), err) + } + } + + log.Printf("[INFO] Updating IoT Domain Configuration: %s", d.Id()) + _, err := conn.UpdateDomainConfiguration(&input) + + if err != nil { + return fmt.Errorf("error updating IoT Domain Configuration (%s): %w", d.Id(), err) + } + + return resourceCertificateRead(d, meta) +} + +func resourceDomainNameConfigurationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).IoTConn + + if d.Get("status").(string) == "ENABLED" { + log.Printf("[INFO] Disabling IoT Domain Configuration: %s", d.Id()) + _, err := conn.UpdateDomainConfiguration(&iot.UpdateDomainConfigurationInput{ + DomainConfigurationName: aws.String(d.Id()), + DomainConfigurationStatus: aws.String("DISABLED"), + }) + + if err != nil { + return fmt.Errorf("error disabling IoT Domain Configuration (%s): %s", d.Id(), err) + } + } + + _, err := conn.DeleteDomainConfiguration(&iot.DeleteDomainConfigurationInput{ + DomainConfigurationName: aws.String(d.Id()), + }) + + if err != nil { + return fmt.Errorf("error deleting certificate: %v", err) + } + + return nil +} diff --git a/internal/service/iot/domain_name_configuration_test.go b/internal/service/iot/domain_name_configuration_test.go new file mode 100644 index 00000000000..a549e165c7d --- /dev/null +++ b/internal/service/iot/domain_name_configuration_test.go @@ -0,0 +1,40 @@ +package iot_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/iot" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccIotDomainNameConfiguration_main(t *testing.T) { + resourceName := "aws_iot_domain_name_configuration.test" + domain := acctest.RandomDomainName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccIotDomainNameConfig(domain), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "status", "ENABLED"), + ), + }, + }, + }) +} + +func testAccIotDomainNameConfig(domain string) string { + return acctest.ConfigCompose(fmt.Sprintf(` +resource "aws_iot_domain_name_configuration" "test" { + name = "test" + domain_name = "%[1]s" + service_type = "DATA" +} +`, domain)) +} diff --git a/website/docs/r/iot_domain_name_configuration.html.markdown b/website/docs/r/iot_domain_name_configuration.html.markdown new file mode 100644 index 00000000000..cd109419bdc --- /dev/null +++ b/website/docs/r/iot_domain_name_configuration.html.markdown @@ -0,0 +1,47 @@ +--- +subcategory: "IoT Core" +layout: "aws" +page_title: "AWS: aws_iot_domain_name_configuration" +description: |- + Creates and manages an AWS IoT domain name configuration. +--- + +# Resource: aws_iot_domain_name_configuration + +Creates and manages an AWS IoT domain name configuration. + +## Example Usage + +```terraform +resource "aws_iot_domain_name_configuration" "iot" { + name = "iot-" + domain_name = "iot.example.com" + service_type = "DATA" + server_certificate_arns = [ + aws_acm_certificate.cert.arn + ] +} +``` + + +## Argument Reference + +* `name` = (Required) The name of the domain configuration. This value must be unique to a region. +* `domain_name` = (Required) Fully-qualified domain name. +* `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. +* `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` = (Optional) an object that specifies the authorization service for a domain. See Below. +* `tags` = (Optional) Key-value map of resource tags. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level. + +### 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. + + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `name` - The name of the created domain name configuration.