diff --git a/CHANGELOG.md b/CHANGELOG.md index eb444f78322..956a686d037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,17 +2,22 @@ IMPROVEMENTS: +- *New Resource*: _alicloud_ram_role_ [GH-48] +- *New Resource*: _alicloud_ram_role_attachment_ [GH-48] +- *New Resource*: _alicloud_ram_role_polocy_attachment_ [GH-48] - *New Resource*: _alicloud_container_cluster_ [GH-47] - *New Resource:* _alicloud_ram_policy_ [GH-46] - *New Resource*: _alicloud_ram_user_policy_attachment_ [GH-46] - *New Resource* _alicloud_ram_user_ [GH-44] - *New Datasource* _alicloud_ram_policies_ [GH-46] - *New Datasource* _alicloud_ram_users_ [GH-44] +- *New Datasource*: _alicloud_ram_roles_ [GH-48] - Added support for importing: - `alicloud_container_cluster` [GH-47] - `alicloud_ram_policy` [GH-46] - `alicloud_ram_user` [GH-44] + - `alicloud_ram_role` [GH-48] ## 0.1.1 (December 11, 2017) diff --git a/alicloud/common.go b/alicloud/common.go index ba448605491..3b972619f0a 100644 --- a/alicloud/common.go +++ b/alicloud/common.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "encoding/base64" "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/schema" @@ -153,3 +154,14 @@ func (client *AliyunClient) JudgeRegionValidation(key string, region common.Regi } return fmt.Errorf("'%s' is invalid. Expected on %v.", key, strings.Join(rs, ", ")) } + +func userDataHashSum(user_data string) string { + // Check whether the user_data is not Base64 encoded. + // Always calculate hash of base64 decoded value since we + // check against double-encoding when setting it + v, base64DecodeError := base64.StdEncoding.DecodeString(user_data) + if base64DecodeError != nil { + v = []byte(user_data) + } + return string(v) +} diff --git a/alicloud/data_source_alicloud_ram_roles.go b/alicloud/data_source_alicloud_ram_roles.go new file mode 100644 index 00000000000..fc938a6e773 --- /dev/null +++ b/alicloud/data_source_alicloud_ram_roles.go @@ -0,0 +1,177 @@ +package alicloud + +import ( + "fmt" + "log" + "regexp" + + "github.com/denverdino/aliyungo/ram" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAlicloudRamRoles() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAlicloudRamRolesRead, + + Schema: map[string]*schema.Schema{ + "name_regex": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "policy_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateRamPolicyName, + }, + "policy_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validatePolicyType, + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + }, + + // Computed values + "roles": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "assume_role_policy_document": { + Type: schema.TypeString, + Computed: true, + }, + "document": { + Type: schema.TypeString, + Computed: true, + }, + "create_date": { + Type: schema.TypeString, + Computed: true, + }, + "update_date": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceAlicloudRamRolesRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).ramconn + allRoles := []interface{}{} + + allRolesMap := make(map[string]interface{}) + policyFilterRolesMap := make(map[string]interface{}) + + dataMap := []map[string]interface{}{} + + policyName, policyNameOk := d.GetOk("policy_name") + policyType, policyTypeOk := d.GetOk("policy_type") + nameRegex, nameRegexOk := d.GetOk("name_regex") + + if policyTypeOk && !policyNameOk { + return fmt.Errorf("You must set 'policy_name' at one time when you set 'policy_type'.") + } + + // all roles + resp, err := conn.ListRoles() + if err != nil { + return fmt.Errorf("ListRoles got an error: %#v", err) + } + for _, v := range resp.Roles.Role { + if nameRegexOk { + r := regexp.MustCompile(nameRegex.(string)) + if !r.MatchString(v.RoleName) { + continue + } + } + allRolesMap[v.RoleName] = v + } + + // roles which attach with this policy + if policyNameOk { + pType := ram.System + if policyTypeOk { + pType = ram.Type(policyType.(string)) + } + resp, err := conn.ListEntitiesForPolicy(ram.PolicyRequest{PolicyName: policyName.(string), PolicyType: pType}) + if err != nil { + return fmt.Errorf("ListEntitiesForPolicy got an error: %#v", err) + } + + for _, v := range resp.Roles.Role { + policyFilterRolesMap[v.RoleName] = v + } + dataMap = append(dataMap, policyFilterRolesMap) + } + + // GetIntersection of each map + allRoles = GetIntersection(dataMap, allRolesMap) + + if len(allRoles) < 1 { + return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") + } + + log.Printf("[DEBUG] alicloud_ram_roles - Roles found: %#v", allRoles) + + return ramRolesDescriptionAttributes(d, meta, allRoles) +} + +func ramRolesDescriptionAttributes(d *schema.ResourceData, meta interface{}, roles []interface{}) error { + var ids []string + var s []map[string]interface{} + for _, v := range roles { + role := v.(ram.Role) + conn := meta.(*AliyunClient).ramconn + resp, _ := conn.GetRole(ram.RoleQueryRequest{RoleName: role.RoleName}) + mapping := map[string]interface{}{ + "id": role.RoleId, + "name": role.RoleName, + "arn": role.Arn, + "description": role.Description, + "create_date": role.CreateDate, + "update_date": role.UpdateDate, + "assume_role_policy_document": resp.Role.AssumeRolePolicyDocument, + "document": resp.Role.AssumeRolePolicyDocument, + } + log.Printf("[DEBUG] alicloud_ram_roles - adding role: %v", mapping) + ids = append(ids, role.RoleId) + s = append(s, mapping) + } + + d.SetId(dataResourceIdHash(ids)) + if err := d.Set("roles", s); err != nil { + return err + } + + // create a json file in current directory and write data source to it. + if output, ok := d.GetOk("output_file"); ok && output.(string) != "" { + writeToFile(output.(string), s) + } + return nil +} diff --git a/alicloud/data_source_alicloud_ram_roles_test.go b/alicloud/data_source_alicloud_ram_roles_test.go new file mode 100644 index 00000000000..7572c74c1b2 --- /dev/null +++ b/alicloud/data_source_alicloud_ram_roles_test.go @@ -0,0 +1,78 @@ +package alicloud + +import ( + "github.com/hashicorp/terraform/helper/resource" + "testing" +) + +func TestAccAlicloudRamRolesDataSource_for_policy(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckAlicloudRamRolesDataSourceForPolicyConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAlicloudDataSourceID("data.alicloud_ram_roles.role"), + resource.TestCheckResourceAttr("data.alicloud_ram_roles.role", "roles.#", "1"), + resource.TestCheckResourceAttr("data.alicloud_ram_roles.role", "roles.0.name", "testrole"), + resource.TestCheckResourceAttr("data.alicloud_ram_roles.role", "roles.0.arn", "acs:ram::1307087942598154:role/testrole"), + resource.TestCheckResourceAttr("data.alicloud_ram_roles.role", "roles.0.id", "345148520161269882"), + ), + }, + }, + }) +} + +func TestAccAlicloudRamRolesDataSource_for_all(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckAlicloudRamRolesDataSourceForAllConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAlicloudDataSourceID("data.alicloud_ram_roles.role"), + resource.TestCheckResourceAttr("data.alicloud_ram_roles.role", "roles.#", "3"), + ), + }, + }, + }) +} + +func TestAccAlicloudRamRolesDataSource_role_name_regex(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckAlicloudRamRolesDataSourceRoleNameRegexConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAlicloudDataSourceID("data.alicloud_ram_roles.role"), + resource.TestCheckResourceAttr("data.alicloud_ram_roles.role", "roles.#", "2"), + ), + }, + }, + }) +} + +const testAccCheckAlicloudRamRolesDataSourceForPolicyConfig = ` +data "alicloud_ram_roles" "role" { + policy_name = "AliyunACSDefaultAccess" + policy_type = "Custom" +}` + +const testAccCheckAlicloudRamRolesDataSourceForAllConfig = ` +data "alicloud_ram_roles" "role" { +}` + +const testAccCheckAlicloudRamRolesDataSourceRoleNameRegexConfig = ` +data "alicloud_ram_roles" "role" { + name_regex = "^test" +}` diff --git a/alicloud/extension_ecs.go b/alicloud/extension_ecs.go index 245624afe66..94c8f9069db 100644 --- a/alicloud/extension_ecs.go +++ b/alicloud/extension_ecs.go @@ -2,13 +2,6 @@ package alicloud import "github.com/denverdino/aliyungo/ecs" -type GroupRuleDirection string - -const ( - GroupRuleIngress = GroupRuleDirection("ingress") - GroupRuleEgress = GroupRuleDirection("egress") -) - type GroupRuleIpProtocol string const ( diff --git a/alicloud/import_alicloud_ram_role_test.go b/alicloud/import_alicloud_ram_role_test.go new file mode 100644 index 00000000000..1523d1fd1dd --- /dev/null +++ b/alicloud/import_alicloud_ram_role_test.go @@ -0,0 +1,29 @@ +package alicloud + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAlicloudRamRole_importBasic(t *testing.T) { + resourceName := "alicloud_ram_role.role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRamRoleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRamRoleConfig, + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"force"}, + }, + }, + }) +} diff --git a/alicloud/provider.go b/alicloud/provider.go index 968f7faf0a6..b5b85e90484 100644 --- a/alicloud/provider.go +++ b/alicloud/provider.go @@ -39,9 +39,13 @@ func Provider() terraform.ResourceProvider { "alicloud_instance_types": dataSourceAlicloudInstanceTypes(), "alicloud_vpcs": dataSourceAlicloudVpcs(), "alicloud_key_pairs": dataSourceAlicloudKeyPairs(), + "alicloud_ram_users": dataSourceAlicloudRamUsers(), + "alicloud_ram_roles": dataSourceAlicloudRamRoles(), + "alicloud_ram_policies": dataSourceAlicloudRamPolicies(), }, ResourcesMap: map[string]*schema.Resource{ "alicloud_instance": resourceAliyunInstance(), + "alicloud_ram_role_attachment": resourceAlicloudRamRoleAttachment(), "alicloud_disk": resourceAliyunDisk(), "alicloud_disk_attachment": resourceAliyunDiskAttachment(), "alicloud_security_group": resourceAliyunSecurityGroup(), @@ -54,21 +58,26 @@ func Provider() terraform.ResourceProvider { "alicloud_vpc": resourceAliyunVpc(), "alicloud_nat_gateway": resourceAliyunNatGateway(), //both subnet and vswith exists,cause compatible old version, and compatible aws habit. - "alicloud_subnet": resourceAliyunSubnet(), - "alicloud_vswitch": resourceAliyunSubnet(), - "alicloud_route_entry": resourceAliyunRouteEntry(), - "alicloud_snat_entry": resourceAliyunSnatEntry(), - "alicloud_forward_entry": resourceAliyunForwardEntry(), - "alicloud_eip": resourceAliyunEip(), - "alicloud_eip_association": resourceAliyunEipAssociation(), - "alicloud_slb": resourceAliyunSlb(), - "alicloud_slb_attachment": resourceAliyunSlbAttachment(), - "alicloud_oss_bucket": resourceAlicloudOssBucket(), - "alicloud_oss_bucket_object": resourceAlicloudOssBucketObject(), - "alicloud_key_pair": resourceAlicloudKeyPair(), - "alicloud_key_pair_attachment": resourceAlicloudKeyPairAttachment(), - "alicloud_container_cluster": resourceAlicloudContainerCluster(), - "alicloud_router_interface": resourceAlicloudRouterInterface(), + "alicloud_subnet": resourceAliyunSubnet(), + "alicloud_vswitch": resourceAliyunSubnet(), + "alicloud_route_entry": resourceAliyunRouteEntry(), + "alicloud_snat_entry": resourceAliyunSnatEntry(), + "alicloud_forward_entry": resourceAliyunForwardEntry(), + "alicloud_eip": resourceAliyunEip(), + "alicloud_eip_association": resourceAliyunEipAssociation(), + "alicloud_slb": resourceAliyunSlb(), + "alicloud_slb_attachment": resourceAliyunSlbAttachment(), + "alicloud_oss_bucket": resourceAlicloudOssBucket(), + "alicloud_oss_bucket_object": resourceAlicloudOssBucketObject(), + "alicloud_key_pair": resourceAlicloudKeyPair(), + "alicloud_key_pair_attachment": resourceAlicloudKeyPairAttachment(), + "alicloud_ram_user": resourceAlicloudRamUser(), + "alicloud_ram_role": resourceAlicloudRamRole(), + "alicloud_ram_policy": resourceAlicloudRamPolicy(), + "alicloud_ram_user_policy_attachment": resourceAlicloudRamUserPolicyAtatchment(), + "alicloud_ram_role_policy_attachment": resourceAlicloudRamRolePolicyAttachment(), + "alicloud_container_cluster": resourceAlicloudContainerCluster(), + "alicloud_router_interface": resourceAlicloudRouterInterface(), }, ConfigureFunc: providerConfigure, diff --git a/alicloud/resource_alicloud_instance.go b/alicloud/resource_alicloud_instance.go index 64b7875b043..e517ad916c4 100644 --- a/alicloud/resource_alicloud_instance.go +++ b/alicloud/resource_alicloud_instance.go @@ -4,7 +4,6 @@ import ( "fmt" "log" - "encoding/base64" "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/resource" @@ -37,9 +36,10 @@ func resourceAliyunInstance() *schema.Resource { }, "instance_type": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateInstanceType, }, "security_groups": &schema.Schema{ @@ -166,6 +166,11 @@ func resourceAliyunInstance() *schema.Resource { Optional: true, ForceNew: true, }, + "role_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, "key_name": &schema.Schema{ Type: schema.TypeString, @@ -204,7 +209,7 @@ func resourceAliyunInstanceCreate(d *schema.ResourceData, meta interface{}) erro // after instance created, its status is pending, // so we need to wait it become to stopped and then start it if err := conn.WaitForInstanceAsyn(d.Id(), ecs.Stopped, defaultTimeout); err != nil { - return fmt.Errorf("[DEBUG] WaitForInstance %s got error: %#v", ecs.Stopped, err) + return fmt.Errorf("WaitForInstance %s got error: %#v", ecs.Stopped, err) } if err := allocateIpAndBandWidthRelative(d, meta); err != nil { @@ -300,6 +305,27 @@ func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error d.Set("user_data", userDataHashSum(ud.UserData)) } + if d.Get("role_name").(string) != "" { + for { + response, err := conn.DescribeInstanceRamRole(&ecs.AttachInstancesArgs{ + RegionId: getRegion(d, meta), + InstanceIds: convertListToJsonString([]interface{}{d.Id()}), + }) + if err != nil { + if IsExceptedError(err, RoleAttachmentUnExpectedJson) { + continue + } + log.Printf("[ERROR] DescribeInstanceRamRole for instance got error: %#v", err) + } + + if len(response.InstanceRamRoleSets.InstanceRamRoleSet) == 0 { + return fmt.Errorf("No Ram role for instance found.") + } + d.Set("role_name", response.InstanceRamRoleSets.InstanceRamRoleSet[0].RamRoleName) + break + } + } + tags, _, err := conn.DescribeTags(&ecs.DescribeTagsArgs{ RegionId: getRegion(d, meta), ResourceType: ecs.TagResourceInstance, @@ -315,7 +341,6 @@ func resourceAliyunInstanceRead(d *schema.ResourceData, meta interface{}) error } func resourceAliyunInstanceUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*AliyunClient) conn := client.ecsconn @@ -338,6 +363,7 @@ func resourceAliyunInstanceUpdate(d *schema.ResourceData, meta interface{}) erro Size: d.Get("system_disk_size").(int), }, } + if v, ok := d.GetOk("status"); ok && v.(string) != "" { if ecs.InstanceStatus(d.Get("status").(string)) == ecs.Running { log.Printf("[DEBUG] StopInstance before change system disk") @@ -349,10 +375,12 @@ func resourceAliyunInstanceUpdate(d *schema.ResourceData, meta interface{}) erro } } } + _, err := conn.ReplaceSystemDisk(replaceSystemArgs) if err != nil { return fmt.Errorf("Replace system disk got an error: %#v", err) } + // Ensure instance's image has been replaced successfully. timeout := ecs.InstanceDefaultTimeout for { @@ -360,15 +388,18 @@ func resourceAliyunInstanceUpdate(d *schema.ResourceData, meta interface{}) erro if errDesc != nil { return fmt.Errorf("Describe instance got an error: %#v", errDesc) } + if instance.ImageId == d.Get("image_id") { break } time.Sleep(ecs.DefaultWaitForInterval * time.Second) + timeout = timeout - ecs.DefaultWaitForInterval if timeout <= 0 { return common.GetClientErrorFromString("Timeout") } } + imageUpdate = true d.SetPartial("system_disk_size") d.SetPartial("image_id") @@ -446,6 +477,7 @@ func resourceAliyunInstanceUpdate(d *schema.ResourceData, meta interface{}) erro if err := conn.WaitForInstance(d.Id(), ecs.Running, 500); err != nil { return fmt.Errorf("WaitForInstance got error: %#v", err) } + } if d.HasChange("security_groups") { @@ -513,6 +545,7 @@ func allocateIpAndBandWidthRelative(d *schema.ResourceData, meta interface{}) er if d.Get("internet_max_bandwidth_out") == 0 { return fmt.Errorf("Error: if allocate_public_ip is true than the internet_max_bandwidth_out cannot equal zero.") } + _, err := conn.AllocatePublicIpAddress(d.Id()) if err != nil { return fmt.Errorf("[DEBUG] AllocatePublicIpAddress for instance got error: %#v", err) @@ -623,20 +656,16 @@ func buildAliyunInstanceArgs(d *schema.ResourceData, meta interface{}) (*ecs.Cre args.UserData = v } + if v := d.Get("role_name").(string); v != "" { + if vswitchValue == "" { + return nil, fmt.Errorf("Role name only supported for VPC instance.") + } + args.RamRoleName = v + } + if v := d.Get("key_name").(string); v != "" { args.KeyPairName = v } return args, nil } - -func userDataHashSum(user_data string) string { - // Check whether the user_data is not Base64 encoded. - // Always calculate hash of base64 decoded value since we - // check against double-encoding when setting it - v, base64DecodeError := base64.StdEncoding.DecodeString(user_data) - if base64DecodeError != nil { - v = []byte(user_data) - } - return string(v) -} diff --git a/alicloud/resource_alicloud_instance_test.go b/alicloud/resource_alicloud_instance_test.go index 8c737f6fca1..daf700d6e8c 100644 --- a/alicloud/resource_alicloud_instance_test.go +++ b/alicloud/resource_alicloud_instance_test.go @@ -6,7 +6,6 @@ import ( "log" - "github.com/denverdino/aliyungo/common" "github.com/denverdino/aliyungo/ecs" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" @@ -430,6 +429,7 @@ func TestAccAlicloudInstanceImage_update(t *testing.T) { "50"), ), }, + resource.TestStep{ Config: testAccCheckInstanceImageUpdate, Check: resource.ComposeTestCheckFunc( @@ -606,8 +606,7 @@ func testAccCheckInstanceExistsWithProviders(n string, i *ecs.InstanceAttributes } // Verify the error is what we want - e, _ := err.(*common.Error) - if e.ErrorResponse.Message == InstanceNotfound { + if NotFoundError(err) { continue } if err != nil { @@ -655,8 +654,7 @@ func testAccCheckInstanceDestroyWithProvider(s *terraform.State, provider *schem } // Verify the error is what we want - e, _ := err.(*common.Error) - if e.ErrorResponse.Message == InstanceNotfound { + if NotFoundError(err) { continue } @@ -717,7 +715,6 @@ resource "alicloud_instance" "foo" { internet_charge_type = "PayByBandwidth" security_groups = ["${alicloud_security_group.tf_test_foo.id}"] instance_name = "test_foo" - io_optimized = "optimized" tags { foo = "bar" diff --git a/alicloud/resource_alicloud_ram_role.go b/alicloud/resource_alicloud_ram_role.go new file mode 100644 index 00000000000..3478a0a9fe5 --- /dev/null +++ b/alicloud/resource_alicloud_ram_role.go @@ -0,0 +1,256 @@ +package alicloud + +import ( + "fmt" + "time" + + "github.com/denverdino/aliyungo/ram" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAlicloudRamRole() *schema.Resource { + return &schema.Resource{ + Create: resourceAlicloudRamRoleCreate, + Read: resourceAlicloudRamRoleRead, + Update: resourceAlicloudRamRoleUpdate, + Delete: resourceAlicloudRamRoleDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateRamName, + }, + "ram_users": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + ConflictsWith: []string{"document"}, + }, + "services": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Set: schema.HashString, + ConflictsWith: []string{"document"}, + }, + "document": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"ram_users", "services", "version"}, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateRamDesc, + }, + "version": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "1", + ConflictsWith: []string{"document"}, + ValidateFunc: validatePolicyDocVersion, + }, + "force": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "arn": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAlicloudRamRoleCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).ramconn + + args, err := buildAlicloudRamRoleCreateArgs(d, meta) + if err != nil { + return err + } + + response, err := conn.CreateRole(args) + if err != nil { + return fmt.Errorf("CreateRole got an error: %#v", err) + } + + d.SetId(response.Role.RoleName) + return resourceAlicloudRamRoleUpdate(d, meta) +} + +func resourceAlicloudRamRoleUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).ramconn + + d.Partial(true) + + args, attributeUpdate, err := buildAlicloudRamRoleUpdateArgs(d, meta) + if err != nil { + return err + } + + if !d.IsNewResource() && attributeUpdate { + if _, err := conn.UpdateRole(args); err != nil { + return fmt.Errorf("UpdateRole got an error: %v", err) + } + } + + d.Partial(false) + return resourceAlicloudRamRoleRead(d, meta) +} + +func resourceAlicloudRamRoleRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).ramconn + + args := ram.RoleQueryRequest{ + RoleName: d.Id(), + } + + response, err := conn.GetRole(args) + if err != nil { + if RamEntityNotExist(err) { + d.SetId("") + } + return fmt.Errorf("GetRole got an error: %v", err) + } + + role := response.Role + rolePolicy, err := ParseRolePolicyDocument(role.AssumeRolePolicyDocument) + if err != nil { + return err + } + if len(rolePolicy.Statement) > 0 { + principal := rolePolicy.Statement[0].Principal + d.Set("services", principal.Service) + d.Set("ram_users", principal.RAM) + } + + d.Set("name", role.RoleName) + d.Set("arn", role.Arn) + d.Set("description", role.Description) + d.Set("version", rolePolicy.Version) + d.Set("document", role.AssumeRolePolicyDocument) + return nil +} + +func resourceAlicloudRamRoleDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).ramconn + + args := ram.RoleQueryRequest{ + RoleName: d.Id(), + } + + if d.Get("force").(bool) { + resp, err := conn.ListPoliciesForRole(args) + if err != nil { + return fmt.Errorf("Error listing Policies for Role (%s) when trying to delete: %#v", d.Id(), err) + } + + // Loop and remove the Policies from the Role + if len(resp.Policies.Policy) > 0 { + for _, v := range resp.Policies.Policy { + _, err = conn.DetachPolicyFromRole(ram.AttachPolicyToRoleRequest{ + PolicyRequest: ram.PolicyRequest{ + PolicyName: v.PolicyName, + PolicyType: ram.Type(v.PolicyType), + }, + RoleName: d.Id(), + }) + if err != nil && !RamEntityNotExist(err) { + return fmt.Errorf("Error detach Policy from Role %s: %#v", d.Id(), err) + } + } + } + } + return resource.Retry(5*time.Minute, func() *resource.RetryError { + if _, err := conn.DeleteRole(args); err != nil { + if IsExceptedError(err, DeleteConflictRolePolicy) { + return resource.RetryableError(fmt.Errorf("The role can not has any attached policy while deleting the role. - you can set force with true to force delete the role.")) + } + return resource.NonRetryableError(fmt.Errorf("Error deleting role %s: %#v, you can set force with true to force delete the role.", d.Id(), err)) + } + return nil + }) +} + +func buildAlicloudRamRoleCreateArgs(d *schema.ResourceData, meta interface{}) (ram.RoleRequest, error) { + + args := ram.RoleRequest{ + RoleName: d.Get("name").(string), + } + + ramUsers, usersOk := d.GetOk("ram_users") + services, servicesOk := d.GetOk("services") + document, documentOk := d.GetOk("document") + + if !usersOk && !servicesOk && !documentOk { + return ram.RoleRequest{}, fmt.Errorf("At least one of 'ram_users', 'services' or 'document' must be set.") + } + + if documentOk { + args.AssumeRolePolicyDocument = document.(string) + } else { + rolePolicyDocument, err := AssembleRolePolicyDocument(ramUsers.(*schema.Set).List(), services.(*schema.Set).List(), d.Get("version").(string)) + if err != nil { + return ram.RoleRequest{}, err + } + args.AssumeRolePolicyDocument = rolePolicyDocument + } + + if v, ok := d.GetOk("description"); ok && v.(string) != "" { + args.Description = v.(string) + } + + return args, nil +} + +func buildAlicloudRamRoleUpdateArgs(d *schema.ResourceData, meta interface{}) (ram.UpdateRoleRequest, bool, error) { + args := ram.UpdateRoleRequest{ + RoleName: d.Id(), + } + + attributeUpdate := false + + if d.HasChange("document") { + d.SetPartial("document") + attributeUpdate = true + args.NewAssumeRolePolicyDocument = d.Get("document").(string) + + } else if d.HasChange("ram_users") || d.HasChange("services") || d.HasChange("version") { + attributeUpdate = true + + if d.HasChange("ram_users") { + d.SetPartial("ram_users") + } + if d.HasChange("services") { + d.SetPartial("services") + } + if d.HasChange("version") { + d.SetPartial("version") + } + + document, err := AssembleRolePolicyDocument(d.Get("ram_users").(*schema.Set).List(), d.Get("services").(*schema.Set).List(), d.Get("version").(string)) + if err != nil { + return ram.UpdateRoleRequest{}, attributeUpdate, err + } + args.NewAssumeRolePolicyDocument = document + } + + return args, attributeUpdate, nil +} diff --git a/alicloud/resource_alicloud_ram_role_attachment.go b/alicloud/resource_alicloud_ram_role_attachment.go new file mode 100644 index 00000000000..c449659a42d --- /dev/null +++ b/alicloud/resource_alicloud_ram_role_attachment.go @@ -0,0 +1,131 @@ +package alicloud + +import ( + "fmt" + "reflect" + "sort" + "strings" + "time" + + "github.com/denverdino/aliyungo/ecs" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAlicloudRamRoleAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceAlicloudInstanceRoleAttachmentCreate, + Read: resourceAlicloudInstanceRoleAttachmentRead, + Delete: resourceAlicloudInstanceRoleAttachmentDelete, + + Schema: map[string]*schema.Schema{ + "role_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validateRamName, + ForceNew: true, + }, + "instance_ids": &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAlicloudInstanceRoleAttachmentCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*AliyunClient) + conn := client.ecsconn + + instanceIds := convertListToJsonString(d.Get("instance_ids").(*schema.Set).List()) + + args := ecs.AttachInstancesArgs{ + RegionId: getRegion(d, meta), + InstanceIds: instanceIds, + RamRoleName: d.Get("role_name").(string), + } + + err := client.JudgeRolePolicyPrincipal(args.RamRoleName) + if err != nil { + return err + } + + return resource.Retry(5*time.Minute, func() *resource.RetryError { + if err := conn.AttachInstanceRamRole(&args); err != nil { + if IsExceptedError(err, RoleAttachmentUnExpectedJson) { + return resource.RetryableError(fmt.Errorf("Please trying again.")) + } + return resource.NonRetryableError(fmt.Errorf("AttachInstanceRamRole got an error: %#v", err)) + } + d.SetId(d.Get("role_name").(string) + ":" + instanceIds) + return resource.NonRetryableError(resourceAlicloudInstanceRoleAttachmentRead(d, meta)) + }) +} + +func resourceAlicloudInstanceRoleAttachmentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).ecsconn + roleName := strings.Split(d.Id(), ":")[0] + instanceIds := strings.Split(d.Id(), ":")[1] + + args := ecs.AttachInstancesArgs{ + RegionId: getRegion(d, meta), + InstanceIds: instanceIds, + } + + return resource.Retry(5*time.Minute, func() *resource.RetryError { + resp, err := conn.DescribeInstanceRamRole(&args) + if err != nil { + if IsExceptedError(err, RoleAttachmentUnExpectedJson) { + return resource.RetryableError(fmt.Errorf("Please trying again.")) + } + if IsExceptedError(err, InvalidRamRoleNotFound) { + d.SetId("") + return nil + } + return resource.NonRetryableError(fmt.Errorf("DescribeInstanceRamRole got an error: %#v", err)) + } + + instRoleSets := resp.InstanceRamRoleSets.InstanceRamRoleSet + if len(instRoleSets) > 0 { + var instIds []string + for _, item := range instRoleSets { + if item.RamRoleName == roleName { + instIds = append(instIds, item.InstanceId) + } + } + ids := strings.Split(strings.TrimRight(strings.TrimLeft(strings.Replace(instanceIds, "\"", "", -1), "["), "]"), ",") + sort.Strings(instIds) + sort.Strings(ids) + if reflect.DeepEqual(instIds, ids) { + d.Set("role_name", resp.InstanceRamRoleSets.InstanceRamRoleSet[0].RamRoleName) + d.Set("instance_ids", instIds) + return nil + } + } + return resource.NonRetryableError(fmt.Errorf("No ram role for instances found.")) + }) +} + +func resourceAlicloudInstanceRoleAttachmentDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).ecsconn + roleName := strings.Split(d.Id(), ":")[0] + instanceIds := strings.Split(d.Id(), ":")[1] + + return resource.Retry(5*time.Minute, func() *resource.RetryError { + err := conn.DetachInstanceRamRole(&ecs.AttachInstancesArgs{ + RegionId: getRegion(d, meta), + RamRoleName: roleName, + InstanceIds: instanceIds, + }) + + if err != nil { + if IsExceptedError(err, RoleAttachmentUnExpectedJson) { + return resource.RetryableError(fmt.Errorf("Please trying again.")) + } + return resource.NonRetryableError(fmt.Errorf("Error DetachInstanceRamRole:%#v", err)) + } + return nil + }) +} diff --git a/alicloud/resource_alicloud_ram_role_attachment_test.go b/alicloud/resource_alicloud_ram_role_attachment_test.go new file mode 100644 index 00000000000..e5d68340a9b --- /dev/null +++ b/alicloud/resource_alicloud_ram_role_attachment_test.go @@ -0,0 +1,177 @@ +package alicloud + +import ( + "fmt" + "strings" + "testing" + + "github.com/denverdino/aliyungo/ecs" + "github.com/denverdino/aliyungo/ram" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAlicloudRamRoleAttachment_basic(t *testing.T) { + var instanceA ecs.InstanceAttributesType + var instanceB ecs.InstanceAttributesType + var role ram.Role + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + + // module name + IDRefreshName: "alicloud_ram_role_attachment.attach", + + Providers: testAccProviders, + CheckDestroy: testAccCheckRamRoleAttachmentDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRamRoleAttachmentConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRamRoleExists( + "alicloud_ram_role.role", &role), + testAccCheckInstanceExists( + "alicloud_instance.instance.0", &instanceA), + testAccCheckInstanceExists( + "alicloud_instance.instance.1", &instanceB), + testAccCheckRamRoleAttachmentExists( + "alicloud_ram_role_attachment.attach", &instanceB, &instanceA, &role), + ), + }, + }, + }) + +} + +func testAccCheckRamRoleAttachmentExists(n string, instanceA *ecs.InstanceAttributesType, instanceB *ecs.InstanceAttributesType, role *ram.Role) 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 Attachment ID is set") + } + + client := testAccProvider.Meta().(*AliyunClient) + conn := client.ecsconn + + request := &ecs.AttachInstancesArgs{ + RegionId: client.Region, + InstanceIds: convertListToJsonString([]interface{}{instanceA.InstanceId, instanceB.InstanceId}), + } + + for { + response, err := conn.DescribeInstanceRamRole(request) + if IsExceptedError(err, RoleAttachmentUnExpectedJson) { + continue + } + if err == nil { + if len(response.InstanceRamRoleSets.InstanceRamRoleSet) > 0 { + for _, v := range response.InstanceRamRoleSets.InstanceRamRoleSet { + if v.RamRoleName == role.RoleName { + return nil + } + } + } + return fmt.Errorf("Error finding attach %s", rs.Primary.ID) + } + return fmt.Errorf("Error finding attach %s: %#v", rs.Primary.ID, err) + } + } +} + +func testAccCheckRamRoleAttachmentDestroy(s *terraform.State) error { + + for _, rs := range s.RootModule().Resources { + if rs.Type != "alicloud_ram_role_attachment" { + continue + } + + // Try to find the attachment + client := testAccProvider.Meta().(*AliyunClient) + conn := client.ecsconn + + request := &ecs.AttachInstancesArgs{ + RegionId: client.Region, + InstanceIds: strings.Split(rs.Primary.ID, ":")[1], + } + + for { + response, err := conn.DescribeInstanceRamRole(request) + if IsExceptedError(err, RoleAttachmentUnExpectedJson) { + continue + } + if IsExceptedError(err, InvalidInstanceIdNotFound) { + return nil + } + if err == nil { + if len(response.InstanceRamRoleSets.InstanceRamRoleSet) > 0 { + for _, v := range response.InstanceRamRoleSets.InstanceRamRoleSet { + if v.RamRoleName != "" { + return fmt.Errorf("Attach %s still exists.", rs.Primary.ID) + } + } + } + return nil + } + return fmt.Errorf("Error detach %s: %#v", rs.Primary.ID, err) + } + } + return nil +} + +const testAccRamRoleAttachmentConfig = ` +data "alicloud_zones" "default" { + "available_disk_category"= "cloud_efficiency" + "available_resource_creation"= "VSwitch" +} + +resource "alicloud_vpc" "foo" { + name = "tf_test_foo" + cidr_block = "172.16.0.0/12" +} + +resource "alicloud_vswitch" "foo" { + vpc_id = "${alicloud_vpc.foo.id}" + cidr_block = "172.16.0.0/21" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" +} + +resource "alicloud_security_group" "tf_test_foo" { + name = "tf_test_foo" + description = "foo" + vpc_id = "${alicloud_vpc.foo.id}" +} + +resource "alicloud_instance" "instance" { + vswitch_id = "${alicloud_vswitch.foo.id}" + image_id = "ubuntu_140405_32_40G_cloudinit_20161115.vhd" + availability_zone = "${data.alicloud_zones.default.zones.0.id}" + + # series III + instance_type = "ecs.n4.large" + system_disk_category = "cloud_efficiency" + count = 2 + + internet_charge_type = "PayByTraffic" + internet_max_bandwidth_out = 5 + allocate_public_ip = true + security_groups = ["${alicloud_security_group.tf_test_foo.id}"] + instance_name = "test_foo" +} + +resource "alicloud_ram_role" "role" { + name = "rolename" + services = ["ecs.aliyuncs.com"] + description = "this is a test" + force = true +} + +resource "alicloud_ram_role_attachment" "attach" { + role_name = "${alicloud_ram_role.role.name}" + instance_ids = ["${alicloud_instance.instance.*.id}"] +}` diff --git a/alicloud/resource_alicloud_ram_role_policy_attachment.go b/alicloud/resource_alicloud_ram_role_policy_attachment.go new file mode 100644 index 00000000000..607c745c771 --- /dev/null +++ b/alicloud/resource_alicloud_ram_role_policy_attachment.go @@ -0,0 +1,118 @@ +package alicloud + +import ( + "fmt" + "github.com/denverdino/aliyungo/ram" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "time" +) + +func resourceAlicloudRamRolePolicyAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceAlicloudRamRolePolicyAttachmentCreate, + Read: resourceAlicloudRamRolePolicyAttachmentRead, + //Update: resourceAlicloudRamRolePolicyAttachmentUpdate, + Delete: resourceAlicloudRamRolePolicyAttachmentDelete, + + Schema: map[string]*schema.Schema{ + "role_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateRamName, + }, + "policy_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateRamPolicyName, + }, + "policy_type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validatePolicyType, + }, + }, + } +} + +func resourceAlicloudRamRolePolicyAttachmentCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).ramconn + args := ram.AttachPolicyToRoleRequest{ + PolicyRequest: ram.PolicyRequest{ + PolicyName: d.Get("policy_name").(string), + PolicyType: ram.Type(d.Get("policy_type").(string)), + }, + RoleName: d.Get("role_name").(string), + } + + if _, err := conn.AttachPolicyToRole(args); err != nil { + return fmt.Errorf("AttachPolicyToRole got an error: %#v", err) + } + d.SetId("role" + args.PolicyName + string(args.PolicyType) + args.RoleName) + + return resourceAlicloudRamRolePolicyAttachmentRead(d, meta) +} + +func resourceAlicloudRamRolePolicyAttachmentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).ramconn + + args := ram.RoleQueryRequest{ + RoleName: d.Get("role_name").(string), + } + + response, err := conn.ListPoliciesForRole(args) + if err != nil { + return fmt.Errorf("Get list policies for role got an error: %v", err) + } + + if len(response.Policies.Policy) > 0 { + for _, v := range response.Policies.Policy { + if v.PolicyName == d.Get("policy_name").(string) && v.PolicyType == d.Get("policy_type").(string) { + d.Set("role_name", args.RoleName) + d.Set("policy_name", v.PolicyName) + d.Set("policy_type", v.PolicyType) + return nil + } + } + } + + d.SetId("") + return nil +} + +func resourceAlicloudRamRolePolicyAttachmentDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AliyunClient).ramconn + + args := ram.AttachPolicyToRoleRequest{ + PolicyRequest: ram.PolicyRequest{ + PolicyName: d.Get("policy_name").(string), + PolicyType: ram.Type(d.Get("policy_type").(string)), + }, + RoleName: d.Get("role_name").(string), + } + + return resource.Retry(5*time.Minute, func() *resource.RetryError { + if _, err := conn.DetachPolicyFromRole(args); err != nil { + if RamEntityNotExist(err) { + return nil + } + return resource.NonRetryableError(fmt.Errorf("Error deleting role policy attachment: %#v", err)) + } + + response, err := conn.ListPoliciesForRole(ram.RoleQueryRequest{RoleName: args.RoleName}) + if err != nil { + if RamEntityNotExist(err) { + return nil + } + return resource.NonRetryableError(err) + } + + if len(response.Policies.Policy) < 1 { + return nil + } + return resource.RetryableError(fmt.Errorf("Error deleting role policy attachment - trying again while it is deleted.")) + }) +} diff --git a/alicloud/resource_alicloud_ram_role_policy_attachment_test.go b/alicloud/resource_alicloud_ram_role_policy_attachment_test.go new file mode 100644 index 00000000000..8bcd4bcfabf --- /dev/null +++ b/alicloud/resource_alicloud_ram_role_policy_attachment_test.go @@ -0,0 +1,140 @@ +package alicloud + +import ( + "fmt" + "testing" + + "github.com/denverdino/aliyungo/ram" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAlicloudRamRolePolicyAttachment_basic(t *testing.T) { + var p ram.Policy + var r ram.Role + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + + // module name + IDRefreshName: "alicloud_ram_role_policy_attachment.attach", + + Providers: testAccProviders, + CheckDestroy: testAccCheckRamRolePolicyAttachmentDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRamRolePolicyAttachmentConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRamPolicyExists( + "alicloud_ram_policy.policy", &p), + testAccCheckRamRoleExists( + "alicloud_ram_role.role", &r), + testAccCheckRamRolePolicyAttachmentExists( + "alicloud_ram_role_policy_attachment.attach", &p, &r), + ), + }, + }, + }) + +} + +func testAccCheckRamRolePolicyAttachmentExists(n string, policy *ram.Policy, role *ram.Role) 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 Attachment ID is set") + } + + client := testAccProvider.Meta().(*AliyunClient) + conn := client.ramconn + + request := ram.RoleQueryRequest{ + RoleName: role.RoleName, + } + + response, err := conn.ListPoliciesForRole(request) + if err == nil { + if len(response.Policies.Policy) > 0 { + for _, v := range response.Policies.Policy { + if v.PolicyName == policy.PolicyName && v.PolicyType == policy.PolicyType { + return nil + } + } + } + return fmt.Errorf("Error finding attach %s", rs.Primary.ID) + } + return fmt.Errorf("Error finding attach %s: %#v", rs.Primary.ID, err) + } +} + +func testAccCheckRamRolePolicyAttachmentDestroy(s *terraform.State) error { + + for _, rs := range s.RootModule().Resources { + if rs.Type != "alicloud_ram_role_policy_attachment" { + continue + } + + // Try to find the attachment + client := testAccProvider.Meta().(*AliyunClient) + conn := client.ramconn + + request := ram.RoleQueryRequest{ + RoleName: rs.Primary.Attributes["role_name"], + } + + response, err := conn.ListPoliciesForRole(request) + + if err != nil { + if RamEntityNotExist(err) { + return nil + } + return err + } + + if len(response.Policies.Policy) > 0 { + for _, v := range response.Policies.Policy { + if v.PolicyName == rs.Primary.Attributes["policy_name"] && v.PolicyType == rs.Primary.Attributes["policy_type"] { + return fmt.Errorf("Error attachment still exist.") + } + } + } + } + return nil +} + +const testAccRamRolePolicyAttachmentConfig = ` +resource "alicloud_ram_policy" "policy" { + name = "policyname" + statement = [ + { + effect = "Deny" + action = [ + "oss:ListObjects", + "oss:ListObjects"] + resource = [ + "acs:oss:*:*:mybucket", + "acs:oss:*:*:mybucket/*"] + }] + description = "this is a policy test" + force = true +} + +resource "alicloud_ram_role" "role" { + name = "rolename" + services = ["apigateway.aliyuncs.com", "ecs.aliyuncs.com"] + ram_users = ["acs:ram::123456789:root", "acs:ram::1234567890:user/username"] + description = "this is a test" + force = true +} + +resource "alicloud_ram_role_policy_attachment" "attach" { + policy_name = "${alicloud_ram_policy.policy.name}" + role_name = "${alicloud_ram_role.role.name}" + policy_type = "${alicloud_ram_policy.policy.type}" +}` diff --git a/alicloud/resource_alicloud_ram_role_test.go b/alicloud/resource_alicloud_ram_role_test.go new file mode 100644 index 00000000000..203c42f625e --- /dev/null +++ b/alicloud/resource_alicloud_ram_role_test.go @@ -0,0 +1,110 @@ +package alicloud + +import ( + "fmt" + "testing" + + "github.com/denverdino/aliyungo/ram" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "log" +) + +func TestAccAlicloudRamRole_basic(t *testing.T) { + var v ram.Role + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + + // module name + IDRefreshName: "alicloud_ram_role.role", + + Providers: testAccProviders, + CheckDestroy: testAccCheckRamRoleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRamRoleConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRamRoleExists( + "alicloud_ram_role.role", &v), + resource.TestCheckResourceAttr( + "alicloud_ram_role.role", + "name", + "rolename"), + resource.TestCheckResourceAttr( + "alicloud_ram_role.role", + "description", + "this is a test"), + ), + }, + }, + }) + +} + +func testAccCheckRamRoleExists(n string, role *ram.Role) 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 ID is set") + } + + client := testAccProvider.Meta().(*AliyunClient) + conn := client.ramconn + + request := ram.RoleQueryRequest{ + RoleName: rs.Primary.Attributes["name"], + } + + response, err := conn.GetRole(request) + log.Printf("[WARN] Role id %#v", rs.Primary.ID) + + if err == nil { + *role = response.Role + return nil + } + return fmt.Errorf("Error finding role %#v", rs.Primary.ID) + } +} + +func testAccCheckRamRoleDestroy(s *terraform.State) error { + + for _, rs := range s.RootModule().Resources { + if rs.Type != "alicloud_ram_role" { + continue + } + + // Try to find the role + client := testAccProvider.Meta().(*AliyunClient) + conn := client.ramconn + + request := ram.RoleQueryRequest{ + RoleName: rs.Primary.Attributes["name"], + } + + _, err := conn.GetRole(request) + + if err != nil { + if RamEntityNotExist(err) { + return nil + } + return err + } + } + return nil +} + +const testAccRamRoleConfig = ` +resource "alicloud_ram_role" "role" { + name = "rolename" + services = ["apigateway.aliyuncs.com", "ecs.aliyuncs.com"] + ram_users = ["acs:ram::123456789:root", "acs:ram::1234567890:user/username"] + description = "this is a test" + force = true +}` diff --git a/website/alicloud.erb b/website/alicloud.erb index 3624f274809..5d3c7dbb189 100644 --- a/website/alicloud.erb +++ b/website/alicloud.erb @@ -34,6 +34,9 @@ > alicloud_ram_policies + > + alicloud_ram_roles + > alicloud_ram_users @@ -168,12 +171,21 @@ > alicloud_ram_policy + > + alicloud_ram_role + + > + alicloud_ram_role_policy_attachment + > alicloud_ram_user > alicloud_ram_user_policy_attachment + > + alicloud_ram_role_attachment + diff --git a/website/docs/d/ram_roles.html.markdown b/website/docs/d/ram_roles.html.markdown new file mode 100644 index 00000000000..714bba7bbff --- /dev/null +++ b/website/docs/d/ram_roles.html.markdown @@ -0,0 +1,45 @@ +--- +layout: "alicloud" +page_title: "Alicloud: alicloud_ram_roles" +sidebar_current: "docs-alicloud-datasource-ram-roles" +description: |- + Provides a list of ram roles available to the user. +--- + +# alicloud\_ram\_roles + +The Ram Roles data source provides a list of Alicloud Ram Roles in an Alicloud account according to the specified filters. + +## Example Usage + +``` +data "alicloud_ram_roles" "role" { + output_file = "roles.txt" + name_regex = ".*test.*" + policy_name = "AliyunACSDefaultAccess" + policy_type = "Custom" +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `name_regex` - (Optional) A regex string to apply to the role list returned by Alicloud. +* `policy_type` - (Optional) Limit search to specific the policy type. Valid items are `Custom` and `System`. If you set this parameter, you must set `policy_name` at one time. +* `policy_name` - (Optional) Limit search to specific the policy name. If you set this parameter without set `policy_type`, we will specified it as `System`. Found the roles which attached with the specified policy. +* `output_file` - (Optional) The name of file that can save roles data source after running `terraform plan`. + +## Attributes Reference + +A list of roles will be exported and its every element contains the following attributes: + +* `id` - Id of the role. +* `name` - Name of the role. +* `arn` - Resource descriptor of the role. +* `description` - Description of the role. +* `assume_role_policy_document` - Authorization strategy of the role. This parameter is deprecated and replaced by `document`. +* `document` - Authorization strategy of the role. +* `create_date` - Create date of the role. +* `update_date` - Update date of the role. \ No newline at end of file diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index fb689a5ee6d..caaef1c8de6 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -71,7 +71,7 @@ The following arguments are supported: * `instance_name` - (Optional) The name of the ECS. This instance_name can have a string of 2 to 128 characters, must contain only alphanumeric characters or hyphens, such as "-",".","_", and must not begin or end with a hyphen, and must not begin with http:// or https://. If not specified, Terraform will autogenerate a default name is `ECS-Instance`. * `allocate_public_ip` - (Optional) Associate a public ip address with an instance in a VPC or Classic. Boolean value, Default is false. -* `system_disk_category` - (Optional) Valid values are `cloud_efficiency`, `cloud_ssd` and `cloud`. `cloud` only is used to some no I/O optimized instance. Default to `cloud_efficiency`. +* `system_disk_category` - (Optional) Valid values are `cloud_efficiency`, `cloud_ssd` and `cloud`. `cloud` only is used to some none I/O optimized instance. Default to `cloud_efficiency`. * `system_disk_size` - (Optional) Size of the system disk, value range: 40GB ~ 500GB. Default is 40GB. ECS instance's system disk can be reset when replacing system disk. * `description` - (Optional) Description of the instance, This description can have a string of 2 to 256 characters, It cannot begin with http:// or https://. Default value is null. * `internet_charge_type` - (Optional) Internet charge type of the instance, Valid values are `PayByBandwidth`, `PayByTraffic`. Default is `PayByTraffic`. @@ -84,7 +84,11 @@ On other OSs such as Linux, the host name can contain a maximum of 30 characters * `instance_charge_type` - (Optional) Valid values are `PrePaid`, `PostPaid`, The default is `PostPaid`. * `period` - (Optional) The time that you have bought the resource, in month. Only valid when instance_charge_type is set as `PrePaid`. Value range [1, 12]. * `tags` - (Optional) A mapping of tags to assign to the resource. +* `user_data` - (Optional) User-defined data to customize the startup behaviors of an ECS instance and to pass data into an ECS instance. * `key_name` - (Optional, Force new resource) The name of key pair that can login ECS instance successfully without password. If it is specified, the password would be invalid. +* `role_name` - (Optional, Force new resource) Instance RAM role name. The name is provided and maintained by RAM. You can use `alicloud_ram_role` to create a new one. + +~> **NOTE:** System disk category `cloud` has been outdated and it only can be used none I/O Optimized ECS instances. Recommend `cloud_efficiency` and `cloud_ssd` disk. ## Attributes Reference @@ -102,4 +106,6 @@ The following attributes are exported: * `public_ip` - The instance public ip. * `vswitch_id` - If the instance created in VPC, then this value is virtual switch ID. * `tags` - The instance tags, use jsonencode(item) to display the value. -* `key_name` - The name of key pair that has been bound in ECS instance. \ No newline at end of file +* `key_name` - The name of key pair that has been bound in ECS instance. +* `role_name` - The name of RAM role that has been bound in ECS instance. +* `user_data` - The hash value of the user data. \ No newline at end of file diff --git a/website/docs/r/ram_role.html.markdown b/website/docs/r/ram_role.html.markdown new file mode 100644 index 00000000000..dc8972b01c9 --- /dev/null +++ b/website/docs/r/ram_role.html.markdown @@ -0,0 +1,50 @@ +--- +layout: "alicloud" +page_title: "Alicloud: alicloud_ram_role" +sidebar_current: "docs-alicloud-resource-ram-role" +description: |- + Provides a RAM Role resource. +--- + +# alicloud\_ram\_role + +Provides a RAM Role resource. + +~> **NOTE:** When you want to destroy this resource forcefully(means remove all the relationships associated with it automatically and then destroy it) without set `force` with `true` at beginning, you need add `force = true` to configuration file and run `terraform plan`, then you can delete resource forcefully. + +## Example Usage + +``` +# Create a new RAM Role. +resource "alicloud_ram_role" "role" { + name = "test_role" + ram_users = ["acs:ram::${your_account_id}:root", "acs:ram::${other_account_id}:user/username"] + services = ["apigateway.aliyuncs.com", "ecs.aliyuncs.com"] + description = "this is a role test." + force = true +} +``` +## Argument Reference + +The following arguments are supported: + +* `name` - (Required, Forces new resource) Name of the RAM role. This name can have a string of 1 to 64 characters, must contain only alphanumeric characters or hyphens, such as "-", "_", and must not begin with a hyphen. +* `services` - (Optional, Type: list, Conflicts with `document`) List of services which can assume the RAM role. The format of each item in this list is `${service}.aliyuncs.com` or `${account_id}@${service}.aliyuncs.com`, such as `ecs.aliyuncs.com` and `1234567890000@ots.aliyuncs.com`. The `${service}` can be `ecs`, `log`, `apigateway` and so on, the `${account_id}` refers to someone's Alicloud account id. +* `ram_users` - (Optional, Type: list, Conflicts with `document`) List of ram users who can assume the RAM role. The format of each item in this list is `acs:ram::${account_id}:root` or `acs:ram::${account_id}:user/${user_name}`, such as `acs:ram::1234567890000:root` and `acs:ram::1234567890001:user/Mary`. The `${user_name}` is the name of a RAM user which must exists in the Alicloud account indicated by the `${account_id}`. +* `version` - (Optional, Conflicts with `document`) Version of the RAM role policy document. Valid value is `1`. Default value is `1`. +* `document` - (Optional, Conflicts with `services`, `ram_users` and `version`) Authorization strategy of the RAM role. It is required when the `services` and `ram_users` are not specified. +* `description` - (Optional, Forces new resource) Description of the RAM role. This name can have a string of 1 to 1024 characters. +* `force` - (Optional) This parameter is used for resource destroy. Default value is `false`. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The role ID. +* `name` - The role name. +* `arn` - The role arn. +* `description` - The role description. +* `version` - The role policy document version. +* `document` - Authorization strategy of the role. +* `ram_users` - List of services which can assume the RAM role. +* `services` - List of services which can assume the RAM role. \ No newline at end of file diff --git a/website/docs/r/ram_role_attachment.html.markdown b/website/docs/r/ram_role_attachment.html.markdown new file mode 100644 index 00000000000..922c74bc398 --- /dev/null +++ b/website/docs/r/ram_role_attachment.html.markdown @@ -0,0 +1,51 @@ +--- +layout: "alicloud" +page_title: "Alicloud: alicloud_ram_role_attachment" +sidebar_current: "docs-alicloud-resource-ram-role-attachment" +description: |- + Provides a RAM role attachment resource to bind role for several ECS instances. +--- + +# alicloud\_ram\_role\_attachment + +Provides a RAM role attachment resource to bind role for several ECS instances. + +## Example Usage + +``` +resource "alicloud_ram_role" "role" { + name = "test_role" + services = ["apigateway.aliyuncs.com", "ecs.aliyuncs.com"] + ram_users = ["acs:ram::${your_account_id}:root", "acs:ram::${other_account_id}:user/username"] + description = "this is a role test." + force = true +} + +resource "alicloud_instance" "instance" { + instance_name = "test-keypair-${format(var.count_format, count.index+1)}" + image_id = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" + instance_type = "ecs.n4.small" + count = 2 + availability_zone = "${var.availability_zones}" + ... +} + +resource "alicloud_ram_role_attachment" "attach" { + role_name = "${alicloud_ram_role.role.name}" + instance_ids = ["${alicloud_instance.instance.*.id}"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `role_name` - (Required, Forces new resource) The name of role used to bind. This name can have a string of 1 to 64 characters, must contain only alphanumeric characters or hyphens, such as "-", "_", and must not begin with a hyphen. +* `instance_ids` - (Required, Forces new resource) The list of ECS instance's IDs. + +## Attributes Reference + +The following attributes are exported: + +* `role_name` - The name of the role. +* `instance_ids` The list of ECS instance's IDs. \ No newline at end of file diff --git a/website/docs/r/ram_role_policy_attachment.html.markdown b/website/docs/r/ram_role_policy_attachment.html.markdown new file mode 100644 index 00000000000..fcc4fb361be --- /dev/null +++ b/website/docs/r/ram_role_policy_attachment.html.markdown @@ -0,0 +1,62 @@ +--- +layout: "alicloud" +page_title: "Alicloud: alicloud_ram_role_policy_attachment" +sidebar_current: "docs-alicloud-resource-ram-role-policy-attachment" +description: |- + Provides a RAM Role Policy attachment resource. +--- + +# alicloud\_ram\_role\_policy\_attachment + +Provides a RAM Role attachment resource. + +## Example Usage + +``` +# Create a RAM Role Policy attachment. +resource "alicloud_ram_role" "role" { + name = "test_role" + ram_users = ["acs:ram::${your_account_id}:root", "acs:ram::${other_account_id}:user/username"] + services = ["apigateway.aliyuncs.com", "ecs.aliyuncs.com"] + description = "this is a role test." + force = true +} + +resource "alicloud_ram_policy" "policy" { + name = "test_policy" + statement = [ + { + effect = "Allow" + action = [ + "oss:ListObjects", + "oss:GetObject"] + resource = [ + "acs:oss:*:*:mybucket", + "acs:oss:*:*:mybucket/*"] + }] + description = "this is a policy test" + force = true +} + +resource "alicloud_ram_role_policy_attachment" "attach" { + policy_name = "${alicloud_ram_policy.policy.name}" + policy_type = "${alicloud_ram_policy.policy.type}" + role_name = "${alicloud_ram_role.role.name}" +} +``` +## Argument Reference + +The following arguments are supported: + +* `role_name` - (Required, Forces new resource) Name of the RAM Role. This name can have a string of 1 to 64 characters, must contain only alphanumeric characters or hyphens, such as "-", "_", and must not begin with a hyphen. +* `policy_name` - (Required, Forces new resource) Name of the RAM policy. This name can have a string of 1 to 128 characters, must contain only alphanumeric characters or hyphen "-", and must not begin with a hyphen. +* `policy_type` - (Required, Forces new resource) Type of the RAM policy. It must be `Custom` or `System`. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The attachment ID. +* `role_name` - The role name. +* `policy_name` - The policy name. +* `policy_type` - The policy type. \ No newline at end of file