Implement AWS IAM resources

- Users
- Groups
- Roles
- Inline policies for the above three
- Instance profiles
- Managed policies
- Access keys

This is most of the data types provided by IAM. There are a few things
missing, but the functionality here is probably sufficient for 95% of
the cases. Makes a dent in #28.
This commit is contained in:
Phil Frost 2015-02-06 10:34:24 -05:00
parent 33183c078b
commit b082117e92
16 changed files with 1465 additions and 8 deletions

View File

@ -83,19 +83,28 @@ func Provider() terraform.ResourceProvider {
}, },
ResourcesMap: map[string]*schema.Resource{ ResourcesMap: map[string]*schema.Resource{
"aws_autoscaling_group": resourceAwsAutoscalingGroup(),
"aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(), "aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(),
"aws_autoscaling_group": resourceAwsAutoscalingGroup(),
"aws_customer_gateway": resourceAwsCustomerGateway(), "aws_customer_gateway": resourceAwsCustomerGateway(),
"aws_db_instance": resourceAwsDbInstance(), "aws_db_instance": resourceAwsDbInstance(),
"aws_db_parameter_group": resourceAwsDbParameterGroup(), "aws_db_parameter_group": resourceAwsDbParameterGroup(),
"aws_db_security_group": resourceAwsDbSecurityGroup(), "aws_db_security_group": resourceAwsDbSecurityGroup(),
"aws_db_subnet_group": resourceAwsDbSubnetGroup(), "aws_db_subnet_group": resourceAwsDbSubnetGroup(),
"aws_ebs_volume": resourceAwsEbsVolume(), "aws_ebs_volume": resourceAwsEbsVolume(),
"aws_elasticache_cluster": resourceAwsElasticacheCluster(),
"aws_elasticache_subnet_group": resourceAwsElasticacheSubnetGroup(),
"aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(),
"aws_eip": resourceAwsEip(), "aws_eip": resourceAwsEip(),
"aws_elasticache_cluster": resourceAwsElasticacheCluster(),
"aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(),
"aws_elasticache_subnet_group": resourceAwsElasticacheSubnetGroup(),
"aws_elb": resourceAwsElb(), "aws_elb": resourceAwsElb(),
"aws_iam_access_key": resourceAwsIamAccessKey(),
"aws_iam_group_policy": resourceAwsIamGroupPolicy(),
"aws_iam_group": resourceAwsIamGroup(),
"aws_iam_instance_profile": resourceAwsIamInstanceProfile(),
"aws_iam_policy": resourceAwsIamPolicy(),
"aws_iam_role_policy": resourceAwsIamRolePolicy(),
"aws_iam_role": resourceAwsIamRole(),
"aws_iam_user_policy": resourceAwsIamUserPolicy(),
"aws_iam_user": resourceAwsIamUser(),
"aws_instance": resourceAwsInstance(), "aws_instance": resourceAwsInstance(),
"aws_internet_gateway": resourceAwsInternetGateway(), "aws_internet_gateway": resourceAwsInternetGateway(),
"aws_key_pair": resourceAwsKeyPair(), "aws_key_pair": resourceAwsKeyPair(),
@ -107,15 +116,15 @@ func Provider() terraform.ResourceProvider {
"aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(),
"aws_route53_record": resourceAwsRoute53Record(), "aws_route53_record": resourceAwsRoute53Record(),
"aws_route53_zone": resourceAwsRoute53Zone(), "aws_route53_zone": resourceAwsRoute53Zone(),
"aws_route_table": resourceAwsRouteTable(),
"aws_route_table_association": resourceAwsRouteTableAssociation(), "aws_route_table_association": resourceAwsRouteTableAssociation(),
"aws_route_table": resourceAwsRouteTable(),
"aws_s3_bucket": resourceAwsS3Bucket(), "aws_s3_bucket": resourceAwsS3Bucket(),
"aws_security_group": resourceAwsSecurityGroup(), "aws_security_group": resourceAwsSecurityGroup(),
"aws_subnet": resourceAwsSubnet(), "aws_subnet": resourceAwsSubnet(),
"aws_vpc": resourceAwsVpc(),
"aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(),
"aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(),
"aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(), "aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(),
"aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(),
"aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(),
"aws_vpc": resourceAwsVpc(),
"aws_vpn_connection": resourceAwsVpnConnection(), "aws_vpn_connection": resourceAwsVpnConnection(),
"aws_vpn_connection_route": resourceAwsVpnConnectionRoute(), "aws_vpn_connection_route": resourceAwsVpnConnectionRoute(),
"aws_vpn_gateway": resourceAwsVpnGateway(), "aws_vpn_gateway": resourceAwsVpnGateway(),

View File

@ -0,0 +1,116 @@
package aws
import (
"fmt"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsIamAccessKey() *schema.Resource {
return &schema.Resource{
Create: resourceAwsIamAccessKeyCreate,
Read: resourceAwsIamAccessKeyRead,
Delete: resourceAwsIamAccessKeyDelete,
Schema: map[string]*schema.Schema{
"user": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"status": &schema.Schema{
Type: schema.TypeString,
// this could be settable, but goamz does not support the
// UpdateAccessKey API yet.
Computed: true,
},
"secret": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceAwsIamAccessKeyCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.CreateAccessKeyInput{
UserName: aws.String(d.Get("user").(string)),
}
createResp, err := iamconn.CreateAccessKey(request)
if err != nil {
return fmt.Errorf(
"Error creating access key for user %s: %s",
*request.UserName,
err,
)
}
if err := d.Set("secret", createResp.AccessKey.SecretAccessKey); err != nil {
return err
}
return resourceAwsIamAccessKeyReadResult(d, &iam.AccessKeyMetadata{
AccessKeyID: createResp.AccessKey.AccessKeyID,
CreateDate: createResp.AccessKey.CreateDate,
Status: createResp.AccessKey.Status,
UserName: createResp.AccessKey.UserName,
})
}
func resourceAwsIamAccessKeyRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.ListAccessKeysInput{
UserName: aws.String(d.Get("user").(string)),
}
getResp, err := iamconn.ListAccessKeys(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" { // XXX TEST ME
// the user does not exist, so the key can't exist.
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM acces key: %s", err)
}
for _, key := range getResp.AccessKeyMetadata {
if key.AccessKeyID != nil && *key.AccessKeyID == d.Id() {
return resourceAwsIamAccessKeyReadResult(d, key)
}
}
// Guess the key isn't around anymore.
d.SetId("")
return nil
}
func resourceAwsIamAccessKeyReadResult(d *schema.ResourceData, key *iam.AccessKeyMetadata) error {
d.SetId(*key.AccessKeyID)
if err := d.Set("user", key.UserName); err != nil {
return err
}
if err := d.Set("status", key.Status); err != nil {
return err
}
return nil
}
func resourceAwsIamAccessKeyDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.DeleteAccessKeyInput{
AccessKeyID: aws.String(d.Id()),
UserName: aws.String(d.Get("user").(string)),
}
if _, err := iamconn.DeleteAccessKey(request); err != nil {
return fmt.Errorf("Error deleting access key %s: %s", d.Id(), err)
}
return nil
}

View File

@ -0,0 +1,106 @@
package aws
import (
"fmt"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsIamGroup() *schema.Resource {
return &schema.Resource{
Create: resourceAwsIamGroupCreate,
Read: resourceAwsIamGroupRead,
// TODO
//Update: resourceAwsIamGroupUpdate,
Delete: resourceAwsIamGroupDelete,
Schema: map[string]*schema.Schema{
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"unique_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "/",
ForceNew: true,
},
},
}
}
func resourceAwsIamGroupCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
name := d.Get("name").(string)
request := &iam.CreateGroupInput{
Path: aws.String(d.Get("path").(string)),
GroupName: aws.String(name),
}
createResp, err := iamconn.CreateGroup(request)
if err != nil {
return fmt.Errorf("Error creating IAM Group %s: %s", name, err)
}
return resourceAwsIamGroupReadResult(d, createResp.Group)
}
func resourceAwsIamGroupRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.GetGroupInput{
GroupName: aws.String(d.Id()),
}
getResp, err := iamconn.GetGroup(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" {
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM Group %s: %s", d.Id(), err)
}
return resourceAwsIamGroupReadResult(d, getResp.Group)
}
func resourceAwsIamGroupReadResult(d *schema.ResourceData, group *iam.Group) error {
d.SetId(*group.GroupName)
if err := d.Set("name", group.GroupName); err != nil {
return err
}
if err := d.Set("arn", group.ARN); err != nil {
return err
}
if err := d.Set("path", group.Path); err != nil {
return err
}
if err := d.Set("unique_id", group.GroupID); err != nil {
return err
}
return nil
}
func resourceAwsIamGroupDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.DeleteGroupInput{
GroupName: aws.String(d.Id()),
}
if _, err := iamconn.DeleteGroup(request); err != nil {
return fmt.Errorf("Error deleting IAM Group %s: %s", d.Id(), err)
}
return nil
}

View File

@ -0,0 +1,110 @@
package aws
import (
"fmt"
"net/url"
"strings"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsIamGroupPolicy() *schema.Resource {
return &schema.Resource{
// PutGroupPolicy API is idempotent, so these can be the same.
Create: resourceAwsIamGroupPolicyPut,
Update: resourceAwsIamGroupPolicyPut,
Read: resourceAwsIamGroupPolicyRead,
Delete: resourceAwsIamGroupPolicyDelete,
Schema: map[string]*schema.Schema{
"policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"group": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceAwsIamGroupPolicyPut(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.PutGroupPolicyInput{
GroupName: aws.String(d.Get("group").(string)),
PolicyName: aws.String(d.Get("name").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
}
if _, err := iamconn.PutGroupPolicy(request); err != nil {
return fmt.Errorf("Error putting IAM group policy %s: %s", *request.PolicyName, err)
}
d.SetId(fmt.Sprintf("%s:%s", *request.GroupName, *request.PolicyName))
return nil
}
func resourceAwsIamGroupPolicyRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
group, name := resourceAwsIamGroupPolicyParseId(d)
request := &iam.GetGroupPolicyInput{
PolicyName: aws.String(name),
GroupName: aws.String(group),
}
getResp, err := iamconn.GetGroupPolicy(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" { // XXX test me
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM policy %s from group %s: %s", name, group, err)
}
if getResp.PolicyDocument == nil {
return fmt.Errorf("GetGroupPolicy returned a nil policy document")
}
policy, err := url.QueryUnescape(*getResp.PolicyDocument)
if err != nil {
return err
}
return d.Set("policy", policy)
}
func resourceAwsIamGroupPolicyDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
group, name := resourceAwsIamGroupPolicyParseId(d)
request := &iam.DeleteGroupPolicyInput{
PolicyName: aws.String(name),
GroupName: aws.String(group),
}
if _, err := iamconn.DeleteGroupPolicy(request); err != nil {
return fmt.Errorf("Error deleting IAM group policy %s: %s", d.Id(), err)
}
return nil
}
func resourceAwsIamGroupPolicyParseId(d *schema.ResourceData) (groupName, policyName string) {
parts := strings.SplitN(d.Id(), ":", 2)
groupName = parts[0]
policyName = parts[1]
return
}

View File

@ -0,0 +1,205 @@
package aws
import (
"fmt"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsIamInstanceProfile() *schema.Resource {
return &schema.Resource{
Create: resourceAwsIamInstanceProfileCreate,
Read: resourceAwsIamInstanceProfileRead,
Update: resourceAwsIamInstanceProfileUpdate,
Delete: resourceAwsIamInstanceProfileDelete,
Schema: map[string]*schema.Schema{
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"create_date": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"unique_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "/",
ForceNew: true,
},
"roles": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
}
}
func resourceAwsIamInstanceProfileCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
name := d.Get("name").(string)
request := &iam.CreateInstanceProfileInput{
InstanceProfileName: aws.String(name),
Path: aws.String(d.Get("path").(string)),
}
response, err := iamconn.CreateInstanceProfile(request)
if err == nil {
err = instanceProfileReadResult(d, response.InstanceProfile)
}
if err != nil {
return fmt.Errorf("Error creating IAM instance profile %s: %s", name, err)
}
return instanceProfileSetRoles(d, iamconn)
}
func instanceProfileAddRole(iamconn *iam.IAM, profileName, roleName string) error {
request := &iam.AddRoleToInstanceProfileInput{
InstanceProfileName: aws.String(profileName),
RoleName: aws.String(roleName),
}
_, err := iamconn.AddRoleToInstanceProfile(request)
return err
}
func instanceProfileRemoveRole(iamconn *iam.IAM, profileName, roleName string) error {
request := &iam.RemoveRoleFromInstanceProfileInput{
InstanceProfileName: aws.String(profileName),
RoleName: aws.String(roleName),
}
_, err := iamconn.RemoveRoleFromInstanceProfile(request)
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" {
return nil
}
return err
}
func instanceProfileSetRoles(d *schema.ResourceData, iamconn *iam.IAM) error {
oldInterface, newInterface := d.GetChange("roles")
oldRoles := oldInterface.(*schema.Set)
newRoles := newInterface.(*schema.Set)
currentRoles := schema.CopySet(oldRoles)
d.Partial(true)
for _, role := range oldRoles.Difference(newRoles).List() {
err := instanceProfileRemoveRole(iamconn, d.Id(), role.(string))
if err != nil {
return fmt.Errorf("Error removing role %s from IAM instance profile %s: %s", role, d.Id(), err)
}
currentRoles.Remove(role)
d.Set("roles", currentRoles)
d.SetPartial("roles")
}
for _, role := range newRoles.Difference(oldRoles).List() {
err := instanceProfileAddRole(iamconn, d.Id(), role.(string))
if err != nil {
return fmt.Errorf("Error adding role %s to IAM instance profile %s: %s", role, d.Id(), err)
}
currentRoles.Add(role)
d.Set("roles", currentRoles)
d.SetPartial("roles")
}
d.Partial(false)
return nil
}
func instanceProfileRemoveAllRoles(d *schema.ResourceData, iamconn *iam.IAM) error {
for _, role := range d.Get("roles").(*schema.Set).List() {
err := instanceProfileRemoveRole(iamconn, d.Id(), role.(string))
if err != nil {
return fmt.Errorf("Error removing role %s from IAM instance profile %s: %s", role, d.Id(), err)
}
}
return nil
}
func resourceAwsIamInstanceProfileUpdate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
if !d.HasChange("roles") {
return nil
}
return instanceProfileSetRoles(d, iamconn)
}
func resourceAwsIamInstanceProfileRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.GetInstanceProfileInput{
InstanceProfileName: aws.String(d.Id()),
}
result, err := iamconn.GetInstanceProfile(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" {
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM instance profile %s: %s", d.Id(), err)
}
return instanceProfileReadResult(d, result.InstanceProfile)
}
func resourceAwsIamInstanceProfileDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
if err := instanceProfileRemoveAllRoles(d, iamconn); err != nil {
return err
}
request := &iam.DeleteInstanceProfileInput{
InstanceProfileName: aws.String(d.Id()),
}
_, err := iamconn.DeleteInstanceProfile(request)
if err != nil {
return fmt.Errorf("Error deleting IAM instance profile %s: %s", d.Id(), err)
}
d.SetId("")
return nil
}
func instanceProfileReadResult(d *schema.ResourceData, result *iam.InstanceProfile) error {
d.SetId(*result.InstanceProfileName)
if err := d.Set("name", result.InstanceProfileName); err != nil {
return err
}
if err := d.Set("path", result.Path); err != nil {
return err
}
roles := &schema.Set{F: schema.HashString}
for _, role := range result.Roles {
roles.Add(*role.RoleName)
}
if err := d.Set("roles", roles); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,31 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAwsIamInstanceProfile(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAwsIamInstanceProfileConfig,
},
},
})
}
const testAccAwsIamInstanceProfileConfig = `
resource "aws_iam_role" "test" {
name = "test"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}"
}
resource "aws_iam_instance_profile" "test" {
name = "test"
roles = ["${aws_iam_role.test.name}"]
}
`

View File

@ -0,0 +1,226 @@
package aws
import (
"fmt"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsIamPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceAwsIamPolicyCreate,
Read: resourceAwsIamPolicyRead,
Update: resourceAwsIamPolicyUpdate,
Delete: resourceAwsIamPolicyDelete,
Schema: map[string]*schema.Schema{
"description": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Optional: true,
},
"path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "/",
ForceNew: true,
},
"policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceAwsIamPolicyCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
name := d.Get("name").(string)
request := &iam.CreatePolicyInput{
Description: aws.String(d.Get("description").(string)),
Path: aws.String(d.Get("path").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
PolicyName: aws.String(name),
}
response, err := iamconn.CreatePolicy(request)
if err != nil {
return fmt.Errorf("Error creating IAM policy %s: %#v", name, err)
}
return readIamPolicy(d, response.Policy)
}
func resourceAwsIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.GetPolicyInput{
PolicyARN: aws.String(d.Id()),
}
response, err := iamconn.GetPolicy(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" {
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM policy %s: %#v", d.Id(), err)
}
return readIamPolicy(d, response.Policy)
}
func resourceAwsIamPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
if err := iamPolicyPruneVersions(d.Id(), iamconn); err != nil {
return err
}
if !d.HasChange("policy") {
return nil
}
request := &iam.CreatePolicyVersionInput{
PolicyARN: aws.String(d.Id()),
PolicyDocument: aws.String(d.Get("policy").(string)),
SetAsDefault: aws.Boolean(true),
}
if _, err := iamconn.CreatePolicyVersion(request); err != nil {
return fmt.Errorf("Error updating IAM policy %s: %#v", d.Id(), err)
}
return nil
}
func resourceAwsIamPolicyDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
if err := iamPolicyDeleteNondefaultVersions(d.Id(), iamconn); err != nil {
return err
}
request := &iam.DeletePolicyInput{
PolicyARN: aws.String(d.Id()),
}
_, err := iamconn.DeletePolicy(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" {
return nil
}
return fmt.Errorf("Error reading IAM policy %s: %#v", d.Id(), err)
}
return nil
}
// iamPolicyPruneVersions deletes the oldest versions.
//
// Old versions are deleted until there are 4 or less remaining, which means at
// least one more can be created before hitting the maximum of 5.
//
// The default version is never deleted.
func iamPolicyPruneVersions(arn string, iamconn *iam.IAM) error {
versions, err := iamPolicyListVersions(arn, iamconn)
if err != nil {
return err
}
if len(versions) < 5 {
return nil
}
var oldestVersion *iam.PolicyVersion
for _, version := range versions {
if *version.IsDefaultVersion {
continue
}
if oldestVersion == nil ||
version.CreateDate.Before(*oldestVersion.CreateDate) {
oldestVersion = version
}
}
if err := iamPolicyDeleteVersion(arn, *oldestVersion.VersionID, iamconn); err != nil {
return err
}
return nil
}
func iamPolicyDeleteNondefaultVersions(arn string, iamconn *iam.IAM) error {
versions, err := iamPolicyListVersions(arn, iamconn)
if err != nil {
return err
}
for _, version := range versions {
if *version.IsDefaultVersion {
continue
}
if err := iamPolicyDeleteVersion(arn, *version.VersionID, iamconn); err != nil {
return err
}
}
return nil
}
func iamPolicyDeleteVersion(arn, versionID string, iamconn *iam.IAM) error {
request := &iam.DeletePolicyVersionInput{
PolicyARN: aws.String(arn),
VersionID: aws.String(versionID),
}
_, err := iamconn.DeletePolicyVersion(request)
if err != nil {
return fmt.Errorf("Error deleting version %s from IAM policy %s: %#v", versionID, arn, err)
}
return nil
}
func iamPolicyListVersions(arn string, iamconn *iam.IAM) ([]*iam.PolicyVersion, error) {
request := &iam.ListPolicyVersionsInput{
PolicyARN: aws.String(arn),
}
response, err := iamconn.ListPolicyVersions(request)
if err != nil {
return nil, fmt.Errorf("Error listing versions for IAM policy %s: %#v", arn, err)
}
return response.Versions, nil
}
func readIamPolicy(d *schema.ResourceData, policy *iam.Policy) error {
d.SetId(*policy.ARN)
if policy.Description != nil {
// the description isn't present in the response to CreatePolicy.
if err := d.Set("description", *policy.Description); err != nil {
return err
}
}
if err := d.Set("path", *policy.Path); err != nil {
return err
}
if err := d.Set("name", *policy.PolicyName); err != nil {
return err
}
if err := d.Set("arn", *policy.ARN); err != nil {
return err
}
// TODO: set policy
return nil
}

View File

@ -0,0 +1,112 @@
package aws
import (
"fmt"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsIamRole() *schema.Resource {
return &schema.Resource{
Create: resourceAwsIamRoleCreate,
Read: resourceAwsIamRoleRead,
// TODO
//Update: resourceAwsIamRoleUpdate,
Delete: resourceAwsIamRoleDelete,
Schema: map[string]*schema.Schema{
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"unique_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "/",
ForceNew: true,
},
"assume_role_policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceAwsIamRoleCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
name := d.Get("name").(string)
request := &iam.CreateRoleInput{
Path: aws.String(d.Get("path").(string)),
RoleName: aws.String(name),
AssumeRolePolicyDocument: aws.String(d.Get("assume_role_policy").(string)),
}
createResp, err := iamconn.CreateRole(request)
if err != nil {
return fmt.Errorf("Error creating IAM Role %s: %s", name, err)
}
return resourceAwsIamRoleReadResult(d, createResp.Role)
}
func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.GetRoleInput{
RoleName: aws.String(d.Id()),
}
getResp, err := iamconn.GetRole(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" { // XXX test me
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM Role %s: %s", d.Id(), err)
}
return resourceAwsIamRoleReadResult(d, getResp.Role)
}
func resourceAwsIamRoleReadResult(d *schema.ResourceData, role *iam.Role) error {
d.SetId(*role.RoleName)
if err := d.Set("name", role.RoleName); err != nil {
return err
}
if err := d.Set("arn", role.ARN); err != nil {
return err
}
if err := d.Set("path", role.Path); err != nil {
return err
}
if err := d.Set("unique_id", role.RoleID); err != nil {
return err
}
return nil
}
func resourceAwsIamRoleDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.DeleteRoleInput{
RoleName: aws.String(d.Id()),
}
if _, err := iamconn.DeleteRole(request); err != nil {
return fmt.Errorf("Error deleting IAM Role %s: %s", d.Id(), err)
}
return nil
}

View File

@ -0,0 +1,110 @@
package aws
import (
"fmt"
"net/url"
"strings"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsIamRolePolicy() *schema.Resource {
return &schema.Resource{
// PutRolePolicy API is idempotent, so these can be the same.
Create: resourceAwsIamRolePolicyPut,
Update: resourceAwsIamRolePolicyPut,
Read: resourceAwsIamRolePolicyRead,
Delete: resourceAwsIamRolePolicyDelete,
Schema: map[string]*schema.Schema{
"policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"role": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceAwsIamRolePolicyPut(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.PutRolePolicyInput{
RoleName: aws.String(d.Get("role").(string)),
PolicyName: aws.String(d.Get("name").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
}
if _, err := iamconn.PutRolePolicy(request); err != nil {
return fmt.Errorf("Error putting IAM role policy %s: %s", *request.PolicyName, err)
}
d.SetId(fmt.Sprintf("%s:%s", *request.RoleName, *request.PolicyName))
return nil
}
func resourceAwsIamRolePolicyRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
role, name := resourceAwsIamRolePolicyParseId(d)
request := &iam.GetRolePolicyInput{
PolicyName: aws.String(name),
RoleName: aws.String(role),
}
getResp, err := iamconn.GetRolePolicy(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" { // XXX test me
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM policy %s from role %s: %s", name, role, err)
}
if getResp.PolicyDocument == nil {
return fmt.Errorf("GetRolePolicy returned a nil policy document")
}
policy, err := url.QueryUnescape(*getResp.PolicyDocument)
if err != nil {
return err
}
return d.Set("policy", policy)
}
func resourceAwsIamRolePolicyDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
role, name := resourceAwsIamRolePolicyParseId(d)
request := &iam.DeleteRolePolicyInput{
PolicyName: aws.String(name),
RoleName: aws.String(role),
}
if _, err := iamconn.DeleteRolePolicy(request); err != nil {
return fmt.Errorf("Error deleting IAM role policy %s: %s", d.Id(), err)
}
return nil
}
func resourceAwsIamRolePolicyParseId(d *schema.ResourceData) (userName, policyName string) {
parts := strings.SplitN(d.Id(), ":", 2)
userName = parts[0]
policyName = parts[1]
return
}

View File

@ -0,0 +1,115 @@
package aws
import (
"fmt"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsIamUser() *schema.Resource {
return &schema.Resource{
Create: resourceAwsIamUserCreate,
Read: resourceAwsIamUserRead,
// There is an UpdateUser API call, but goamz doesn't support it yet.
// XXX but we aren't using goamz anymore.
//Update: resourceAwsIamUserUpdate,
Delete: resourceAwsIamUserDelete,
Schema: map[string]*schema.Schema{
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
/*
The UniqueID could be used as the Id(), but none of the API
calls allow specifying a user by the UniqueID: they require the
name. The only way to locate a user by UniqueID is to list them
all and that would make this provider unnecessarilly complex
and inefficient. Still, there are other reasons one might want
the UniqueID, so we can make it availible.
*/
"unique_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "/",
ForceNew: true,
},
},
}
}
func resourceAwsIamUserCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
name := d.Get("name").(string)
request := &iam.CreateUserInput{
Path: aws.String(d.Get("path").(string)),
UserName: aws.String(name),
}
createResp, err := iamconn.CreateUser(request)
if err != nil {
return fmt.Errorf("Error creating IAM User %s: %s", name, err)
}
return resourceAwsIamUserReadResult(d, createResp.User)
}
func resourceAwsIamUserRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.GetUserInput{
UserName: aws.String(d.Id()),
}
getResp, err := iamconn.GetUser(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" { // XXX test me
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM User %s: %s", d.Id(), err)
}
return resourceAwsIamUserReadResult(d, getResp.User)
}
func resourceAwsIamUserReadResult(d *schema.ResourceData, user *iam.User) error {
d.SetId(*user.UserName)
if err := d.Set("name", user.UserName); err != nil {
return err
}
if err := d.Set("arn", user.ARN); err != nil {
return err
}
if err := d.Set("path", user.Path); err != nil {
return err
}
if err := d.Set("unique_id", user.UserID); err != nil {
return err
}
return nil
}
func resourceAwsIamUserDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.DeleteUserInput{
UserName: aws.String(d.Id()),
}
if _, err := iamconn.DeleteUser(request); err != nil {
return fmt.Errorf("Error deleting IAM User %s: %s", d.Id(), err)
}
return nil
}

