From 2e0a1dfe50d0248c207997fdc90c198ad5d42982 Mon Sep 17 00:00:00 2001 From: Oleg Vaskevich Date: Thu, 20 Feb 2020 11:49:00 -0800 Subject: [PATCH 01/20] Add aws_iot_provisioning_template resource --- aws/provider.go | 1 + aws/resource_aws_iot_provisioning_template.go | 152 +++++++++++++++++ ...urce_aws_iot_provisioning_template_test.go | 156 ++++++++++++++++++ .../r/iot_provisioning_template.html.markdown | 77 +++++++++ 4 files changed, 386 insertions(+) create mode 100644 aws/resource_aws_iot_provisioning_template.go create mode 100644 aws/resource_aws_iot_provisioning_template_test.go create mode 100644 website/docs/r/iot_provisioning_template.html.markdown diff --git a/aws/provider.go b/aws/provider.go index d2ea8a71fb32..9a870a873bdc 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -587,6 +587,7 @@ func Provider() terraform.ResourceProvider { "aws_iot_certificate": resourceAwsIotCertificate(), "aws_iot_policy": resourceAwsIotPolicy(), "aws_iot_policy_attachment": resourceAwsIotPolicyAttachment(), + "aws_iot_provisioning_template": resourceAwsIotProvisioningTemplate(), "aws_iot_thing": resourceAwsIotThing(), "aws_iot_thing_principal_attachment": resourceAwsIotThingPrincipalAttachment(), "aws_iot_thing_type": resourceAwsIotThingType(), diff --git a/aws/resource_aws_iot_provisioning_template.go b/aws/resource_aws_iot_provisioning_template.go new file mode 100644 index 000000000000..ed43bfc719a7 --- /dev/null +++ b/aws/resource_aws_iot_provisioning_template.go @@ -0,0 +1,152 @@ +package aws + +import ( + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iot" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceAwsIotProvisioningTemplate() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsIotProvisioningTemplateCreate, + Read: resourceAwsIotProvisioningTemplateRead, + Update: resourceAwsIotProvisioningTemplateUpdate, + Delete: resourceAwsIotProvisioningTemplateDelete, + Schema: map[string]*schema.Schema{ + "default_version_id": { + Type: schema.TypeInt, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "provisioning_role_arn": { + Type: schema.TypeString, + Required: true, + }, + "template_arn": { + Type: schema.TypeString, + Computed: true, + }, + "template_body": { + Type: schema.TypeString, + Required: true, + }, + "template_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsIotProvisioningTemplateCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iotconn + + var out *iot.CreateProvisioningTemplateOutput + err := resource.Retry(1*time.Minute, func() *resource.RetryError { + var err error + out, err = conn.CreateProvisioningTemplate(&iot.CreateProvisioningTemplateInput{ + Description: aws.String(d.Get("description").(string)), + Enabled: aws.Bool(d.Get("enabled").(bool)), + ProvisioningRoleArn: aws.String(d.Get("provisioning_role_arn").(string)), + TemplateBody: aws.String(d.Get("template_body").(string)), + TemplateName: aws.String(d.Get("template_name").(string)), + }) + + // Handle IoT not detecting the provisioning role's assume role policy immediately. + if isAWSErr(err, iot.ErrCodeInvalidRequestException, "The provisioning role cannot be assumed by AWS IoT") { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + }) + + if err != nil { + log.Printf("[ERROR] %s", err) + return err + } + + d.SetId(*out.TemplateName) + + return resourceAwsIotProvisioningTemplateRead(d, meta) +} + +func resourceAwsIotProvisioningTemplateRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iotconn + + out, err := conn.DescribeProvisioningTemplate(&iot.DescribeProvisioningTemplateInput{ + TemplateName: aws.String(d.Id()), + }) + + if err != nil { + log.Printf("[ERROR] %s", err) + return err + } + + d.Set("default_version_id", out.DefaultVersionId) + d.Set("description", out.Description) + d.Set("enabled", out.Enabled) + d.Set("provisioning_role_arn", out.ProvisioningRoleArn) + d.Set("template_arn", out.TemplateArn) + d.Set("template_body", out.TemplateBody) + d.Set("template_name", out.TemplateName) + + return nil +} + +func resourceAwsIotProvisioningTemplateUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iotconn + + if d.HasChange("template_body") { + _, err := conn.CreateProvisioningTemplateVersion(&iot.CreateProvisioningTemplateVersionInput{ + TemplateName: aws.String(d.Id()), + TemplateBody: aws.String(d.Get("template_body").(string)), + SetAsDefault: aws.Bool(true), + }) + + if err != nil { + log.Printf("[ERROR] %s", err) + return err + } + } + + _, err := conn.UpdateProvisioningTemplate(&iot.UpdateProvisioningTemplateInput{ + Description: aws.String(d.Get("description").(string)), + Enabled: aws.Bool(d.Get("enabled").(bool)), + ProvisioningRoleArn: aws.String(d.Get("provisioning_role_arn").(string)), + TemplateName: aws.String(d.Id()), + }) + + if err != nil { + log.Printf("[ERROR] %s", err) + return err + } + + return resourceAwsIotProvisioningTemplateRead(d, meta) +} + +func resourceAwsIotProvisioningTemplateDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).iotconn + + _, err := conn.DeleteProvisioningTemplate(&iot.DeleteProvisioningTemplateInput{ + TemplateName: aws.String(d.Id()), + }) + + if err != nil { + log.Printf("[ERROR] %s", err) + return err + } + + return nil +} diff --git a/aws/resource_aws_iot_provisioning_template_test.go b/aws/resource_aws_iot_provisioning_template_test.go new file mode 100644 index 000000000000..cdd919db19c1 --- /dev/null +++ b/aws/resource_aws_iot_provisioning_template_test.go @@ -0,0 +1,156 @@ +package aws + +import ( + "fmt" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iot" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAWSIoTProvisioningTemplate_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("Fleet-") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSIoTProvisioningTemplateDestroy_basic, + Steps: []resource.TestStep{ + { + Config: testAccAWSIoTProvisioningTemplateConfigInitialState(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "default_version_id", "1"), + resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "description", "My provisioning template"), + resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "enabled", "false"), + resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "provisioning_role_arn"), + resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_arn"), + resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "template_name", rName), + resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_body"), + testAccAWSIoTProvisioningTemplateCheckVersionExists(rName, 1), + ), + }, + { + Config: testAccAWSIoTProvisioningTemplateConfigTemplateBodyUpdate(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "default_version_id", "2"), + resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_body"), + testAccAWSIoTProvisioningTemplateCheckVersionExists(rName, 2), + ), + }, + }, + }) +} + +func testAccCheckAWSIoTProvisioningTemplateDestroy_basic(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).iotconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iot_provisioning_template" { + continue + } + + _, err := conn.DescribeProvisioningTemplate(&iot.DescribeProvisioningTemplateInput{ + TemplateName: aws.String(rs.Primary.Attributes["template_name"]), + }) + + if isAWSErr(err, iot.ErrCodeResourceNotFoundException, "") { + return nil + } else if err != nil { + return err + } + + return fmt.Errorf("IoT Provisioning Template still exists") + } + + return nil +} + +func testAccAWSIoTProvisioningTemplateCheckVersionExists(templateName string, numVersions int) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).iotconn + + resp, err := conn.ListProvisioningTemplateVersions(&iot.ListProvisioningTemplateVersionsInput{ + TemplateName: aws.String(templateName), + }) + + if err != nil { + return err + } + + if len(resp.Versions) != numVersions { + return fmt.Errorf("Expected %d versions for template %s but found %d", numVersions, templateName, len(resp.Versions)) + } + + return nil + } +} + +func testAccAWSIoTProvisioningTemplateConfigInitialState(rName string) string { + return fmt.Sprintf(` +data "aws_iam_policy_document" "iot_assume_role_policy" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["iot.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "iot_fleet_provisioning" { + name = "IoTProvisioningServiceRole" + path = "/service-role/" + assume_role_policy = data.aws_iam_policy_document.iot_assume_role_policy.json +} + +resource "aws_iam_role_policy_attachment" "iot_fleet_provisioning_registration" { + role = aws_iam_role.iot_fleet_provisioning.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration" +} + +resource "aws_iot_provisioning_template" "fleet" { + template_name = "%s" + description = "My provisioning template" + provisioning_role_arn = aws_iam_role.iot_fleet_provisioning.arn + + template_body = < **NOTE:** The fleet provisioning feature is in beta and is subject to change. + +## Example Usage + +```hcl +resource "aws_iam_policy_document" "iot_assume_role_policy" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["iot.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "iot_fleet_provisioning" { + name = "IoTProvisioningServiceRole" + path = "/service-role/" + assume_role_policy = data.aws_iam_policy_document.iot_assume_role_policy.json +} + +resource "aws_iam_role_policy_attachment" "iot_fleet_provisioning_registration" { + role = aws_iam_role.iot_fleet_provisioning.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration" +} + +resource "aws_iot_provisioning_template" "fleet" { + template_name = "FleetProvisioningTemplate" + description = "My fleet provisioning template" + provisioning_role_arn = aws_iam_role.iot_fleet_provisioning + + template_body = < Date: Thu, 20 Feb 2020 12:43:28 -0800 Subject: [PATCH 02/20] Support importing --- aws/resource_aws_iot_provisioning_template.go | 5 ++++ ...urce_aws_iot_provisioning_template_test.go | 25 ++++++++++++------- .../r/iot_provisioning_template.html.markdown | 8 ++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/aws/resource_aws_iot_provisioning_template.go b/aws/resource_aws_iot_provisioning_template.go index ed43bfc719a7..11f0ab8242d9 100644 --- a/aws/resource_aws_iot_provisioning_template.go +++ b/aws/resource_aws_iot_provisioning_template.go @@ -16,6 +16,11 @@ func resourceAwsIotProvisioningTemplate() *schema.Resource { Read: resourceAwsIotProvisioningTemplateRead, Update: resourceAwsIotProvisioningTemplateUpdate, Delete: resourceAwsIotProvisioningTemplateDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ "default_version_id": { Type: schema.TypeInt, diff --git a/aws/resource_aws_iot_provisioning_template_test.go b/aws/resource_aws_iot_provisioning_template_test.go index cdd919db19c1..c6f614cc22d9 100644 --- a/aws/resource_aws_iot_provisioning_template_test.go +++ b/aws/resource_aws_iot_provisioning_template_test.go @@ -13,6 +13,7 @@ import ( ) func TestAccAWSIoTProvisioningTemplate_basic(t *testing.T) { + resourceName := "aws_iot_provisioning_template.fleet" rName := acctest.RandomWithPrefix("Fleet-") resource.ParallelTest(t, resource.TestCase{ @@ -23,21 +24,27 @@ func TestAccAWSIoTProvisioningTemplate_basic(t *testing.T) { { Config: testAccAWSIoTProvisioningTemplateConfigInitialState(rName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "default_version_id", "1"), - resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "description", "My provisioning template"), - resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "enabled", "false"), - resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "provisioning_role_arn"), - resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_arn"), - resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "template_name", rName), - resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_body"), + resource.TestCheckResourceAttr(resourceName, "default_version_id", "1"), + resource.TestCheckResourceAttr(resourceName, "description", "My provisioning template"), + resource.TestCheckResourceAttr(resourceName, "enabled", "false"), + resource.TestCheckResourceAttrSet(resourceName, "provisioning_role_arn"), + resource.TestCheckResourceAttrSet(resourceName, "template_arn"), + resource.TestCheckResourceAttr(resourceName, "template_name", rName), + resource.TestCheckResourceAttrSet(resourceName, "template_body"), testAccAWSIoTProvisioningTemplateCheckVersionExists(rName, 1), ), }, + { + Config: testAccAWSIoTProvisioningTemplateConfigInitialState(rName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccAWSIoTProvisioningTemplateConfigTemplateBodyUpdate(rName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "default_version_id", "2"), - resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_body"), + resource.TestCheckResourceAttr(resourceName, "default_version_id", "2"), + resource.TestCheckResourceAttrSet(resourceName, "template_body"), testAccAWSIoTProvisioningTemplateCheckVersionExists(rName, 2), ), }, diff --git a/website/docs/r/iot_provisioning_template.html.markdown b/website/docs/r/iot_provisioning_template.html.markdown index ba2098886988..c1da01391a76 100644 --- a/website/docs/r/iot_provisioning_template.html.markdown +++ b/website/docs/r/iot_provisioning_template.html.markdown @@ -75,3 +75,11 @@ In addition to all arguments above, the following attributes are exported: * `default_version_id` - The default version of the fleet provisioning template. * `template_arn` - The ARN that identifies the provisioning template. + +## Import + +IoT fleet provisioning templates can be imported using the `template_name`, e.g. + +``` +$ terraform import aws_iot_provisioning_template.fleet FleetProvisioningTemplate +``` From 1bbf5aa6b9ad6b49765da529e6437001bb08ebfd Mon Sep 17 00:00:00 2001 From: Oleg Vaskevich Date: Thu, 20 Feb 2020 15:29:23 -0800 Subject: [PATCH 03/20] Update website index --- website/aws.erb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/aws.erb b/website/aws.erb index 359187c75d01..87d67661be9f 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1821,6 +1821,9 @@
  • aws_iot_policy_attachment
  • +
  • + aws_iot_provisioning_template +
  • aws_iot_topic_rule
  • From 7071637e009be5ca0635fcbac4f6d74a4f9623f0 Mon Sep 17 00:00:00 2001 From: Oleg Vaskevich Date: Thu, 20 Feb 2020 15:34:58 -0800 Subject: [PATCH 04/20] Fix spacing, caps --- aws/resource_aws_iot_provisioning_template_test.go | 4 ++-- website/docs/r/iot_provisioning_template.html.markdown | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aws/resource_aws_iot_provisioning_template_test.go b/aws/resource_aws_iot_provisioning_template_test.go index c6f614cc22d9..f8dfcb0c554d 100644 --- a/aws/resource_aws_iot_provisioning_template_test.go +++ b/aws/resource_aws_iot_provisioning_template_test.go @@ -110,8 +110,8 @@ data "aws_iam_policy_document" "iot_assume_role_policy" { } resource "aws_iam_role" "iot_fleet_provisioning" { - name = "IoTProvisioningServiceRole" - path = "/service-role/" + name = "IoTProvisioningServiceRole" + path = "/service-role/" assume_role_policy = data.aws_iam_policy_document.iot_assume_role_policy.json } diff --git a/website/docs/r/iot_provisioning_template.html.markdown b/website/docs/r/iot_provisioning_template.html.markdown index c1da01391a76..97eac67b8c15 100644 --- a/website/docs/r/iot_provisioning_template.html.markdown +++ b/website/docs/r/iot_provisioning_template.html.markdown @@ -3,12 +3,12 @@ subcategory: "IoT" layout: "aws" page_title: "AWS: aws_iot_provisioning_template" description: |- - Creates an IoT Fleet Provisioning template. + Creates an IoT fleet provisioning template. --- # Resource: aws_iot_provisioning_template -Creates an IoT Fleet Provisioning template. For more info, see the AWS documentation on [Fleet Provisioning](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html). +Creates an IoT fleet provisioning template. For more info, see the AWS documentation on [fleet provisioning](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html). ~> **NOTE:** The fleet provisioning feature is in beta and is subject to change. @@ -27,8 +27,8 @@ resource "aws_iam_policy_document" "iot_assume_role_policy" { } resource "aws_iam_role" "iot_fleet_provisioning" { - name = "IoTProvisioningServiceRole" - path = "/service-role/" + name = "IoTProvisioningServiceRole" + path = "/service-role/" assume_role_policy = data.aws_iam_policy_document.iot_assume_role_policy.json } From ffc3e586f2069771f3f86ee08cbf829fa96a31b7 Mon Sep 17 00:00:00 2001 From: Oleg Vaskevich Date: Sat, 22 Feb 2020 02:34:34 -0800 Subject: [PATCH 05/20] Fix docs --- website/docs/r/iot_provisioning_template.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/iot_provisioning_template.html.markdown b/website/docs/r/iot_provisioning_template.html.markdown index 97eac67b8c15..a5de2b2e9a73 100644 --- a/website/docs/r/iot_provisioning_template.html.markdown +++ b/website/docs/r/iot_provisioning_template.html.markdown @@ -15,7 +15,7 @@ Creates an IoT fleet provisioning template. For more info, see the AWS documenta ## Example Usage ```hcl -resource "aws_iam_policy_document" "iot_assume_role_policy" { +data "aws_iam_policy_document" "iot_assume_role_policy" { statement { actions = ["sts:AssumeRole"] From e1737d7952a338c8e2940dca1f35c03e230b9715 Mon Sep 17 00:00:00 2001 From: Oleg Vaskevich Date: Sat, 22 Feb 2020 19:03:16 -0800 Subject: [PATCH 06/20] Prettify tests and docs --- ...urce_aws_iot_provisioning_template_test.go | 66 +++++++++++-------- .../r/iot_provisioning_template.html.markdown | 57 +++++++++++----- 2 files changed, 77 insertions(+), 46 deletions(-) diff --git a/aws/resource_aws_iot_provisioning_template_test.go b/aws/resource_aws_iot_provisioning_template_test.go index f8dfcb0c554d..61cba405f5be 100644 --- a/aws/resource_aws_iot_provisioning_template_test.go +++ b/aws/resource_aws_iot_provisioning_template_test.go @@ -120,44 +120,52 @@ resource "aws_iam_role_policy_attachment" "iot_fleet_provisioning_registration" policy_arn = "arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration" } +data "aws_iam_policy_document" "device_policy" { + statement { + actions = ["iot:Subscribe"] + resources = ["*"] + } +} + +resource "aws_iot_policy" "device_policy" { + name = "DevicePolicy" + policy = data.aws_iam_policy_document.device_policy.json +} + resource "aws_iot_provisioning_template" "fleet" { template_name = "%s" description = "My provisioning template" provisioning_role_arn = aws_iam_role.iot_fleet_provisioning.arn - template_body = < Date: Tue, 25 Feb 2020 01:05:43 -0800 Subject: [PATCH 07/20] Handle profile change on update; optimize update --- aws/resource_aws_iot_provisioning_template.go | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/aws/resource_aws_iot_provisioning_template.go b/aws/resource_aws_iot_provisioning_template.go index 11f0ab8242d9..76db083b66af 100644 --- a/aws/resource_aws_iot_provisioning_template.go +++ b/aws/resource_aws_iot_provisioning_template.go @@ -126,16 +126,26 @@ func resourceAwsIotProvisioningTemplateUpdate(d *schema.ResourceData, meta inter } } - _, err := conn.UpdateProvisioningTemplate(&iot.UpdateProvisioningTemplateInput{ - Description: aws.String(d.Get("description").(string)), - Enabled: aws.Bool(d.Get("enabled").(bool)), - ProvisioningRoleArn: aws.String(d.Get("provisioning_role_arn").(string)), - TemplateName: aws.String(d.Id()), - }) + if d.HasChanges("description", "enabled", "provisioning_role_arn") { + err := resource.Retry(1*time.Minute, func() *resource.RetryError { + _, err := conn.UpdateProvisioningTemplate(&iot.UpdateProvisioningTemplateInput{ + Description: aws.String(d.Get("description").(string)), + Enabled: aws.Bool(d.Get("enabled").(bool)), + ProvisioningRoleArn: aws.String(d.Get("provisioning_role_arn").(string)), + TemplateName: aws.String(d.Id()), + }) + + // Handle IoT not detecting the provisioning role's assume role policy immediately. + if isAWSErr(err, iot.ErrCodeInvalidRequestException, "The provisioning role cannot be assumed by AWS IoT") { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + }) - if err != nil { - log.Printf("[ERROR] %s", err) - return err + if err != nil { + log.Printf("[ERROR] %s", err) + return err + } } return resourceAwsIotProvisioningTemplateRead(d, meta) From 73afad6d82d26e87ed3d53522842b4e172441914 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 13:58:28 -0400 Subject: [PATCH 08/20] Revert "Handle profile change on update; optimize update" This reverts commit ae2f056de4fe0c1d0c2ce8838f5049867aae0265. --- aws/resource_aws_iot_provisioning_template.go | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/aws/resource_aws_iot_provisioning_template.go b/aws/resource_aws_iot_provisioning_template.go index 76db083b66af..11f0ab8242d9 100644 --- a/aws/resource_aws_iot_provisioning_template.go +++ b/aws/resource_aws_iot_provisioning_template.go @@ -126,26 +126,16 @@ func resourceAwsIotProvisioningTemplateUpdate(d *schema.ResourceData, meta inter } } - if d.HasChanges("description", "enabled", "provisioning_role_arn") { - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - _, err := conn.UpdateProvisioningTemplate(&iot.UpdateProvisioningTemplateInput{ - Description: aws.String(d.Get("description").(string)), - Enabled: aws.Bool(d.Get("enabled").(bool)), - ProvisioningRoleArn: aws.String(d.Get("provisioning_role_arn").(string)), - TemplateName: aws.String(d.Id()), - }) - - // Handle IoT not detecting the provisioning role's assume role policy immediately. - if isAWSErr(err, iot.ErrCodeInvalidRequestException, "The provisioning role cannot be assumed by AWS IoT") { - return resource.RetryableError(err) - } - return resource.NonRetryableError(err) - }) + _, err := conn.UpdateProvisioningTemplate(&iot.UpdateProvisioningTemplateInput{ + Description: aws.String(d.Get("description").(string)), + Enabled: aws.Bool(d.Get("enabled").(bool)), + ProvisioningRoleArn: aws.String(d.Get("provisioning_role_arn").(string)), + TemplateName: aws.String(d.Id()), + }) - if err != nil { - log.Printf("[ERROR] %s", err) - return err - } + if err != nil { + log.Printf("[ERROR] %s", err) + return err } return resourceAwsIotProvisioningTemplateRead(d, meta) From 234610de381d4ba5310368938fd274cc15896a5a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 13:58:39 -0400 Subject: [PATCH 09/20] Revert "Prettify tests and docs" This reverts commit e1737d7952a338c8e2940dca1f35c03e230b9715. --- ...urce_aws_iot_provisioning_template_test.go | 66 ++++++++----------- .../r/iot_provisioning_template.html.markdown | 57 +++++----------- 2 files changed, 46 insertions(+), 77 deletions(-) diff --git a/aws/resource_aws_iot_provisioning_template_test.go b/aws/resource_aws_iot_provisioning_template_test.go index 61cba405f5be..f8dfcb0c554d 100644 --- a/aws/resource_aws_iot_provisioning_template_test.go +++ b/aws/resource_aws_iot_provisioning_template_test.go @@ -120,52 +120,44 @@ resource "aws_iam_role_policy_attachment" "iot_fleet_provisioning_registration" policy_arn = "arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration" } -data "aws_iam_policy_document" "device_policy" { - statement { - actions = ["iot:Subscribe"] - resources = ["*"] - } -} - -resource "aws_iot_policy" "device_policy" { - name = "DevicePolicy" - policy = data.aws_iam_policy_document.device_policy.json -} - resource "aws_iot_provisioning_template" "fleet" { template_name = "%s" description = "My provisioning template" provisioning_role_arn = aws_iam_role.iot_fleet_provisioning.arn - template_body = jsonencode({ - Parameters = { - "AWS::IoT::Certificate::Id" = { Type = "String" } - SerialNumber = { Type = "String" } - } - - Resources = { - certificate = { - Properties = { - CertificateId = { Ref = "AWS::IoT::Certificate::Id" } - Status = "Active" - } - - Type = "AWS::IoT::Certificate" - } - - policy = { - Properties = { - PolicyName = aws_iot_policy.device_policy.name - } - - Type = "AWS::IoT::Policy" - } + template_body = < Date: Fri, 1 Apr 2022 13:58:49 -0400 Subject: [PATCH 10/20] Revert "Fix docs" This reverts commit ffc3e586f2069771f3f86ee08cbf829fa96a31b7. --- website/docs/r/iot_provisioning_template.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/iot_provisioning_template.html.markdown b/website/docs/r/iot_provisioning_template.html.markdown index a5de2b2e9a73..97eac67b8c15 100644 --- a/website/docs/r/iot_provisioning_template.html.markdown +++ b/website/docs/r/iot_provisioning_template.html.markdown @@ -15,7 +15,7 @@ Creates an IoT fleet provisioning template. For more info, see the AWS documenta ## Example Usage ```hcl -data "aws_iam_policy_document" "iot_assume_role_policy" { +resource "aws_iam_policy_document" "iot_assume_role_policy" { statement { actions = ["sts:AssumeRole"] From 86404478ae4147dabbacc50fe5155d265d331f34 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 13:59:06 -0400 Subject: [PATCH 11/20] Revert "Fix spacing, caps" This reverts commit 7071637e009be5ca0635fcbac4f6d74a4f9623f0. --- aws/resource_aws_iot_provisioning_template_test.go | 4 ++-- website/docs/r/iot_provisioning_template.html.markdown | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aws/resource_aws_iot_provisioning_template_test.go b/aws/resource_aws_iot_provisioning_template_test.go index f8dfcb0c554d..c6f614cc22d9 100644 --- a/aws/resource_aws_iot_provisioning_template_test.go +++ b/aws/resource_aws_iot_provisioning_template_test.go @@ -110,8 +110,8 @@ data "aws_iam_policy_document" "iot_assume_role_policy" { } resource "aws_iam_role" "iot_fleet_provisioning" { - name = "IoTProvisioningServiceRole" - path = "/service-role/" + name = "IoTProvisioningServiceRole" + path = "/service-role/" assume_role_policy = data.aws_iam_policy_document.iot_assume_role_policy.json } diff --git a/website/docs/r/iot_provisioning_template.html.markdown b/website/docs/r/iot_provisioning_template.html.markdown index 97eac67b8c15..c1da01391a76 100644 --- a/website/docs/r/iot_provisioning_template.html.markdown +++ b/website/docs/r/iot_provisioning_template.html.markdown @@ -3,12 +3,12 @@ subcategory: "IoT" layout: "aws" page_title: "AWS: aws_iot_provisioning_template" description: |- - Creates an IoT fleet provisioning template. + Creates an IoT Fleet Provisioning template. --- # Resource: aws_iot_provisioning_template -Creates an IoT fleet provisioning template. For more info, see the AWS documentation on [fleet provisioning](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html). +Creates an IoT Fleet Provisioning template. For more info, see the AWS documentation on [Fleet Provisioning](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html). ~> **NOTE:** The fleet provisioning feature is in beta and is subject to change. @@ -27,8 +27,8 @@ resource "aws_iam_policy_document" "iot_assume_role_policy" { } resource "aws_iam_role" "iot_fleet_provisioning" { - name = "IoTProvisioningServiceRole" - path = "/service-role/" + name = "IoTProvisioningServiceRole" + path = "/service-role/" assume_role_policy = data.aws_iam_policy_document.iot_assume_role_policy.json } From aa57b58aa95c219cc0611561b4a65f5fde812f2a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 13:59:20 -0400 Subject: [PATCH 12/20] Revert "Update website index" This reverts commit 1bbf5aa6b9ad6b49765da529e6437001bb08ebfd. --- website/aws.erb | 3 --- 1 file changed, 3 deletions(-) diff --git a/website/aws.erb b/website/aws.erb index 87d67661be9f..359187c75d01 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1821,9 +1821,6 @@
  • aws_iot_policy_attachment
  • -
  • - aws_iot_provisioning_template -
  • aws_iot_topic_rule
  • From 3b12806bfc804785149579625fb30f5c5b27c4c4 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 13:59:30 -0400 Subject: [PATCH 13/20] Revert "Support importing" This reverts commit 299451b40248712a7d163401bd783eb43b84501d. --- aws/resource_aws_iot_provisioning_template.go | 5 ---- ...urce_aws_iot_provisioning_template_test.go | 25 +++++++------------ .../r/iot_provisioning_template.html.markdown | 8 ------ 3 files changed, 9 insertions(+), 29 deletions(-) diff --git a/aws/resource_aws_iot_provisioning_template.go b/aws/resource_aws_iot_provisioning_template.go index 11f0ab8242d9..ed43bfc719a7 100644 --- a/aws/resource_aws_iot_provisioning_template.go +++ b/aws/resource_aws_iot_provisioning_template.go @@ -16,11 +16,6 @@ func resourceAwsIotProvisioningTemplate() *schema.Resource { Read: resourceAwsIotProvisioningTemplateRead, Update: resourceAwsIotProvisioningTemplateUpdate, Delete: resourceAwsIotProvisioningTemplateDelete, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - Schema: map[string]*schema.Schema{ "default_version_id": { Type: schema.TypeInt, diff --git a/aws/resource_aws_iot_provisioning_template_test.go b/aws/resource_aws_iot_provisioning_template_test.go index c6f614cc22d9..cdd919db19c1 100644 --- a/aws/resource_aws_iot_provisioning_template_test.go +++ b/aws/resource_aws_iot_provisioning_template_test.go @@ -13,7 +13,6 @@ import ( ) func TestAccAWSIoTProvisioningTemplate_basic(t *testing.T) { - resourceName := "aws_iot_provisioning_template.fleet" rName := acctest.RandomWithPrefix("Fleet-") resource.ParallelTest(t, resource.TestCase{ @@ -24,27 +23,21 @@ func TestAccAWSIoTProvisioningTemplate_basic(t *testing.T) { { Config: testAccAWSIoTProvisioningTemplateConfigInitialState(rName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "default_version_id", "1"), - resource.TestCheckResourceAttr(resourceName, "description", "My provisioning template"), - resource.TestCheckResourceAttr(resourceName, "enabled", "false"), - resource.TestCheckResourceAttrSet(resourceName, "provisioning_role_arn"), - resource.TestCheckResourceAttrSet(resourceName, "template_arn"), - resource.TestCheckResourceAttr(resourceName, "template_name", rName), - resource.TestCheckResourceAttrSet(resourceName, "template_body"), + resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "default_version_id", "1"), + resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "description", "My provisioning template"), + resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "enabled", "false"), + resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "provisioning_role_arn"), + resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_arn"), + resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "template_name", rName), + resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_body"), testAccAWSIoTProvisioningTemplateCheckVersionExists(rName, 1), ), }, - { - Config: testAccAWSIoTProvisioningTemplateConfigInitialState(rName), - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccAWSIoTProvisioningTemplateConfigTemplateBodyUpdate(rName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "default_version_id", "2"), - resource.TestCheckResourceAttrSet(resourceName, "template_body"), + resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "default_version_id", "2"), + resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_body"), testAccAWSIoTProvisioningTemplateCheckVersionExists(rName, 2), ), }, diff --git a/website/docs/r/iot_provisioning_template.html.markdown b/website/docs/r/iot_provisioning_template.html.markdown index c1da01391a76..ba2098886988 100644 --- a/website/docs/r/iot_provisioning_template.html.markdown +++ b/website/docs/r/iot_provisioning_template.html.markdown @@ -75,11 +75,3 @@ In addition to all arguments above, the following attributes are exported: * `default_version_id` - The default version of the fleet provisioning template. * `template_arn` - The ARN that identifies the provisioning template. - -## Import - -IoT fleet provisioning templates can be imported using the `template_name`, e.g. - -``` -$ terraform import aws_iot_provisioning_template.fleet FleetProvisioningTemplate -``` From ca9a640f59fa12d42762bf8d26b1b43dd1ed8b6d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 13:59:40 -0400 Subject: [PATCH 14/20] Revert "Add aws_iot_provisioning_template resource" This reverts commit 2e0a1dfe50d0248c207997fdc90c198ad5d42982. --- aws/provider.go | 1 - aws/resource_aws_iot_provisioning_template.go | 152 ----------------- ...urce_aws_iot_provisioning_template_test.go | 156 ------------------ .../r/iot_provisioning_template.html.markdown | 77 --------- 4 files changed, 386 deletions(-) delete mode 100644 aws/resource_aws_iot_provisioning_template.go delete mode 100644 aws/resource_aws_iot_provisioning_template_test.go delete mode 100644 website/docs/r/iot_provisioning_template.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 9a870a873bdc..d2ea8a71fb32 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -587,7 +587,6 @@ func Provider() terraform.ResourceProvider { "aws_iot_certificate": resourceAwsIotCertificate(), "aws_iot_policy": resourceAwsIotPolicy(), "aws_iot_policy_attachment": resourceAwsIotPolicyAttachment(), - "aws_iot_provisioning_template": resourceAwsIotProvisioningTemplate(), "aws_iot_thing": resourceAwsIotThing(), "aws_iot_thing_principal_attachment": resourceAwsIotThingPrincipalAttachment(), "aws_iot_thing_type": resourceAwsIotThingType(), diff --git a/aws/resource_aws_iot_provisioning_template.go b/aws/resource_aws_iot_provisioning_template.go deleted file mode 100644 index ed43bfc719a7..000000000000 --- a/aws/resource_aws_iot_provisioning_template.go +++ /dev/null @@ -1,152 +0,0 @@ -package aws - -import ( - "log" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/iot" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" -) - -func resourceAwsIotProvisioningTemplate() *schema.Resource { - return &schema.Resource{ - Create: resourceAwsIotProvisioningTemplateCreate, - Read: resourceAwsIotProvisioningTemplateRead, - Update: resourceAwsIotProvisioningTemplateUpdate, - Delete: resourceAwsIotProvisioningTemplateDelete, - Schema: map[string]*schema.Schema{ - "default_version_id": { - Type: schema.TypeInt, - Computed: true, - }, - "description": { - Type: schema.TypeString, - Optional: true, - }, - "enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "provisioning_role_arn": { - Type: schema.TypeString, - Required: true, - }, - "template_arn": { - Type: schema.TypeString, - Computed: true, - }, - "template_body": { - Type: schema.TypeString, - Required: true, - }, - "template_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - }, - } -} - -func resourceAwsIotProvisioningTemplateCreate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).iotconn - - var out *iot.CreateProvisioningTemplateOutput - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - var err error - out, err = conn.CreateProvisioningTemplate(&iot.CreateProvisioningTemplateInput{ - Description: aws.String(d.Get("description").(string)), - Enabled: aws.Bool(d.Get("enabled").(bool)), - ProvisioningRoleArn: aws.String(d.Get("provisioning_role_arn").(string)), - TemplateBody: aws.String(d.Get("template_body").(string)), - TemplateName: aws.String(d.Get("template_name").(string)), - }) - - // Handle IoT not detecting the provisioning role's assume role policy immediately. - if isAWSErr(err, iot.ErrCodeInvalidRequestException, "The provisioning role cannot be assumed by AWS IoT") { - return resource.RetryableError(err) - } - return resource.NonRetryableError(err) - }) - - if err != nil { - log.Printf("[ERROR] %s", err) - return err - } - - d.SetId(*out.TemplateName) - - return resourceAwsIotProvisioningTemplateRead(d, meta) -} - -func resourceAwsIotProvisioningTemplateRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).iotconn - - out, err := conn.DescribeProvisioningTemplate(&iot.DescribeProvisioningTemplateInput{ - TemplateName: aws.String(d.Id()), - }) - - if err != nil { - log.Printf("[ERROR] %s", err) - return err - } - - d.Set("default_version_id", out.DefaultVersionId) - d.Set("description", out.Description) - d.Set("enabled", out.Enabled) - d.Set("provisioning_role_arn", out.ProvisioningRoleArn) - d.Set("template_arn", out.TemplateArn) - d.Set("template_body", out.TemplateBody) - d.Set("template_name", out.TemplateName) - - return nil -} - -func resourceAwsIotProvisioningTemplateUpdate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).iotconn - - if d.HasChange("template_body") { - _, err := conn.CreateProvisioningTemplateVersion(&iot.CreateProvisioningTemplateVersionInput{ - TemplateName: aws.String(d.Id()), - TemplateBody: aws.String(d.Get("template_body").(string)), - SetAsDefault: aws.Bool(true), - }) - - if err != nil { - log.Printf("[ERROR] %s", err) - return err - } - } - - _, err := conn.UpdateProvisioningTemplate(&iot.UpdateProvisioningTemplateInput{ - Description: aws.String(d.Get("description").(string)), - Enabled: aws.Bool(d.Get("enabled").(bool)), - ProvisioningRoleArn: aws.String(d.Get("provisioning_role_arn").(string)), - TemplateName: aws.String(d.Id()), - }) - - if err != nil { - log.Printf("[ERROR] %s", err) - return err - } - - return resourceAwsIotProvisioningTemplateRead(d, meta) -} - -func resourceAwsIotProvisioningTemplateDelete(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).iotconn - - _, err := conn.DeleteProvisioningTemplate(&iot.DeleteProvisioningTemplateInput{ - TemplateName: aws.String(d.Id()), - }) - - if err != nil { - log.Printf("[ERROR] %s", err) - return err - } - - return nil -} diff --git a/aws/resource_aws_iot_provisioning_template_test.go b/aws/resource_aws_iot_provisioning_template_test.go deleted file mode 100644 index cdd919db19c1..000000000000 --- a/aws/resource_aws_iot_provisioning_template_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package aws - -import ( - "fmt" - "strings" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/iot" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" -) - -func TestAccAWSIoTProvisioningTemplate_basic(t *testing.T) { - rName := acctest.RandomWithPrefix("Fleet-") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSIoTProvisioningTemplateDestroy_basic, - Steps: []resource.TestStep{ - { - Config: testAccAWSIoTProvisioningTemplateConfigInitialState(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "default_version_id", "1"), - resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "description", "My provisioning template"), - resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "enabled", "false"), - resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "provisioning_role_arn"), - resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_arn"), - resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "template_name", rName), - resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_body"), - testAccAWSIoTProvisioningTemplateCheckVersionExists(rName, 1), - ), - }, - { - Config: testAccAWSIoTProvisioningTemplateConfigTemplateBodyUpdate(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("aws_iot_provisioning_template.fleet", "default_version_id", "2"), - resource.TestCheckResourceAttrSet("aws_iot_provisioning_template.fleet", "template_body"), - testAccAWSIoTProvisioningTemplateCheckVersionExists(rName, 2), - ), - }, - }, - }) -} - -func testAccCheckAWSIoTProvisioningTemplateDestroy_basic(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).iotconn - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_iot_provisioning_template" { - continue - } - - _, err := conn.DescribeProvisioningTemplate(&iot.DescribeProvisioningTemplateInput{ - TemplateName: aws.String(rs.Primary.Attributes["template_name"]), - }) - - if isAWSErr(err, iot.ErrCodeResourceNotFoundException, "") { - return nil - } else if err != nil { - return err - } - - return fmt.Errorf("IoT Provisioning Template still exists") - } - - return nil -} - -func testAccAWSIoTProvisioningTemplateCheckVersionExists(templateName string, numVersions int) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).iotconn - - resp, err := conn.ListProvisioningTemplateVersions(&iot.ListProvisioningTemplateVersionsInput{ - TemplateName: aws.String(templateName), - }) - - if err != nil { - return err - } - - if len(resp.Versions) != numVersions { - return fmt.Errorf("Expected %d versions for template %s but found %d", numVersions, templateName, len(resp.Versions)) - } - - return nil - } -} - -func testAccAWSIoTProvisioningTemplateConfigInitialState(rName string) string { - return fmt.Sprintf(` -data "aws_iam_policy_document" "iot_assume_role_policy" { - statement { - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["iot.amazonaws.com"] - } - } -} - -resource "aws_iam_role" "iot_fleet_provisioning" { - name = "IoTProvisioningServiceRole" - path = "/service-role/" - assume_role_policy = data.aws_iam_policy_document.iot_assume_role_policy.json -} - -resource "aws_iam_role_policy_attachment" "iot_fleet_provisioning_registration" { - role = aws_iam_role.iot_fleet_provisioning.name - policy_arn = "arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration" -} - -resource "aws_iot_provisioning_template" "fleet" { - template_name = "%s" - description = "My provisioning template" - provisioning_role_arn = aws_iam_role.iot_fleet_provisioning.arn - - template_body = < **NOTE:** The fleet provisioning feature is in beta and is subject to change. - -## Example Usage - -```hcl -resource "aws_iam_policy_document" "iot_assume_role_policy" { - statement { - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["iot.amazonaws.com"] - } - } -} - -resource "aws_iam_role" "iot_fleet_provisioning" { - name = "IoTProvisioningServiceRole" - path = "/service-role/" - assume_role_policy = data.aws_iam_policy_document.iot_assume_role_policy.json -} - -resource "aws_iam_role_policy_attachment" "iot_fleet_provisioning_registration" { - role = aws_iam_role.iot_fleet_provisioning.name - policy_arn = "arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration" -} - -resource "aws_iot_provisioning_template" "fleet" { - template_name = "FleetProvisioningTemplate" - description = "My fleet provisioning template" - provisioning_role_arn = aws_iam_role.iot_fleet_provisioning - - template_body = < Date: Fri, 1 Apr 2022 14:54:26 -0400 Subject: [PATCH 15/20] Add 'RetryWhenAWSErrMessageContains'. --- internal/tfresource/retry.go | 16 ++++++++ internal/tfresource/retry_test.go | 62 +++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/internal/tfresource/retry.go b/internal/tfresource/retry.go index 9042a3ce1282..eb1c51194543 100644 --- a/internal/tfresource/retry.go +++ b/internal/tfresource/retry.go @@ -73,6 +73,22 @@ func RetryWhenAWSErrCodeEquals(timeout time.Duration, f func() (interface{}, err return RetryWhenAWSErrCodeEqualsContext(context.Background(), timeout, f, codes...) } +// RetryWhenAWSErrMessageContainsContext retries the specified function when it returns an AWS error containing the specified message. +func RetryWhenAWSErrMessageContainsContext(ctx context.Context, timeout time.Duration, f func() (interface{}, error), code, message string) (interface{}, error) { + return RetryWhenContext(ctx, timeout, f, func(err error) (bool, error) { + if tfawserr.ErrMessageContains(err, code, message) { + return true, err + } + + return false, err + }) +} + +// RetryWhenAWSErrMessageContains retries the specified function when it returns an AWS error containing the specified message. +func RetryWhenAWSErrMessageContains(timeout time.Duration, f func() (interface{}, error), code, message string) (interface{}, error) { + return RetryWhenAWSErrMessageContainsContext(context.Background(), timeout, f, code, message) +} + var resourceFoundError = errors.New(`found resource`) // RetryUntilNotFoundContext retries the specified function until it returns a resource.NotFoundError. diff --git a/internal/tfresource/retry_test.go b/internal/tfresource/retry_test.go index eb8d7049e362..8eb11c1c8b31 100644 --- a/internal/tfresource/retry_test.go +++ b/internal/tfresource/retry_test.go @@ -75,6 +75,68 @@ func TestRetryWhenAWSErrCodeEquals(t *testing.T) { } } +func TestRetryWhenAWSErrMessageContains(t *testing.T) { + var retryCount int32 + + testCases := []struct { + Name string + F func() (interface{}, error) + ExpectError bool + }{ + { + Name: "no error", + F: func() (interface{}, error) { + return nil, nil + }, + }, + { + Name: "non-retryable other error", + F: func() (interface{}, error) { + return nil, errors.New("TestCode") + }, + ExpectError: true, + }, + { + Name: "non-retryable AWS error", + F: func() (interface{}, error) { + return nil, awserr.New("TestCode1", "Testing", nil) + }, + ExpectError: true, + }, + { + Name: "retryable AWS error timeout", + F: func() (interface{}, error) { + return nil, awserr.New("TestCode1", "TestMessage1", nil) + }, + ExpectError: true, + }, + { + Name: "retryable AWS error success", + F: func() (interface{}, error) { + if atomic.CompareAndSwapInt32(&retryCount, 0, 1) { + return nil, awserr.New("TestCode1", "TestMessage1", nil) + } + + return nil, nil + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + retryCount = 0 + + _, err := tfresource.RetryWhenAWSErrMessageContains(5*time.Second, testCase.F, "TestCode1", "TestMessage1") + + if testCase.ExpectError && err == nil { + t.Fatal("expected error") + } else if !testCase.ExpectError && err != nil { + t.Fatalf("unexpected error: %s", err) + } + }) + } +} + func TestRetryWhenNewResourceNotFound(t *testing.T) { var retryCount int32 From 4578343d2d0afbc0c4056274f2e42f9502d679d2 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 15:14:45 -0400 Subject: [PATCH 16/20] Check error in 'FindReportPlanByName'. --- internal/service/backup/report_plan.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/service/backup/report_plan.go b/internal/service/backup/report_plan.go index 30a71de62d4b..b4a6854f4a76 100644 --- a/internal/service/backup/report_plan.go +++ b/internal/service/backup/report_plan.go @@ -360,6 +360,10 @@ func FindReportPlanByName(conn *backup.Backup, name string) (*backup.ReportPlan, } } + if err != nil { + return nil, err + } + if output == nil || output.ReportPlan == nil { return nil, tfresource.NewEmptyResultError(input) } From b5b382de0d314fd168f8325e3e5ebef2ac01fe60 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 15:36:33 -0400 Subject: [PATCH 17/20] r/aws_iot_provisioning_template: New resource. --- .changelog/12108.txt | 3 + internal/provider/provider.go | 1 + internal/service/iot/provisioning_template.go | 338 ++++++++++++++++++ .../service/iot/provisioning_template_test.go | 1 + .../r/iot_provisioning_template.html.markdown | 113 ++++++ 5 files changed, 456 insertions(+) create mode 100644 .changelog/12108.txt create mode 100644 internal/service/iot/provisioning_template.go create mode 100644 internal/service/iot/provisioning_template_test.go create mode 100644 website/docs/r/iot_provisioning_template.html.markdown diff --git a/.changelog/12108.txt b/.changelog/12108.txt new file mode 100644 index 000000000000..f210a4a379ee --- /dev/null +++ b/.changelog/12108.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_iot_provisioning_template +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index f8d7051c6db5..192872cdbce3 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1510,6 +1510,7 @@ func Provider() *schema.Provider { "aws_iot_certificate": iot.ResourceCertificate(), "aws_iot_policy": iot.ResourcePolicy(), "aws_iot_policy_attachment": iot.ResourcePolicyAttachment(), + "aws_iot_provisioning_template": iot.ResourceProvisioningTemplate(), "aws_iot_role_alias": iot.ResourceRoleAlias(), "aws_iot_thing": iot.ResourceThing(), "aws_iot_thing_group": iot.ResourceThingGroup(), diff --git a/internal/service/iot/provisioning_template.go b/internal/service/iot/provisioning_template.go new file mode 100644 index 000000000000..009cb03c089d --- /dev/null +++ b/internal/service/iot/provisioning_template.go @@ -0,0 +1,338 @@ +package iot + +import ( + "context" + "log" + "regexp" + + "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-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + 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" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +const ( + provisioningHookPayloadVersion2020_04_01 = "2020-04-01" +) + +func provisioningHookPayloadVersion_Values() []string { + return []string{ + provisioningHookPayloadVersion2020_04_01, + } +} + +func ResourceProvisioningTemplate() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceProvisioningTemplateCreate, + ReadWithoutTimeout: resourceProvisioningTemplateRead, + UpdateWithoutTimeout: resourceProvisioningTemplateUpdate, + DeleteWithoutTimeout: resourceProvisioningTemplateDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "default_version_id": { + Type: schema.TypeInt, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 500), + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 36), + validation.StringMatch(regexp.MustCompile(`^[0-9A-Za-z_-]+$`), "must contain only alphanumeric characters and/or the following: _-"), + ), + }, + "pre_provisioning_hook": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "payload_version": { + Type: schema.TypeString, + Optional: true, + Default: provisioningHookPayloadVersion2020_04_01, + ValidateFunc: validation.StringInSlice(provisioningHookPayloadVersion_Values(), false), + }, + "target_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, + "provisioning_role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "template_body": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringIsJSON, + validation.StringLenBetween(0, 10240), + ), + }, + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceProvisioningTemplateCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + 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) + input := &iot.CreateProvisioningTemplateInput{ + Enabled: aws.Bool(d.Get("enabled").(bool)), + TemplateName: aws.String(name), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("pre_provisioning_hook"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.PreProvisioningHook = expandProvisioningHook(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("provisioning_role_arn"); ok { + input.ProvisioningRoleArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("template_body"); ok { + input.TemplateBody = aws.String(v.(string)) + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + log.Printf("[DEBUG] Creating IoT Provisioning Template: %s", input) + outputRaw, err := tfresource.RetryWhenAWSErrMessageContainsContext(ctx, tfiam.PropagationTimeout, + func() (interface{}, error) { + return conn.CreateProvisioningTemplateWithContext(ctx, input) + }, + iot.ErrCodeInvalidRequestException, "The provisioning role cannot be assumed by AWS IoT") + + if err != nil { + return diag.Errorf("error creating IoT Provisioning Template (%s): %s", name, err) + } + + d.SetId(aws.StringValue(outputRaw.(*iot.CreateProvisioningTemplateOutput).TemplateName)) + + return resourceProvisioningTemplateRead(ctx, d, meta) +} + +func resourceProvisioningTemplateRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IoTConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + output, err := FindProvisioningTemplateByName(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] IoT Provisioning Template %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading IoT Provisioning Template (%s): %s", d.Id(), err) + } + + d.Set("arn", output.TemplateArn) + d.Set("default_version_id", output.DefaultVersionId) + d.Set("description", output.Description) + d.Set("enabled", output.Enabled) + d.Set("name", output.TemplateName) + if output.PreProvisioningHook != nil { + if err := d.Set("pre_provisioning_hook", []interface{}{flattenProvisioningHook(output.PreProvisioningHook)}); err != nil { + return diag.Errorf("error setting pre_provisioning_hook: %s", err) + } + } else { + d.Set("pre_provisioning_hook", nil) + } + d.Set("provisioning_role_arn", output.ProvisioningRoleArn) + d.Set("template_body", output.TemplateBody) + + tags, err := ListTags(conn, d.Get("arn").(string)) + + if err != nil { + return diag.Errorf("error listing tags for IoT Provisioning Template (%s): %s", d.Id(), err) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("error setting tags_all: %s", err) + } + + return nil +} + +func resourceProvisioningTemplateUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IoTConn + + if d.HasChange("template_body") { + input := &iot.CreateProvisioningTemplateVersionInput{ + SetAsDefault: aws.Bool(true), + TemplateBody: aws.String(d.Get("template_body").(string)), + TemplateName: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Creating IoT Provisioning Template version: %s", input) + _, err := conn.CreateProvisioningTemplateVersionWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating IoT Provisioning Template (%s) version: %s", d.Id(), err) + } + } + + if d.HasChanges("description", "enabled", "provisioning_role_arn") { + input := &iot.UpdateProvisioningTemplateInput{ + Description: aws.String(d.Get("description").(string)), + Enabled: aws.Bool(d.Get("enabled").(bool)), + ProvisioningRoleArn: aws.String(d.Get("provisioning_role_arn").(string)), + TemplateName: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Updating IoT Provisioning Template: %s", input) + _, err := tfresource.RetryWhenAWSErrMessageContainsContext(ctx, tfiam.PropagationTimeout, + func() (interface{}, error) { + return conn.UpdateProvisioningTemplateWithContext(ctx, input) + }, + iot.ErrCodeInvalidRequestException, "The provisioning role cannot be assumed by AWS IoT") + + if err != nil { + return diag.Errorf("error updating IoT Provisioning Template (%s): %s", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("error updating tags: %s", err) + } + } + + return resourceProvisioningTemplateRead(ctx, d, meta) +} + +func resourceProvisioningTemplateDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).IoTConn + + log.Printf("[INFO] Deleting IoT Provisioning Template: %s", d.Id()) + _, err := conn.DeleteProvisioningTemplateWithContext(ctx, &iot.DeleteProvisioningTemplateInput{ + TemplateName: aws.String(d.Id()), + }) + + if tfawserr.ErrCodeEquals(err, iot.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("error deleting IoT Provisioning Template (%s): %s", d.Id(), err) + } + + return nil +} + +func flattenProvisioningHook(apiObject *iot.ProvisioningHook) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.PayloadVersion; v != nil { + tfMap["payload_version"] = aws.StringValue(v) + } + + if v := apiObject.TargetArn; v != nil { + tfMap["target_arn"] = aws.StringValue(v) + } + + return tfMap +} + +func expandProvisioningHook(tfMap map[string]interface{}) *iot.ProvisioningHook { + if tfMap == nil { + return nil + } + + apiObject := &iot.ProvisioningHook{} + + if v, ok := tfMap["payload_version"].(string); ok && v != "" { + apiObject.PayloadVersion = aws.String(v) + } + + if v, ok := tfMap["target_arn"].(string); ok && v != "" { + apiObject.TargetArn = aws.String(v) + } + + return apiObject +} + +func FindProvisioningTemplateByName(ctx context.Context, conn *iot.IoT, name string) (*iot.DescribeProvisioningTemplateOutput, error) { + input := &iot.DescribeProvisioningTemplateInput{ + TemplateName: aws.String(name), + } + + output, err := conn.DescribeProvisioningTemplateWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, iot.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/iot/provisioning_template_test.go b/internal/service/iot/provisioning_template_test.go new file mode 100644 index 000000000000..160eb6de3bd7 --- /dev/null +++ b/internal/service/iot/provisioning_template_test.go @@ -0,0 +1 @@ +package iot_test diff --git a/website/docs/r/iot_provisioning_template.html.markdown b/website/docs/r/iot_provisioning_template.html.markdown new file mode 100644 index 000000000000..8ed4db80facd --- /dev/null +++ b/website/docs/r/iot_provisioning_template.html.markdown @@ -0,0 +1,113 @@ +--- +subcategory: "IoT" +layout: "aws" +page_title: "AWS: aws_iot_provisioning_template" +description: |- + Manages an IoT fleet provisioning template. +--- + +# Resource: aws_iot_provisioning_template + +Manages an IoT fleet provisioning template. For more info, see the AWS documentation on [fleet provisioning](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html). + +## Example Usage + +```terraform +data "aws_iam_policy_document" "iot_assume_role_policy" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["iot.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "iot_fleet_provisioning" { + name = "IoTProvisioningServiceRole" + path = "/service-role/" + assume_role_policy = data.aws_iam_policy_document.iot_assume_role_policy.json +} + +resource "aws_iam_role_policy_attachment" "iot_fleet_provisioning_registration" { + role = aws_iam_role.iot_fleet_provisioning.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration" +} + +data "aws_iam_policy_document" "device_policy" { + statement { + actions = ["iot:Subscribe"] + resources = ["*"] + } +} + +resource "aws_iot_policy" "device_policy" { + name = "DevicePolicy" + policy = data.aws_iam_policy_document.device_policy.json +} + +resource "aws_iot_provisioning_template" "fleet" { + name = "FleetTemplate" + description = "My provisioning template" + provisioning_role_arn = aws_iam_role.iot_fleet_provisioning.arn + + template_body = jsonencode({ + Parameters = { + SerialNumber = { Type = "String" } + } + + Resources = { + certificate = { + Properties = { + CertificateId = { Ref = "AWS::IoT::Certificate::Id" } + Status = "Active" + } + Type = "AWS::IoT::Certificate" + } + + policy = { + Properties = { + PolicyName = aws_iot_policy.device_policy.name + } + Type = "AWS::IoT::Policy" + } + } + }) +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the fleet provisioning template. +* `description` - (Optional) The description of the fleet provisioning template. +* `enabled` - (Optional) True to enable the fleet provisioning template, otherwise false. +* `pre_provisioning_hook` - (Optional) Creates a pre-provisioning hook template. Details below. +* `provisioning_role_arn` - (Required) The role ARN for the role associated with the fleet provisioning template. This IoT role grants permission to provision a device. +* `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +* `template_body` - (Required) The JSON formatted contents of the fleet provisioning template. + +### pre_provisioning_hook + +The `pre_provisioning_hook` configuration block supports the following: + +* `payload_version` - (Optional) The version of the payload that was sent to the target function. The only valid (and the default) payload version is `"2020-04-01"`. +* `target_arb` - (Optional) The ARN of the target function. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The ARN that identifies the provisioning template. +* `default_version_id` - The default version of the fleet provisioning template. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +IoT fleet provisioning templates can be imported using the `name`, e.g. + +``` +$ terraform import aws_iot_provisioning_template.fleet FleetProvisioningTemplate +``` \ No newline at end of file From 7425810d35c4c9ae7bdadb265ade21d65fe5f3c2 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 16:56:42 -0400 Subject: [PATCH 18/20] r/aws_iot_provisioning_template: Initial acceptance tests. Acceptance test output: % make testacc TESTS=TestAccIoTProvisioningTemplate_ PKG=iot ACCTEST_PARALLELISM=2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/iot/... -v -count 1 -parallel 2 -run='TestAccIoTProvisioningTemplate_' -timeout 180m === RUN TestAccIoTProvisioningTemplate_basic --- PASS: TestAccIoTProvisioningTemplate_basic (36.10s) === RUN TestAccIoTProvisioningTemplate_disappears --- PASS: TestAccIoTProvisioningTemplate_disappears (22.58s) === RUN TestAccIoTProvisioningTemplate_tags --- PASS: TestAccIoTProvisioningTemplate_tags (59.08s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/iot 121.496s --- .../service/iot/provisioning_template_test.go | 338 ++++++++++++++++++ 1 file changed, 338 insertions(+) diff --git a/internal/service/iot/provisioning_template_test.go b/internal/service/iot/provisioning_template_test.go index 160eb6de3bd7..925c26f08064 100644 --- a/internal/service/iot/provisioning_template_test.go +++ b/internal/service/iot/provisioning_template_test.go @@ -1 +1,339 @@ package iot_test + +import ( + "context" + "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 TestAccIoTProvisioningTemplate_basic(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_iot_provisioning_template.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckProvisioningTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProvisioningTemplateConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProvisioningTemplateExists(resourceName), + testAccCheckProvisioningTemplateNumVersions(rName, 1), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "pre_provisioning_hook.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "provisioning_role_arn"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "template_body"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccIoTProvisioningTemplate_disappears(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_iot_provisioning_template.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckProvisioningTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProvisioningTemplateConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProvisioningTemplateExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfiot.ResourceProvisioningTemplate(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccIoTProvisioningTemplate_tags(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_iot_provisioning_template.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckProvisioningTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProvisioningTemplateConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProvisioningTemplateExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + testAccCheckProvisioningTemplateNumVersions(rName, 1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccProvisioningTemplateConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProvisioningTemplateExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + testAccCheckProvisioningTemplateNumVersions(rName, 1), + ), + }, + { + Config: testAccProvisioningTemplateConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProvisioningTemplateExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + testAccCheckProvisioningTemplateNumVersions(rName, 1), + ), + }, + }, + }) +} + +func testAccCheckProvisioningTemplateExists(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 Provisioning Template ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).IoTConn + + _, err := tfiot.FindProvisioningTemplateByName(context.TODO(), conn, rs.Primary.ID) + + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckProvisioningTemplateDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).IoTConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iot_provisioning_template" { + continue + } + + _, err := tfiot.FindProvisioningTemplateByName(context.TODO(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("IoT Provisioning Template %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckProvisioningTemplateNumVersions(name string, want int) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).IoTConn + + var got int + err := conn.ListProvisioningTemplateVersionsPages( + &iot.ListProvisioningTemplateVersionsInput{TemplateName: aws.String(name)}, + func(page *iot.ListProvisioningTemplateVersionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + got += len(page.Versions) + + return !lastPage + }) + + if err != nil { + return err + } + + if got != want { + return fmt.Errorf("Incorrect version count for IoT Provisioning Template %s; got: %d, want: %d", name, got, want) + } + + return nil + } +} + +func testAccProvisioningTemplateBaseConfig(rName string) string { + return fmt.Sprintf(` +data "aws_iam_policy_document" "assume_role" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["iot.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "test" { + name = %[1]q + path = "/service-role/" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration" +} + +data "aws_iam_policy_document" "device" { + statement { + actions = ["iot:Subscribe"] + resources = ["*"] + } +} + +resource "aws_iot_policy" "test" { + name = %[1]q + policy = data.aws_iam_policy_document.device.json +} +`, rName) +} + +func testAccProvisioningTemplateConfig(rName string) string { + return acctest.ConfigCompose(testAccProvisioningTemplateBaseConfig(rName), fmt.Sprintf(` +resource "aws_iot_provisioning_template" "test" { + name = %[1]q + provisioning_role_arn = aws_iam_role.test.arn + + template_body = jsonencode({ + Parameters = { + SerialNumber = { Type = "String" } + } + + Resources = { + certificate = { + Properties = { + CertificateId = { Ref = "AWS::IoT::Certificate::Id" } + Status = "Active" + } + Type = "AWS::IoT::Certificate" + } + + policy = { + Properties = { + PolicyName = aws_iot_policy.test.name + } + Type = "AWS::IoT::Policy" + } + } + }) +} +`, rName)) +} + +func testAccProvisioningTemplateConfigTags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccProvisioningTemplateBaseConfig(rName), fmt.Sprintf(` +resource "aws_iot_provisioning_template" "test" { + name = %[1]q + provisioning_role_arn = aws_iam_role.test.arn + + template_body = jsonencode({ + Parameters = { + SerialNumber = { Type = "String" } + } + + Resources = { + certificate = { + Properties = { + CertificateId = { Ref = "AWS::IoT::Certificate::Id" } + Status = "Active" + } + Type = "AWS::IoT::Certificate" + } + + policy = { + Properties = { + PolicyName = aws_iot_policy.test.name + } + Type = "AWS::IoT::Policy" + } + } + }) + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1)) +} + +func testAccProvisioningTemplateConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccProvisioningTemplateBaseConfig(rName), fmt.Sprintf(` +resource "aws_iot_provisioning_template" "test" { + name = %[1]q + provisioning_role_arn = aws_iam_role.test.arn + + template_body = jsonencode({ + Parameters = { + SerialNumber = { Type = "String" } + } + + Resources = { + certificate = { + Properties = { + CertificateId = { Ref = "AWS::IoT::Certificate::Id" } + Status = "Active" + } + Type = "AWS::IoT::Certificate" + } + + policy = { + Properties = { + PolicyName = aws_iot_policy.test.name + } + Type = "AWS::IoT::Policy" + } + } + }) + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) +} From 63e9034b6a5a25de61cbd2a73b30d38f35ec8fcd Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 17:05:09 -0400 Subject: [PATCH 19/20] r/aws_iot_provisioning_template: Additional acceptance test. Acceptance test output: % make testacc TESTS=TestAccIoTProvisioningTemplate_ PKG=iot ACCTEST_PARALLELISM=2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/iot/... -v -count 1 -parallel 2 -run='TestAccIoTProvisioningTemplate_' -timeout 180m === RUN TestAccIoTProvisioningTemplate_basic === PAUSE TestAccIoTProvisioningTemplate_basic === RUN TestAccIoTProvisioningTemplate_disappears === PAUSE TestAccIoTProvisioningTemplate_disappears === RUN TestAccIoTProvisioningTemplate_tags === PAUSE TestAccIoTProvisioningTemplate_tags === RUN TestAccIoTProvisioningTemplate_update === PAUSE TestAccIoTProvisioningTemplate_update === CONT TestAccIoTProvisioningTemplate_basic === CONT TestAccIoTProvisioningTemplate_tags --- PASS: TestAccIoTProvisioningTemplate_basic (34.32s) === CONT TestAccIoTProvisioningTemplate_update --- PASS: TestAccIoTProvisioningTemplate_tags (57.94s) === CONT TestAccIoTProvisioningTemplate_disappears --- PASS: TestAccIoTProvisioningTemplate_update (45.06s) --- PASS: TestAccIoTProvisioningTemplate_disappears (30.26s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/iot 91.917s --- .../service/iot/provisioning_template_test.go | 89 ++++++++++++++++++- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/internal/service/iot/provisioning_template_test.go b/internal/service/iot/provisioning_template_test.go index 925c26f08064..2bfd25cb1ba1 100644 --- a/internal/service/iot/provisioning_template_test.go +++ b/internal/service/iot/provisioning_template_test.go @@ -20,7 +20,7 @@ func TestAccIoTProvisioningTemplate_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_iot_provisioning_template.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), Providers: acctest.Providers, @@ -54,7 +54,7 @@ func TestAccIoTProvisioningTemplate_disappears(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_iot_provisioning_template.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), Providers: acctest.Providers, @@ -76,7 +76,7 @@ func TestAccIoTProvisioningTemplate_tags(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_iot_provisioning_template.test" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), Providers: acctest.Providers, @@ -119,6 +119,55 @@ func TestAccIoTProvisioningTemplate_tags(t *testing.T) { }) } +func TestAccIoTProvisioningTemplate_update(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_iot_provisioning_template.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckProvisioningTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProvisioningTemplateConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProvisioningTemplateExists(resourceName), + testAccCheckProvisioningTemplateNumVersions(rName, 1), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "pre_provisioning_hook.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "provisioning_role_arn"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "template_body"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccProvisioningTemplateUpdatedConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProvisioningTemplateExists(resourceName), + testAccCheckProvisioningTemplateNumVersions(rName, 2), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "description", "For testing"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "pre_provisioning_hook.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "provisioning_role_arn"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "template_body"), + ), + }, + }, + }) +} + func testAccCheckProvisioningTemplateExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -337,3 +386,37 @@ resource "aws_iot_provisioning_template" "test" { } `, rName, tagKey1, tagValue1, tagKey2, tagValue2)) } + +func testAccProvisioningTemplateUpdatedConfig(rName string) string { + return acctest.ConfigCompose(testAccProvisioningTemplateBaseConfig(rName), fmt.Sprintf(` +resource "aws_iot_provisioning_template" "test" { + name = %[1]q + provisioning_role_arn = aws_iam_role.test.arn + description = "For testing" + enabled = true + + template_body = jsonencode({ + Parameters = { + SerialNumber = { Type = "String" } + } + + Resources = { + certificate = { + Properties = { + CertificateId = { Ref = "AWS::IoT::Certificate::Id" } + Status = "Inactive" + } + Type = "AWS::IoT::Certificate" + } + + policy = { + Properties = { + PolicyName = aws_iot_policy.test.name + } + Type = "AWS::IoT::Policy" + } + } + }) +} +`, rName)) +} From 997cdf3716fec9cbaff11d4595e73546c33f50ea Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Apr 2022 17:35:55 -0400 Subject: [PATCH 20/20] Fix providerlint error: 'AWSAT005: avoid hardcoded ARN AWS partitions, use aws_partition data source'. --- internal/service/iot/provisioning_template_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/service/iot/provisioning_template_test.go b/internal/service/iot/provisioning_template_test.go index 2bfd25cb1ba1..b441ac3e85b0 100644 --- a/internal/service/iot/provisioning_template_test.go +++ b/internal/service/iot/provisioning_template_test.go @@ -263,9 +263,11 @@ resource "aws_iam_role" "test" { assume_role_policy = data.aws_iam_policy_document.assume_role.json } +data "aws_partition" "current" {} + resource "aws_iam_role_policy_attachment" "test" { role = aws_iam_role.test.name - policy_arn = "arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration" + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSIoTThingsRegistration" } data "aws_iam_policy_document" "device" {