Merge pull request #6881 from hashicorp/iam-policy-datasource
provider/aws: aws_iam_policy_document data source
This commit is contained in:
commit
50a32b5ea4
|
@ -0,0 +1,210 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/hashcode"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var dataSourceAwsIamPolicyDocumentVarReplacer = strings.NewReplacer("&{", "${")
|
||||
|
||||
func dataSourceAwsIamPolicyDocument() *schema.Resource {
|
||||
setOfString := &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
}
|
||||
|
||||
return &schema.Resource{
|
||||
Read: dataSourceAwsIamPolicyDocumentRead,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"statement": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Required: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"effect": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "Allow",
|
||||
},
|
||||
"actions": setOfString,
|
||||
"not_actions": setOfString,
|
||||
"resources": setOfString,
|
||||
"not_resources": setOfString,
|
||||
"principals": dataSourceAwsIamPolicyPrincipalSchema(),
|
||||
"not_principals": dataSourceAwsIamPolicyPrincipalSchema(),
|
||||
"condition": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"test": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"variable": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"values": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Required: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"json": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dataSourceAwsIamPolicyDocumentRead(d *schema.ResourceData, meta interface{}) error {
|
||||
doc := &IAMPolicyDoc{
|
||||
Version: "2012-10-17",
|
||||
}
|
||||
|
||||
if policyId, hasPolicyId := d.GetOk("id"); hasPolicyId {
|
||||
doc.Id = policyId.(string)
|
||||
}
|
||||
|
||||
var cfgStmts = d.Get("statement").(*schema.Set).List()
|
||||
stmts := make([]*IAMPolicyStatement, len(cfgStmts))
|
||||
doc.Statements = stmts
|
||||
for i, stmtI := range cfgStmts {
|
||||
cfgStmt := stmtI.(map[string]interface{})
|
||||
stmt := &IAMPolicyStatement{
|
||||
Effect: cfgStmt["effect"].(string),
|
||||
}
|
||||
|
||||
if actions := cfgStmt["actions"].(*schema.Set).List(); len(actions) > 0 {
|
||||
stmt.Actions = iamPolicyDecodeConfigStringList(actions)
|
||||
}
|
||||
if actions := cfgStmt["not_actions"].(*schema.Set).List(); len(actions) > 0 {
|
||||
stmt.NotActions = iamPolicyDecodeConfigStringList(actions)
|
||||
}
|
||||
|
||||
if resources := cfgStmt["resources"].(*schema.Set).List(); len(resources) > 0 {
|
||||
stmt.Resources = dataSourceAwsIamPolicyDocumentReplaceVarsInList(
|
||||
iamPolicyDecodeConfigStringList(resources),
|
||||
)
|
||||
}
|
||||
if resources := cfgStmt["not_resources"].(*schema.Set).List(); len(resources) > 0 {
|
||||
stmt.NotResources = dataSourceAwsIamPolicyDocumentReplaceVarsInList(
|
||||
iamPolicyDecodeConfigStringList(resources),
|
||||
)
|
||||
}
|
||||
|
||||
if principals := cfgStmt["principals"].(*schema.Set).List(); len(principals) > 0 {
|
||||
stmt.Principals = dataSourceAwsIamPolicyDocumentMakePrincipals(principals)
|
||||
}
|
||||
|
||||
if principals := cfgStmt["not_principals"].(*schema.Set).List(); len(principals) > 0 {
|
||||
stmt.NotPrincipals = dataSourceAwsIamPolicyDocumentMakePrincipals(principals)
|
||||
}
|
||||
|
||||
if conditions := cfgStmt["condition"].(*schema.Set).List(); len(conditions) > 0 {
|
||||
stmt.Conditions = dataSourceAwsIamPolicyDocumentMakeConditions(conditions)
|
||||
}
|
||||
|
||||
stmts[i] = stmt
|
||||
}
|
||||
|
||||
jsonDoc, err := json.MarshalIndent(doc, "", " ")
|
||||
if err != nil {
|
||||
// should never happen if the above code is correct
|
||||
return err
|
||||
}
|
||||
jsonString := string(jsonDoc)
|
||||
|
||||
d.Set("json", jsonString)
|
||||
d.SetId(strconv.Itoa(hashcode.String(jsonString)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dataSourceAwsIamPolicyDocumentReplaceVarsInList(in []string) []string {
|
||||
out := make([]string, len(in))
|
||||
for i, item := range in {
|
||||
out[i] = dataSourceAwsIamPolicyDocumentVarReplacer.Replace(item)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func dataSourceAwsIamPolicyDocumentMakeConditions(in []interface{}) IAMPolicyStatementConditionSet {
|
||||
out := make([]IAMPolicyStatementCondition, len(in))
|
||||
for i, itemI := range in {
|
||||
item := itemI.(map[string]interface{})
|
||||
out[i] = IAMPolicyStatementCondition{
|
||||
Test: item["test"].(string),
|
||||
Variable: item["variable"].(string),
|
||||
Values: dataSourceAwsIamPolicyDocumentReplaceVarsInList(
|
||||
iamPolicyDecodeConfigStringList(
|
||||
item["values"].(*schema.Set).List(),
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
return IAMPolicyStatementConditionSet(out)
|
||||
}
|
||||
|
||||
func dataSourceAwsIamPolicyDocumentMakePrincipals(in []interface{}) IAMPolicyStatementPrincipalSet {
|
||||
out := make([]IAMPolicyStatementPrincipal, len(in))
|
||||
for i, itemI := range in {
|
||||
item := itemI.(map[string]interface{})
|
||||
out[i] = IAMPolicyStatementPrincipal{
|
||||
Type: item["type"].(string),
|
||||
Identifiers: dataSourceAwsIamPolicyDocumentReplaceVarsInList(
|
||||
iamPolicyDecodeConfigStringList(
|
||||
item["identifiers"].(*schema.Set).List(),
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
return IAMPolicyStatementPrincipalSet(out)
|
||||
}
|
||||
|
||||
func dataSourceAwsIamPolicyPrincipalSchema() *schema.Schema {
|
||||
return &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"identifiers": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Required: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccAWSIAMPolicyDocument(t *testing.T) {
|
||||
// This really ought to be able to be a unit test rather than an
|
||||
// acceptance test, but just instantiating the AWS provider requires
|
||||
// some AWS API calls, and so this needs valid AWS credentials to work.
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccAWSIAMPolicyDocumentConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckStateValue(
|
||||
"data.aws_iam_policy_document.test",
|
||||
"json",
|
||||
testAccAWSIAMPolicyDocumentExpectedJSON,
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckStateValue(id, name, value string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", id)
|
||||
}
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
v := rs.Primary.Attributes[name]
|
||||
if v != value {
|
||||
return fmt.Errorf(
|
||||
"Value for %s is %s, not %s", name, v, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var testAccAWSIAMPolicyDocumentConfig = `
|
||||
data "aws_iam_policy_document" "test" {
|
||||
statement {
|
||||
actions = [
|
||||
"s3:ListAllMyBuckets",
|
||||
"s3:GetBucketLocation",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:s3:::*",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
actions = [
|
||||
"s3:ListBucket",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:s3:::foo",
|
||||
]
|
||||
condition {
|
||||
test = "StringLike"
|
||||
variable = "s3:prefix"
|
||||
values = [
|
||||
"",
|
||||
"home/",
|
||||
"home/&{aws:username}/",
|
||||
]
|
||||
}
|
||||
|
||||
not_principals {
|
||||
type = "AWS"
|
||||
identifiers = ["arn:blahblah:example"]
|
||||
}
|
||||
}
|
||||
|
||||
statement {
|
||||
actions = [
|
||||
"s3:*",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:s3:::foo/home/&{aws:username}",
|
||||
"arn:aws:s3:::foo/home/&{aws:username}/*",
|
||||
]
|
||||
principals {
|
||||
type = "AWS"
|
||||
identifiers = ["arn:blahblah:example"]
|
||||
}
|
||||
}
|
||||
|
||||
statement {
|
||||
effect = "Deny"
|
||||
not_actions = ["s3:*"]
|
||||
not_resources = ["arn:aws:s3:::*"]
|
||||
}
|
||||
|
||||
}
|
||||
`
|
||||
|
||||
var testAccAWSIAMPolicyDocumentExpectedJSON = `{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:GetBucketLocation",
|
||||
"s3:ListAllMyBuckets"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::foo"
|
||||
],
|
||||
"NotPrincipal": {
|
||||
"AWS": [
|
||||
"arn:blahblah:example"
|
||||
]
|
||||
},
|
||||
"Condition": {
|
||||
"StringLike": {
|
||||
"s3:prefix": [
|
||||
"",
|
||||
"home/",
|
||||
"home/${aws:username}/"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:*"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::foo/home/${aws:username}/*",
|
||||
"arn:aws:s3:::foo/home/${aws:username}"
|
||||
],
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"arn:blahblah:example"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Deny",
|
||||
"NotAction": [
|
||||
"s3:*"
|
||||
],
|
||||
"NotResource": [
|
||||
"arn:aws:s3:::*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
|
@ -0,0 +1,74 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type IAMPolicyDoc struct {
|
||||
Id string `json:",omitempty"`
|
||||
Version string `json:",omitempty"`
|
||||
Statements []*IAMPolicyStatement `json:"Statement"`
|
||||
}
|
||||
|
||||
type IAMPolicyStatement struct {
|
||||
Sid string `json:",omitempty"`
|
||||
Effect string `json:",omitempty"`
|
||||
Actions []string `json:"Action,omitempty"`
|
||||
NotActions []string `json:"NotAction,omitempty"`
|
||||
Resources []string `json:"Resource,omitempty"`
|
||||
NotResources []string `json:"NotResource,omitempty"`
|
||||
Principals IAMPolicyStatementPrincipalSet `json:"Principal,omitempty"`
|
||||
NotPrincipals IAMPolicyStatementPrincipalSet `json:"NotPrincipal,omitempty"`
|
||||
Conditions IAMPolicyStatementConditionSet `json:"Condition,omitempty"`
|
||||
}
|
||||
|
||||
type IAMPolicyStatementPrincipal struct {
|
||||
Type string
|
||||
Identifiers []string
|
||||
}
|
||||
|
||||
type IAMPolicyStatementCondition struct {
|
||||
Test string
|
||||
Variable string
|
||||
Values []string
|
||||
}
|
||||
|
||||
type IAMPolicyStatementPrincipalSet []IAMPolicyStatementPrincipal
|
||||
type IAMPolicyStatementConditionSet []IAMPolicyStatementCondition
|
||||
|
||||
func (ps IAMPolicyStatementPrincipalSet) MarshalJSON() ([]byte, error) {
|
||||
raw := map[string][]string{}
|
||||
|
||||
for _, p := range ps {
|
||||
if _, ok := raw[p.Type]; !ok {
|
||||
raw[p.Type] = make([]string, 0, len(p.Identifiers))
|
||||
}
|
||||
raw[p.Type] = append(raw[p.Type], p.Identifiers...)
|
||||
}
|
||||
|
||||
return json.Marshal(&raw)
|
||||
}
|
||||
|
||||
func (cs IAMPolicyStatementConditionSet) MarshalJSON() ([]byte, error) {
|
||||
raw := map[string]map[string][]string{}
|
||||
|
||||
for _, c := range cs {
|
||||
if _, ok := raw[c.Test]; !ok {
|
||||
raw[c.Test] = map[string][]string{}
|
||||
}
|
||||
if _, ok := raw[c.Test][c.Variable]; !ok {
|
||||
raw[c.Test][c.Variable] = make([]string, 0, len(c.Values))
|
||||
}
|
||||
raw[c.Test][c.Variable] = append(raw[c.Test][c.Variable], c.Values...)
|
||||
}
|
||||
|
||||
return json.Marshal(&raw)
|
||||
}
|
||||
|
||||
func iamPolicyDecodeConfigStringList(lI []interface{}) []string {
|
||||
ret := make([]string, len(lI))
|
||||
for i, vI := range lI {
|
||||
ret[i] = vI.(string)
|
||||
}
|
||||
return ret
|
||||
}
|
|
@ -111,8 +111,9 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
|
||||
DataSourcesMap: map[string]*schema.Resource{
|
||||
"aws_ami": dataSourceAwsAmi(),
|
||||
"aws_availability_zones": dataSourceAwsAvailabilityZones(),
|
||||
"aws_ami": dataSourceAwsAmi(),
|
||||
"aws_availability_zones": dataSourceAwsAvailabilityZones(),
|
||||
"aws_iam_policy_document": dataSourceAwsIamPolicyDocument(),
|
||||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_iam_policy_document"
|
||||
sidebar_current: "docs-aws-resource-iam-policy-document"
|
||||
description: |-
|
||||
Generates an IAM policy document in JSON format
|
||||
---
|
||||
|
||||
# aws\_iam\_policy\_document
|
||||
|
||||
Generates an IAM policy document in JSON format.
|
||||
|
||||
This is a data source which can be used to construct a JSON representation of
|
||||
an IAM policy document, for use with resources which expect policy documents,
|
||||
such as the `aws_iam_policy` resource.
|
||||
|
||||
```
|
||||
data "aws_iam_policy_document" "example" {
|
||||
statement {
|
||||
actions = [
|
||||
"s3:ListAllMyBuckets",
|
||||
"s3:GetBucketLocation",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:s3:::*",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
actions = [
|
||||
"s3:ListBucket",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:s3:::${var.s3_bucket_name}",
|
||||
]
|
||||
condition {
|
||||
test = "StringLike"
|
||||
variable = "s3:prefix"
|
||||
values = [
|
||||
"",
|
||||
"home/",
|
||||
"home/&{aws:username}/",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
statement {
|
||||
actions = [
|
||||
"s3:*",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:s3:::${var.s3_bucket_name}/home/&{aws:username}",
|
||||
"arn:aws:s3:::${var.s3_bucket_name}/home/&{aws:username}/*",
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "example" {
|
||||
name = "example_policy"
|
||||
path = "/"
|
||||
policy = "${data.aws_iam_policy.example.json}"
|
||||
}
|
||||
```
|
||||
|
||||
Using this data source to generate policy documents is *optional*. It is also
|
||||
valid to use literal JSON strings within your configuration, or to use the
|
||||
`file` interpolation function to read a raw JSON policy document from a file.
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `id` (Optional) - An ID for the policy document.
|
||||
* `statement` (Required) - A nested configuration block (described below)
|
||||
configuring one *statement* to be included in the policy document.
|
||||
|
||||
Each document configuration must have one or more `statement` blocks, which
|
||||
each accept the following arguments:
|
||||
|
||||
* `id` (Optional) - An ID for the policy statement.
|
||||
* `effect` (Optional) - Either "Allow" or "Deny", to specify whether this
|
||||
statement allows or denies the given actions. The default is "Allow".
|
||||
* `actions` (Optional) - A list of actions that this statement either allows
|
||||
or denies. For example, ``["ec2:RunInstances", "s3:*"]``.
|
||||
* `not_actions` (Optional) - A list of actions that this statement does *not*
|
||||
apply to. Used to apply a policy statement to all actions *except* those
|
||||
listed.
|
||||
* `resources` (Optional) - A list of resource ARNs that this statement applies
|
||||
to.
|
||||
* `not_resources` (Optional) - A list of resource ARNs that this statement
|
||||
does *not* apply to. Used to apply a policy statement to all resources
|
||||
*except* those listed.
|
||||
* `principals` (Optional) - A nested configuration block (described below)
|
||||
specifying a resource (or resource pattern) to which this statement applies.
|
||||
* `not_principals` (Optional) - Like `principals` except gives resources that
|
||||
the statement does *not* apply to.
|
||||
* `condition` (Optional) - A nested configuration block (described below)
|
||||
that defines a further, possibly-service-specific condition that constrains
|
||||
whether this statement applies.
|
||||
|
||||
Each policy may have either zero or more `principals` blocks or zero or more
|
||||
`not_principals` blocks, both of which each accept the following arguments:
|
||||
|
||||
* `type` (Required) The type of principal. For AWS accounts this is "AWS".
|
||||
* `identifiers` (Required) List of identifiers for principals. When `type`
|
||||
is "AWS", these are IAM user or role ARNs.
|
||||
|
||||
Each policy statement may have zero or more `condition` blocks, which each
|
||||
accept the following arguments:
|
||||
|
||||
* `test` (Required) The name of the
|
||||
[IAM condition type](http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#AccessPolicyLanguage_ConditionType)
|
||||
to evaluate.
|
||||
* `variable` (Required) The name of a
|
||||
[Context Variable](http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#AvailableKeys)
|
||||
to apply the condition to. Context variables may either be standard AWS
|
||||
variables starting with `aws:`, or service-specific variables prefixed with
|
||||
the service name.
|
||||
* `values` (Required) The values to evaluate the condition against. If multiple
|
||||
values are provided, the condition matches if at least one of them applies.
|
||||
(That is, the tests are combined with the "OR" boolean operation.)
|
||||
|
||||
When multiple `condition` blocks are provided, they must *all* evaluate to true
|
||||
for the policy statement to apply. (In other words, the conditions are combined
|
||||
with the "AND" boolean operation.)
|
||||
|
||||
## Context Variable Interpolation
|
||||
|
||||
The IAM policy document format allows context variables to be interpolated
|
||||
into various strings within a statement. The native IAM policy document format
|
||||
uses `${...}`-style syntax that is in conflict with Terraform's interpolation
|
||||
syntax, so this data source instead uses `&{...}` syntax for interpolations that
|
||||
should be processed by AWS rather than by Terraform.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attribute is exported:
|
||||
|
||||
* `json` - The above arguments serialized as a standard JSON policy document.
|
||||
|
|
@ -19,6 +19,10 @@
|
|||
<li<%= sidebar_current("docs-aws-datasource-availability-zones") %>>
|
||||
<a href="/docs/providers/aws/d/availability_zones.html">aws_availability_zones</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-datasource-iam-policy-document") %>>
|
||||
<a href="/docs/providers/aws/d/iam_policy_document.html">aws_iam_policy_document</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
@ -402,6 +406,7 @@
|
|||
<li<%= sidebar_current("docs-aws-resource-iam-policy") %>>
|
||||
<a href="/docs/providers/aws/r/iam_policy.html">aws_iam_policy</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-iam-policy-attachment") %>>
|
||||
<a href="/docs/providers/aws/r/iam_policy_attachment.html">aws_iam_policy_attachment</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue