diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index e8e6d12bd97b..ee40ae6890f8 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -83,19 +83,28 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "aws_autoscaling_group": resourceAwsAutoscalingGroup(), "aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(), + "aws_autoscaling_group": resourceAwsAutoscalingGroup(), "aws_customer_gateway": resourceAwsCustomerGateway(), "aws_db_instance": resourceAwsDbInstance(), "aws_db_parameter_group": resourceAwsDbParameterGroup(), "aws_db_security_group": resourceAwsDbSecurityGroup(), "aws_db_subnet_group": resourceAwsDbSubnetGroup(), "aws_ebs_volume": resourceAwsEbsVolume(), + "aws_eip": resourceAwsEip(), "aws_elasticache_cluster": resourceAwsElasticacheCluster(), - "aws_elasticache_subnet_group": resourceAwsElasticacheSubnetGroup(), "aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(), - "aws_eip": resourceAwsEip(), + "aws_elasticache_subnet_group": resourceAwsElasticacheSubnetGroup(), "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_internet_gateway": resourceAwsInternetGateway(), "aws_key_pair": resourceAwsKeyPair(), @@ -107,15 +116,15 @@ func Provider() terraform.ResourceProvider { "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), "aws_route53_record": resourceAwsRoute53Record(), "aws_route53_zone": resourceAwsRoute53Zone(), - "aws_route_table": resourceAwsRouteTable(), "aws_route_table_association": resourceAwsRouteTableAssociation(), + "aws_route_table": resourceAwsRouteTable(), "aws_s3_bucket": resourceAwsS3Bucket(), "aws_security_group": resourceAwsSecurityGroup(), "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": resourceAwsVpcDhcpOptions(), + "aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(), + "aws_vpc": resourceAwsVpc(), "aws_vpn_connection": resourceAwsVpnConnection(), "aws_vpn_connection_route": resourceAwsVpnConnectionRoute(), "aws_vpn_gateway": resourceAwsVpnGateway(), diff --git a/builtin/providers/aws/resource_aws_autoscaling_group.go b/builtin/providers/aws/resource_aws_autoscaling_group.go index 741e3b72530f..051c54c04f26 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group.go @@ -6,7 +6,6 @@ import ( "strings" "time" - "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" @@ -81,9 +80,7 @@ func resourceAwsAutoscalingGroup() *schema.Resource { Required: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "load_balancers": &schema.Schema{ @@ -91,9 +88,7 @@ func resourceAwsAutoscalingGroup() *schema.Resource { Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "vpc_zone_identifier": &schema.Schema{ @@ -102,9 +97,7 @@ func resourceAwsAutoscalingGroup() *schema.Resource { Computed: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "termination_policies": &schema.Schema{ @@ -113,9 +106,7 @@ func resourceAwsAutoscalingGroup() *schema.Resource { Computed: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "tag": autoscalingTagsSchema(), diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index ed2da52ac6af..d4f37052baca 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -9,7 +9,6 @@ import ( "github.com/awslabs/aws-sdk-go/service/iam" "github.com/awslabs/aws-sdk-go/service/rds" - "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -132,18 +131,14 @@ func resourceAwsDbInstance() *schema.Resource { Optional: true, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "security_group_names": &schema.Schema{ Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "final_snapshot_identifier": &schema.Schema{ @@ -372,9 +367,7 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { // Create an empty schema.Set to hold all vpc security group ids ids := &schema.Set{ - F: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + F: schema.HashString, } for _, v := range v.VPCSecurityGroups { ids.Add(*v.VPCSecurityGroupID) @@ -383,9 +376,7 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { // Create an empty schema.Set to hold all security group names sgn := &schema.Set{ - F: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + F: schema.HashString, } for _, v := range v.DBSecurityGroups { sgn.Add(*v.DBSecurityGroupName) diff --git a/builtin/providers/aws/resource_aws_db_subnet_group.go b/builtin/providers/aws/resource_aws_db_subnet_group.go index d0cdbe4a2fb2..286099ac6efa 100644 --- a/builtin/providers/aws/resource_aws_db_subnet_group.go +++ b/builtin/providers/aws/resource_aws_db_subnet_group.go @@ -8,7 +8,6 @@ import ( "github.com/awslabs/aws-sdk-go/aws" "github.com/awslabs/aws-sdk-go/service/rds" - "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -37,9 +36,7 @@ func resourceAwsDbSubnetGroup() *schema.Resource { Required: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, }, } diff --git a/builtin/providers/aws/resource_aws_elb.go b/builtin/providers/aws/resource_aws_elb.go index c2af9593156d..2fba5469ecaa 100644 --- a/builtin/providers/aws/resource_aws_elb.go +++ b/builtin/providers/aws/resource_aws_elb.go @@ -43,9 +43,7 @@ func resourceAwsElb() *schema.Resource { Optional: true, ForceNew: true, Computed: true, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "instances": &schema.Schema{ @@ -53,9 +51,7 @@ func resourceAwsElb() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, Computed: true, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "security_groups": &schema.Schema{ @@ -63,9 +59,7 @@ func resourceAwsElb() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, Computed: true, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "source_security_group": &schema.Schema{ @@ -80,9 +74,7 @@ func resourceAwsElb() *schema.Resource { Optional: true, ForceNew: true, Computed: true, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "idle_timeout": &schema.Schema{ diff --git a/builtin/providers/aws/resource_aws_iam_access_key.go b/builtin/providers/aws/resource_aws_iam_access_key.go new file mode 100644 index 000000000000..efeec62f723b --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_access_key.go @@ -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 +} diff --git a/builtin/providers/aws/resource_aws_iam_access_key_test.go b/builtin/providers/aws/resource_aws_iam_access_key_test.go new file mode 100644 index 000000000000..0aacc058d70f --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_access_key_test.go @@ -0,0 +1,117 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSAccessKey_normal(t *testing.T) { + var conf iam.AccessKeyMetadata + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAccessKeyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSAccessKeyConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAccessKeyExists("aws_iam_access_key.a_key", &conf), + testAccCheckAWSAccessKeyAttributes(&conf), + ), + }, + }, + }) +} + +func testAccCheckAWSAccessKeyDestroy(s *terraform.State) error { + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_access_key" { + continue + } + + // Try to get access key + resp, err := iamconn.ListAccessKeys(&iam.ListAccessKeysInput{ + UserName: aws.String(rs.Primary.ID), + }) + if err == nil { + if len(resp.AccessKeyMetadata) > 0 { + return fmt.Errorf("still exist.") + } + return nil + } + + // Verify the error is what we want + ec2err, ok := err.(aws.APIError) + if !ok { + return err + } + if ec2err.Code != "NoSuchEntity" { + return err + } + } + + return nil +} + +func testAccCheckAWSAccessKeyExists(n string, res *iam.AccessKeyMetadata) 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 Role name is set") + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + resp, err := iamconn.ListAccessKeys(&iam.ListAccessKeysInput{ + UserName: aws.String("testuser"), + }) + if err != nil { + return err + } + + if len(resp.AccessKeyMetadata) != 1 || + *resp.AccessKeyMetadata[0].UserName != "testuser" { + return fmt.Errorf("User not found not found") + } + + *res = *resp.AccessKeyMetadata[0] + + return nil + } +} + +func testAccCheckAWSAccessKeyAttributes(accessKeyMetadata *iam.AccessKeyMetadata) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *accessKeyMetadata.UserName != "testuser" { + return fmt.Errorf("Bad username: %s", *accessKeyMetadata.UserName) + } + + if *accessKeyMetadata.Status != "Active" { + return fmt.Errorf("Bad status: %s", *accessKeyMetadata.Status) + } + + return nil + } +} + +const testAccAWSAccessKeyConfig = ` +resource "aws_iam_user" "a_user" { + name = "testuser" +} + +resource "aws_iam_access_key" "a_key" { + user = "${aws_iam_user.a_user.name}" +} +` diff --git a/builtin/providers/aws/resource_aws_iam_group.go b/builtin/providers/aws/resource_aws_iam_group.go new file mode 100644 index 000000000000..1d0bc6cf4bc6 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_group.go @@ -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 +} diff --git a/builtin/providers/aws/resource_aws_iam_group_policy.go b/builtin/providers/aws/resource_aws_iam_group_policy.go new file mode 100644 index 000000000000..51190aa5fc32 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_group_policy.go @@ -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.Id()) + + 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.Id()) + + 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(id string) (groupName, policyName string) { + parts := strings.SplitN(id, ":", 2) + groupName = parts[0] + policyName = parts[1] + return +} diff --git a/builtin/providers/aws/resource_aws_iam_group_policy_test.go b/builtin/providers/aws/resource_aws_iam_group_policy_test.go new file mode 100644 index 000000000000..1778d52165a7 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_group_policy_test.go @@ -0,0 +1,112 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSIAMGroupPolicy(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIAMGroupPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccIAMGroupPolicyConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMGroupPolicy( + "aws_iam_group.group", + "aws_iam_group_policy.foo", + ), + ), + }, + resource.TestStep{ + Config: testAccIAMGroupPolicyConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMGroupPolicy( + "aws_iam_group.group", + "aws_iam_group_policy.bar", + ), + ), + }, + }, + }) +} + +func testAccCheckIAMGroupPolicyDestroy(s *terraform.State) error { + if len(s.RootModule().Resources) > 0 { + return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources) + } + + return nil +} + +func testAccCheckIAMGroupPolicy( + iamGroupResource string, + iamGroupPolicyResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[iamGroupResource] + if !ok { + return fmt.Errorf("Not Found: %s", iamGroupResource) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + policy, ok := s.RootModule().Resources[iamGroupPolicyResource] + if !ok { + return fmt.Errorf("Not Found: %s", iamGroupPolicyResource) + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + group, name := resourceAwsIamGroupPolicyParseId(policy.Primary.ID) + _, err := iamconn.GetGroupPolicy(&iam.GetGroupPolicyInput{ + GroupName: aws.String(group), + PolicyName: aws.String(name), + }) + + if err != nil { + return err + } + + return nil + } +} + +const testAccIAMGroupPolicyConfig = ` +resource "aws_iam_group" "group" { + name = "test_group" + path = "/" +} + +resource "aws_iam_group_policy" "foo" { + name = "foo_policy" + group = "${aws_iam_group.group.name}" + policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}" +} +` + +const testAccIAMGroupPolicyConfigUpdate = ` +resource "aws_iam_group" "group" { + name = "test_group" + path = "/" +} + +resource "aws_iam_group_policy" "foo" { + name = "foo_policy" + group = "${aws_iam_group.group.name}" + policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}" +} + +resource "aws_iam_group_policy" "bar" { + name = "bar_policy" + group = "${aws_iam_group.group.name}" + policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}" +} +` diff --git a/builtin/providers/aws/resource_aws_iam_group_test.go b/builtin/providers/aws/resource_aws_iam_group_test.go new file mode 100644 index 000000000000..b28f152fde98 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_group_test.go @@ -0,0 +1,106 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSGroup_normal(t *testing.T) { + var conf iam.GetGroupOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSGroupConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSGroupExists("aws_iam_group.group", &conf), + testAccCheckAWSGroupAttributes(&conf), + ), + }, + }, + }) +} + +func testAccCheckAWSGroupDestroy(s *terraform.State) error { + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iam_group" { + continue + } + + // Try to get group + _, err := iamconn.GetGroup(&iam.GetGroupInput{ + GroupName: aws.String(rs.Primary.ID), + }) + if err == nil { + return fmt.Errorf("still exist.") + } + + // Verify the error is what we want + ec2err, ok := err.(aws.APIError) + if !ok { + return err + } + if ec2err.Code != "NoSuchEntity" { + return err + } + } + + return nil +} + +func testAccCheckAWSGroupExists(n string, res *iam.GetGroupOutput) 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 Group name is set") + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + resp, err := iamconn.GetGroup(&iam.GetGroupInput{ + GroupName: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + *res = *resp + + return nil + } +} + +func testAccCheckAWSGroupAttributes(group *iam.GetGroupOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *group.Group.GroupName != "test-group" { + return fmt.Errorf("Bad name: %s", *group.Group.GroupName) + } + + if *group.Group.Path != "/" { + return fmt.Errorf("Bad path: %s", *group.Group.Path) + } + + return nil + } +} + +const testAccAWSGroupConfig = ` +resource "aws_iam_group" "group" { + name = "test-group" + path = "/" +} +` diff --git a/builtin/providers/aws/resource_aws_iam_instance_profile.go b/builtin/providers/aws/resource_aws_iam_instance_profile.go new file mode 100644 index 000000000000..506935d8eb80 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_instance_profile.go @@ -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 +} diff --git a/builtin/providers/aws/resource_aws_iam_instance_profile_test.go b/builtin/providers/aws/resource_aws_iam_instance_profile_test.go new file mode 100644 index 000000000000..0d50024ecca6 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_instance_profile_test.go @@ -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}"] +} +` diff --git a/builtin/providers/aws/resource_aws_iam_policy.go b/builtin/providers/aws/resource_aws_iam_policy.go new file mode 100644 index 000000000000..9d4e3d6dd159 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_policy.go @@ -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 +} diff --git a/builtin/providers/aws/resource_aws_iam_role.go b/builtin/providers/aws/resource_aws_iam_role.go new file mode 100644 index 000000000000..6a9bdc00ad47 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_role.go @@ -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 +} diff --git a/builtin/providers/aws/resource_aws_iam_role_policy.go b/builtin/providers/aws/resource_aws_iam_role_policy.go new file mode 100644 index 000000000000..0eb073a9d7d4 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_role_policy.go @@ -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.Id()) + + 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.Id()) + + 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(id string) (roleName, policyName string) { + parts := strings.SplitN(id, ":", 2) + roleName = parts[0] + policyName = parts[1] + return +} diff --git a/builtin/providers/aws/resource_aws_iam_role_policy_test.go b/builtin/providers/aws/resource_aws_iam_role_policy_test.go new file mode 100644 index 000000000000..a43fe94c841d --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_role_policy_test.go @@ -0,0 +1,114 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSIAMRolePolicy(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIAMRolePolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccIAMRolePolicyConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMRolePolicy( + "aws_iam_role.role", + "aws_iam_role_policy.foo", + ), + ), + }, + resource.TestStep{ + Config: testAccIAMRolePolicyConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMRolePolicy( + "aws_iam_role.role", + "aws_iam_role_policy.bar", + ), + ), + }, + }, + }) +} + +func testAccCheckIAMRolePolicyDestroy(s *terraform.State) error { + if len(s.RootModule().Resources) > 0 { + return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources) + } + + return nil +} + +func testAccCheckIAMRolePolicy( + iamRoleResource string, + iamRolePolicyResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[iamRoleResource] + if !ok { + return fmt.Errorf("Not Found: %s", iamRoleResource) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + policy, ok := s.RootModule().Resources[iamRolePolicyResource] + if !ok { + return fmt.Errorf("Not Found: %s", iamRolePolicyResource) + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + role, name := resourceAwsIamRolePolicyParseId(policy.Primary.ID) + _, err := iamconn.GetRolePolicy(&iam.GetRolePolicyInput{ + RoleName: aws.String(role), + PolicyName: aws.String(name), + }) + + if err != nil { + return err + } + + return nil + } +} + +const testAccIAMRolePolicyConfig = ` +resource "aws_iam_role" "role" { + name = "test_role" + path = "/" + assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Effect\":\"Allow\",\"Sid\":\"\"}]}" +} + +resource "aws_iam_role_policy" "foo" { + name = "foo_policy" + role = "${aws_iam_role.role.name}" + policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}" +} +` + +const testAccIAMRolePolicyConfigUpdate = ` +resource "aws_iam_role" "role" { + name = "test_role" + path = "/" + assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Effect\":\"Allow\",\"Sid\":\"\"}]}" +} + +resource "aws_iam_role_policy" "foo" { + name = "foo_policy" + role = "${aws_iam_role.role.name}" + policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}" +} + +resource "aws_iam_role_policy" "bar" { + name = "bar_policy" + role = "${aws_iam_role.role.name}" + policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}" +} +` diff --git a/builtin/providers/aws/resource_aws_iam_role_test.go b/builtin/providers/aws/resource_aws_iam_role_test.go new file mode 100644 index 000000000000..d7f05fcfc0d4 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_role_test.go @@ -0,0 +1,106 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSRole_normal(t *testing.T) { + var conf iam.GetRoleOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSRoleConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRoleExists("aws_iam_role.role", &conf), + testAccCheckAWSRoleAttributes(&conf), + ), + }, + }, + }) +} + +func testAccCheckAWSRoleDestroy(s *terraform.State) error { + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iam_role" { + continue + } + + // Try to get role + _, err := iamconn.GetRole(&iam.GetRoleInput{ + RoleName: aws.String(rs.Primary.ID), + }) + if err == nil { + return fmt.Errorf("still exist.") + } + + // Verify the error is what we want + ec2err, ok := err.(aws.APIError) + if !ok { + return err + } + if ec2err.Code != "NoSuchEntity" { + return err + } + } + + return nil +} + +func testAccCheckAWSRoleExists(n string, res *iam.GetRoleOutput) 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 Role name is set") + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + resp, err := iamconn.GetRole(&iam.GetRoleInput{ + RoleName: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + *res = *resp + + return nil + } +} + +func testAccCheckAWSRoleAttributes(role *iam.GetRoleOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *role.Role.RoleName != "test-role" { + return fmt.Errorf("Bad name: %s", *role.Role.RoleName) + } + + if *role.Role.Path != "/" { + return fmt.Errorf("Bad path: %s", *role.Role.Path) + } + return nil + } +} + +const testAccAWSRoleConfig = ` +resource "aws_iam_role" "role" { + name = "test-role" + path = "/" + assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}" +} +` diff --git a/builtin/providers/aws/resource_aws_iam_user.go b/builtin/providers/aws/resource_aws_iam_user.go new file mode 100644 index 000000000000..73c5ac8f0505 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_user.go @@ -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 +} diff --git a/builtin/providers/aws/resource_aws_iam_user_policy.go b/builtin/providers/aws/resource_aws_iam_user_policy.go new file mode 100644 index 000000000000..96ea6afc0e88 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_user_policy.go @@ -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.Id()) + + 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.Id()) + + 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(id string) (userName, policyName string) { + parts := strings.SplitN(id, ":", 2) + userName = parts[0] + policyName = parts[1] + return +} diff --git a/builtin/providers/aws/resource_aws_iam_user_policy_test.go b/builtin/providers/aws/resource_aws_iam_user_policy_test.go new file mode 100644 index 000000000000..2a2147e567de --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_user_policy_test.go @@ -0,0 +1,112 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSIAMUserPolicy(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIAMUserPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccIAMUserPolicyConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMUserPolicy( + "aws_iam_user.user", + "aws_iam_user_policy.foo", + ), + ), + }, + resource.TestStep{ + Config: testAccIAMUserPolicyConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMUserPolicy( + "aws_iam_user.user", + "aws_iam_user_policy.bar", + ), + ), + }, + }, + }) +} + +func testAccCheckIAMUserPolicyDestroy(s *terraform.State) error { + if len(s.RootModule().Resources) > 0 { + return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources) + } + + return nil +} + +func testAccCheckIAMUserPolicy( + iamUserResource string, + iamUserPolicyResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[iamUserResource] + if !ok { + return fmt.Errorf("Not Found: %s", iamUserResource) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + policy, ok := s.RootModule().Resources[iamUserPolicyResource] + if !ok { + return fmt.Errorf("Not Found: %s", iamUserPolicyResource) + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + username, name := resourceAwsIamUserPolicyParseId(policy.Primary.ID) + _, err := iamconn.GetUserPolicy(&iam.GetUserPolicyInput{ + UserName: aws.String(username), + PolicyName: aws.String(name), + }) + + if err != nil { + return err + } + + return nil + } +} + +const testAccIAMUserPolicyConfig = ` +resource "aws_iam_user" "user" { + name = "test_user" + path = "/" +} + +resource "aws_iam_user_policy" "foo" { + name = "foo_policy" + user = "${aws_iam_user.user.name}" + policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}" +} +` + +const testAccIAMUserPolicyConfigUpdate = ` +resource "aws_iam_user" "user" { + name = "test_user" + path = "/" +} + +resource "aws_iam_user_policy" "foo" { + name = "foo_policy" + user = "${aws_iam_user.user.name}" + policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}" +} + +resource "aws_iam_user_policy" "bar" { + name = "bar_policy" + user = "${aws_iam_user.user.name}" + policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}" +} +` diff --git a/builtin/providers/aws/resource_aws_iam_user_test.go b/builtin/providers/aws/resource_aws_iam_user_test.go new file mode 100644 index 000000000000..513636f98613 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_user_test.go @@ -0,0 +1,106 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSUser_normal(t *testing.T) { + var conf iam.GetUserOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSUserDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSUserConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSUserExists("aws_iam_user.user", &conf), + testAccCheckAWSUserAttributes(&conf), + ), + }, + }, + }) +} + +func testAccCheckAWSUserDestroy(s *terraform.State) error { + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iam_user" { + continue + } + + // Try to get user + _, err := iamconn.GetUser(&iam.GetUserInput{ + UserName: aws.String(rs.Primary.ID), + }) + if err == nil { + return fmt.Errorf("still exist.") + } + + // Verify the error is what we want + ec2err, ok := err.(aws.APIError) + if !ok { + return err + } + if ec2err.Code != "NoSuchEntity" { + return err + } + } + + return nil +} + +func testAccCheckAWSUserExists(n string, res *iam.GetUserOutput) 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 User name is set") + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + resp, err := iamconn.GetUser(&iam.GetUserInput{ + UserName: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + + *res = *resp + + return nil + } +} + +func testAccCheckAWSUserAttributes(user *iam.GetUserOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *user.User.UserName != "test-user" { + return fmt.Errorf("Bad name: %s", *user.User.UserName) + } + + if *user.User.Path != "/" { + return fmt.Errorf("Bad path: %s", *user.User.Path) + } + + return nil + } +} + +const testAccAWSUserConfig = ` +resource "aws_iam_user" "user" { + name = "test-user" + path = "/" +} +` diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index c66e335b1d67..c95b722ed7d1 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -107,9 +107,7 @@ func resourceAwsInstance() *schema.Resource { Computed: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "vpc_security_group_ids": &schema.Schema{ diff --git a/builtin/providers/aws/resource_aws_launch_configuration.go b/builtin/providers/aws/resource_aws_launch_configuration.go index 8d8e09342bc4..406362af2dd8 100644 --- a/builtin/providers/aws/resource_aws_launch_configuration.go +++ b/builtin/providers/aws/resource_aws_launch_configuration.go @@ -76,9 +76,7 @@ func resourceAwsLaunchConfiguration() *schema.Resource { Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "associate_public_ip_address": &schema.Schema{ diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index 00c913b119d0..095bfb955b2b 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -34,9 +34,7 @@ func resourceAwsNetworkInterface() *schema.Resource { Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "security_groups": &schema.Schema{ @@ -44,9 +42,7 @@ func resourceAwsNetworkInterface() *schema.Resource { Optional: true, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, "attachment": &schema.Schema{ diff --git a/builtin/providers/aws/resource_aws_route53_record.go b/builtin/providers/aws/resource_aws_route53_record.go index 19fb87fdd4ed..ad3795712731 100644 --- a/builtin/providers/aws/resource_aws_route53_record.go +++ b/builtin/providers/aws/resource_aws_route53_record.go @@ -88,9 +88,7 @@ func resourceAwsRoute53Record() *schema.Resource { ConflictsWith: []string{"alias"}, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, + Set: schema.HashString, }, }, } diff --git a/builtin/providers/aws/structure_test.go b/builtin/providers/aws/structure_test.go index 9c7eaf88d411..6ec80274cedc 100644 --- a/builtin/providers/aws/structure_test.go +++ b/builtin/providers/aws/structure_test.go @@ -10,7 +10,6 @@ import ( "github.com/awslabs/aws-sdk-go/service/rds" "github.com/awslabs/aws-sdk-go/service/route53" "github.com/hashicorp/terraform/flatmap" - "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" ) @@ -38,9 +37,7 @@ func testConf() map[string]string { } func TestexpandIPPerms(t *testing.T) { - hash := func(v interface{}) int { - return hashcode.String(v.(string)) - } + hash := schema.HashString expanded := []interface{}{ map[string]interface{}{ @@ -121,9 +118,7 @@ func TestexpandIPPerms(t *testing.T) { } func TestExpandIPPerms_nonVPC(t *testing.T) { - hash := func(v interface{}) int { - return hashcode.String(v.(string)) - } + hash := schema.HashString expanded := []interface{}{ map[string]interface{}{ diff --git a/helper/schema/set.go b/helper/schema/set.go index fbfd78987462..7176b86308ad 100644 --- a/helper/schema/set.go +++ b/helper/schema/set.go @@ -5,8 +5,16 @@ import ( "reflect" "sort" "sync" + + "github.com/hashicorp/terraform/helper/hashcode" ) +// HashString hashes strings. If you want a Set of strings, this is the +// SchemaSetFunc you want. +func HashString(v interface{}) int { + return hashcode.String(v.(string)) +} + // Set is a set data structure that is returned for elements of type // TypeSet. type Set struct { @@ -27,11 +35,21 @@ func NewSet(f SchemaSetFunc, items []interface{}) *Set { 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. func (s *Set) Add(item interface{}) { 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. func (s *Set) Contains(item interface{}) bool { _, ok := s.m[s.hash(item)] @@ -139,6 +157,15 @@ func (s *Set) hash(item interface{}) int { 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 { return sort.SearchInts(s.listCode(), s.hash(item)) } diff --git a/website/source/docs/providers/aws/r/iam_access_key.html.markdown b/website/source/docs/providers/aws/r/iam_access_key.html.markdown new file mode 100644 index 000000000000..0040c260c158 --- /dev/null +++ b/website/source/docs/providers/aws/r/iam_access_key.html.markdown @@ -0,0 +1,60 @@ +--- +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 = <aws_elb + > + aws_iam_access_key + + + > + aws_iam_group + + + > + aws_iam_group_policy + + + > + aws_iam_instance_profile + + + > + aws_iam_policy + + + > + aws_iam_role_policy + + + > + aws_iam_user + + + > + aws_iam_user_policy + + > aws_instance