Skip to content

Commit

Permalink
Add aws_iot_provisioning_template resource
Browse files Browse the repository at this point in the history
  • Loading branch information
oleg-codaio committed Feb 20, 2020
1 parent 9f67e78 commit 2e0a1df
Show file tree
Hide file tree
Showing 4 changed files with 386 additions and 0 deletions.
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
152 changes: 152 additions & 0 deletions aws/resource_aws_iot_provisioning_template.go
Original file line number Diff line number Diff line change
@@ -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
}
156 changes: 156 additions & 0 deletions aws/resource_aws_iot_provisioning_template_test.go
Original file line number Diff line number Diff line change
@@ -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 = <<EOF
{
"Parameters": {
"SerialNumber": {
"Type": "String"
},
"AWS::IoT::Certificate::Id": {
"Type": "String"
}
},
"Resources": {
"certificate": {
"Properties": {
"CertificateId": {
"Ref": "AWS::IoT::Certificate::Id"
},
"Status": "Active"
},
"Type": "AWS::IoT::Certificate"
},
"policy": {
"Properties": {
"PolicyDocument": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": \"iot:*\",\n \"Resource\": \"*\"\n }\n ]\n}"
},
"Type": "AWS::IoT::Policy"
}
}
}
EOF
}
`, rName)
}

func testAccAWSIoTProvisioningTemplateConfigTemplateBodyUpdate(rName string) string {
return strings.ReplaceAll(testAccAWSIoTProvisioningTemplateConfigInitialState(rName), "Allow", "Deny")
}
77 changes: 77 additions & 0 deletions website/docs/r/iot_provisioning_template.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
subcategory: "IoT"
layout: "aws"
page_title: "AWS: aws_iot_provisioning_template"
description: |-
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).

~> **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 = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"iot:*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}
```

## Argument Reference

The following arguments are supported:

* `template_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.
* `provisioningRoleArn` - (Required) The role ARN for the role associated with the fleet provisioning template. This IoT role grants permission to provision a device.
* `template_body` - (Required) The JSON formatted contents of the fleet provisioning template.

## Attributes Reference

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.

0 comments on commit 2e0a1df

Please sign in to comment.