View File

@ -0,0 +1,110 @@
package aws
import (
"fmt"
"net/url"
"strings"
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsIamUserPolicy() *schema.Resource {
return &schema.Resource{
// PutUserPolicy API is idempotent, so these can be the same.
Create: resourceAwsIamUserPolicyPut,
Update: resourceAwsIamUserPolicyPut,
Read: resourceAwsIamUserPolicyRead,
Delete: resourceAwsIamUserPolicyDelete,
Schema: map[string]*schema.Schema{
"policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"user": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceAwsIamUserPolicyPut(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
request := &iam.PutUserPolicyInput{
UserName: aws.String(d.Get("user").(string)),
PolicyName: aws.String(d.Get("name").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
}
if _, err := iamconn.PutUserPolicy(request); err != nil {
return fmt.Errorf("Error putting IAM user policy %s: %s", *request.PolicyName, err)
}
d.SetId(fmt.Sprintf("%s:%s", *request.UserName, *request.PolicyName))
return nil
}
func resourceAwsIamUserPolicyRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
user, name := resourceAwsIamUserPolicyParseId(d)
request := &iam.GetUserPolicyInput{
PolicyName: aws.String(name),
UserName: aws.String(user),
}
getResp, err := iamconn.GetUserPolicy(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" { // XXX test me
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM policy %s from user %s: %s", name, user, err)
}
if getResp.PolicyDocument == nil {
return fmt.Errorf("GetUserPolicy returned a nil policy document")
}
policy, err := url.QueryUnescape(*getResp.PolicyDocument)
if err != nil {
return err
}
return d.Set("policy", policy)
}
func resourceAwsIamUserPolicyDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
user, name := resourceAwsIamUserPolicyParseId(d)
request := &iam.DeleteUserPolicyInput{
PolicyName: aws.String(name),
UserName: aws.String(user),
}
if _, err := iamconn.DeleteUserPolicy(request); err != nil {
return fmt.Errorf("Error deleting IAM user policy %s: %s", d.Id(), err)
}
return nil
}
func resourceAwsIamUserPolicyParseId(d *schema.ResourceData) (userName, policyName string) {
parts := strings.SplitN(d.Id(), ":", 2)
userName = parts[0]
policyName = parts[1]
return
}

View File

@ -35,11 +35,21 @@ func NewSet(f SchemaSetFunc, items []interface{}) *Set {
return s return s
} }
// CopySet returns a copy of another set.
func CopySet(otherSet *Set) *Set {
return NewSet(otherSet.F, otherSet.List())
}
// Add adds an item to the set if it isn't already in the set. // Add adds an item to the set if it isn't already in the set.
func (s *Set) Add(item interface{}) { func (s *Set) Add(item interface{}) {
s.add(item) s.add(item)
} }
// Remove removes an item if it's already in the set. Idempotent.
func (s *Set) Remove(item interface{}) {
s.remove(item)
}
// Contains checks if the set has the given item. // Contains checks if the set has the given item.
func (s *Set) Contains(item interface{}) bool { func (s *Set) Contains(item interface{}) bool {
_, ok := s.m[s.hash(item)] _, ok := s.m[s.hash(item)]
@ -147,6 +157,15 @@ func (s *Set) hash(item interface{}) int {
return code return code
} }
func (s *Set) remove(item interface{}) int {
s.once.Do(s.init)
code := s.F(item)
delete(s.m, code)
return code
}
func (s *Set) index(item interface{}) int { func (s *Set) index(item interface{}) int {
return sort.SearchInts(s.listCode(), s.hash(item)) return sort.SearchInts(s.listCode(), s.hash(item))
} }

View File

@ -0,0 +1,59 @@
---
layout: "aws"
page_title: "AWS: aws_iam_access_key"
sidebar_current: "docs-aws-resource-iam-access-key"
description: |-
Provides an IAM access key. This is a set of credentials that allow API requests to be made as an IAM user.
---
# aws\_iam\_access\_key
Provides an IAM access key. This is a set of credentials that allow API requests to be made as an IAM user.
## Example Usage
```
resource "aws_iam_user" "lb" {
name = "loadbalancer"
path = "/system/"
}
resource "aws_iam_access_key" "lb" {
user = "${aws_iam_user.lb.name}"
status = "Active"
}
resource "aws_iam_user_policy" "lb_ro" {
name = "test"
user = "${aws_iam_user.lb.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ec2:Describe*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}
```
## Argument Reference
The following arguments are supported:
* `user` - (Required) The IAM user to associate with this access key.
## Attributes Reference
The following attributes are exported:
* `id` - The access key ID.
* `secret` - The secret access key. Note that this will be written to the state file.
* `status` - "Active" or "Inactive". Keys are initially active, but can be made
inactive by other means.

View File

@ -0,0 +1,60 @@
---
layout: "aws"
page_title: "AWS: aws_iam_user"
sidebar_current: "docs-aws-resource-iam-user"
description: |-
Provides an IAM user.
---
# aws\_iam\_user
Provides an IAM user.
## Example Usage
```
resource "aws_iam_user" "lb" {
name = "loadbalancer"
path = "/system/"
}
resource "aws_iam_access_key" "lb" {
user = "${aws_iam_user.lb.name}"
status = "Active"
}
resource "aws_iam_user_policy" "lb_ro" {
name = "test"
user = "${aws_iam_user.lb.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ec2:Describe*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The user's name.
* `path` - (Optional, default "/") Path in which to create the user.
## Attributes Reference
The following attributes are exported:
* `unique_id` - The [unique ID][1] assigned by AWS.
* `arn` - The ARN assigned by AWS for this user.
[1]: http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html#GUIDs

View File

@ -0,0 +1,57 @@
---
layout: "aws"
page_title: "AWS: aws_iam_user_policy"
sidebar_current: "docs-aws-resource-iam-user-policy"
description: |-
Provides an IAM policy attached to a user.
---
# aws\_iam\_user\_policy
Provides an IAM policy attached to a user.
## Example Usage
```
resource "aws_iam_user" "lb" {
name = "loadbalancer"
path = "/system/"
}
resource "aws_iam_access_key" "lb" {
user = "${aws_iam_user.lb.name}"
status = "Active"
}
resource "aws_iam_user_policy" "lb_ro" {
name = "test"
user = "${aws_iam_user.lb.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ec2:Describe*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}
```
## Argument Reference
The following arguments are supported:
* `policy` - (Required) The policy document. This is a JSON formatted string.
The heredoc syntax or `file` function is helpful here.
* `name` - (Required) Name of the policy.
* `user` - (Required) IAM user to which to attach this policy.
## Attributes Reference
This resource has no attributes.

View File

@ -49,6 +49,18 @@
<a href="/docs/providers/aws/r/elb.html">aws_elb</a> <a href="/docs/providers/aws/r/elb.html">aws_elb</a>
</li> </li>
<li<%= sidebar_current("docs-aws-resource-iam=access-key") %>>
<a href="/docs/providers/aws/r/iam_access_key.html">aws_iam_access_key</a>
</li>
<li<%= sidebar_current("docs-aws-resource-iam-user") %>>
<a href="/docs/providers/aws/r/iam_user.html">aws_iam_user</a>
</li>
<li<%= sidebar_current("docs-aws-resource-iam-user-policy") %>>
<a href="/docs/providers/aws/r/iam_user_policy.html">aws_iam_user_policy</a>
</li>
<li<%= sidebar_current("docs-aws-resource-instance") %>> <li<%= sidebar_current("docs-aws-resource-instance") %>>
<a href="/docs/providers/aws/r/instance.html">aws_instance</a> <a href="/docs/providers/aws/r/instance.html">aws_instance</a>
</li> </li>