diff --git a/go.mod b/go.mod index 2954c0db..d8dfa94f 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/selectel/dbaas-go v0.12.1 github.com/selectel/domains-go v1.0.2 github.com/selectel/go-selvpcclient/v3 v3.1.1 - github.com/selectel/iam-go v0.2.0 + github.com/selectel/iam-go v0.3.0 github.com/selectel/mks-go v0.15.0 github.com/selectel/secretsmanager-go v0.2.1 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index a3b6f1b2..6df326b6 100644 --- a/go.sum +++ b/go.sum @@ -170,8 +170,8 @@ github.com/selectel/domains-go v1.0.2 h1:Si6iGaMnTFJxwiJVI50DOdZnwcxc87kqaWrVQYW github.com/selectel/domains-go v1.0.2/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA= github.com/selectel/go-selvpcclient/v3 v3.1.1 h1:C1q2LqqosiapoLpnGITGmysg0YCSQYDo2Gh69CioevM= github.com/selectel/go-selvpcclient/v3 v3.1.1/go.mod h1:NM7IXhh1IzqZ88DOw1Qc5Ez3tULLViXo95l5+rKPuyQ= -github.com/selectel/iam-go v0.2.0 h1:c6ldpbsa/8R3b29ML5B21FU9oyJ2A2AwBNzCbE+pGN8= -github.com/selectel/iam-go v0.2.0/go.mod h1:OIAkW7MZK97YUm+uvUgYbgDhkI9SdzTCxwd4yZoOR1o= +github.com/selectel/iam-go v0.3.0 h1:HRoxSBXwvASE9v/A4WgeEDeMvomARIWyj2essV4LmYc= +github.com/selectel/iam-go v0.3.0/go.mod h1:OIAkW7MZK97YUm+uvUgYbgDhkI9SdzTCxwd4yZoOR1o= github.com/selectel/mks-go v0.15.0 h1:0ytV5DiQAgbojKA0ukBjtwfWBSQh658nF3mhjZTrWj8= github.com/selectel/mks-go v0.15.0/go.mod h1:VxtV3dzwgOEzZc+9VMQb9DvxfSlej2ZQ8jnT8kqIGgU= github.com/selectel/secretsmanager-go v0.2.1 h1:OSBrA/07lm/Ecpwg59IJHFAoUHZR29oyfwUgTpr/dos= diff --git a/selectel/provider.go b/selectel/provider.go index a8d2c8ba..e141d7dc 100644 --- a/selectel/provider.go +++ b/selectel/provider.go @@ -27,6 +27,8 @@ const ( objectUser = "user" objectServiceUser = "service user" objectS3Credentials = "s3 credentials" + objectGroup = "group" + objectGroupMembership = "group-membership" objectCluster = "cluster" objectKubeConfig = "kubeconfig" objectKubeVersions = "kube-versions" @@ -135,6 +137,8 @@ func Provider() *schema.Provider { "selectel_iam_serviceuser_v1": resourceIAMServiceUserV1(), "selectel_iam_user_v1": resourceIAMUserV1(), "selectel_iam_s3_credentials_v1": resourceIAMS3CredentialsV1(), + "selectel_iam_group_v1": resourceIAMGroupV1(), + "selectel_iam_group_membership_v1": resourceIAMGroupMembershipV1(), "selectel_vpc_vrrp_subnet_v2": resourceVPCVRRPSubnetV2(), // DEPRECATED "selectel_vpc_crossregion_subnet_v2": resourceVPCCrossRegionSubnetV2(), // DEPRECATED "selectel_mks_cluster_v1": resourceMKSClusterV1(), diff --git a/selectel/resource_selectel_iam_group_membership_v1.go b/selectel/resource_selectel_iam_group_membership_v1.go new file mode 100644 index 00000000..59cab131 --- /dev/null +++ b/selectel/resource_selectel_iam_group_membership_v1.go @@ -0,0 +1,178 @@ +package selectel + +import ( + "context" + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceIAMGroupMembershipV1() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceIAMGroupMembershipV1Create, + ReadContext: resourceIAMGroupMembershipV1Read, + UpdateContext: resourceIAMGroupMembershipV1Update, + DeleteContext: resourceIAMGroupMembershipV1Delete, + Schema: map[string]*schema.Schema{ + "group_id": { + Type: schema.TypeString, + Required: true, + }, + "user_ids": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func resourceIAMGroupMembershipV1Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + iamClient, diagErr := getIAMClient(meta) + if diagErr != nil { + return diagErr + } + + userIDsInterface := d.Get("user_ids").([]interface{}) + userIDs := make([]string, len(userIDsInterface)) + for i, v := range userIDsInterface { + userIDs[i] = v.(string) + } + log.Print(msgCreate(objectGroupMembership, userIDs)) + + if len(userIDs) == 0 { + createErr := fmt.Errorf("error creating group membership: no user ids specified") + return diag.FromErr(errCreatingObject(objectGroupMembership, createErr)) + } + err := iamClient.Groups.AddUsers(ctx, d.Get("group_id").(string), userIDs) + if err != nil { + return diag.FromErr(errCreatingObject(objectGroupMembership, err)) + } + + d.SetId(d.Get("group_id").(string)) + + return resourceIAMGroupMembershipV1Read(ctx, d, meta) +} + +func resourceIAMGroupMembershipV1Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + iamClient, diagErr := getIAMClient(meta) + if diagErr != nil { + return diagErr + } + + groupID := d.Id() + + userIDsInterface := d.Get("user_ids").([]interface{}) + userIDs := make([]string, len(userIDsInterface)) + for i, v := range userIDsInterface { + userIDs[i] = v.(string) + } + + response, err := iamClient.Groups.Get(ctx, groupID) + if err != nil { + return diag.FromErr(errGettingObject(objectGroupMembership, d.Id(), err)) + } + + responseUserIDs := make([]string, 0) + for _, user := range response.Users { + responseUserIDs = append(responseUserIDs, user.KeystoneID) + } + + responseServiceUserIDs := make([]string, 0) + for _, serviceUser := range response.ServiceUsers { + responseServiceUserIDs = append(responseServiceUserIDs, serviceUser.ID) + } + + d.Set("group_id", groupID) + d.Set("user_ids", append(responseUserIDs, responseServiceUserIDs...)) + + return nil +} + +func resourceIAMGroupMembershipV1Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + iamClient, diagErr := getIAMClient(meta) + if diagErr != nil { + return diagErr + } + + groupID := d.Id() + + oldValue, newValue := d.GetChange("user_ids") + + oldUserIDs := make(map[string]struct{}) + for _, v := range oldValue.([]interface{}) { + oldUserIDs[v.(string)] = struct{}{} + } + + newUserIDs := make(map[string]struct{}) + for _, v := range newValue.([]interface{}) { + newUserIDs[v.(string)] = struct{}{} + } + + usersToAdd, usersToRemove := diffUsers(oldUserIDs, newUserIDs) + + if len(usersToAdd) > 0 { + err := iamClient.Groups.AddUsers(ctx, groupID, usersToAdd) + if err != nil { + return diag.FromErr(err) + } + } + + if len(usersToRemove) > 0 { + err := iamClient.Groups.DeleteUsers(ctx, groupID, usersToRemove) + if err != nil { + return diag.FromErr(err) + } + } + + d.SetId(groupID) + + return resourceIAMGroupMembershipV1Read(ctx, d, meta) +} + +func resourceIAMGroupMembershipV1Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + iamClient, diagErr := getIAMClient(meta) + if diagErr != nil { + return diagErr + } + + groupID := d.Id() + + userIDsInterface := d.Get("user_ids").([]interface{}) + userIDs := make([]string, len(userIDsInterface)) + for i, v := range userIDsInterface { + userIDs[i] = v.(string) + } + + err := iamClient.Groups.DeleteUsers(ctx, groupID, userIDs) + if err != nil { + return diag.FromErr(errDeletingObject(objectGroupMembership, d.Id(), err)) + } + + d.SetId("") + + return nil +} + +func diffUsers(oldUsers, newUsers map[string]struct{}) ([]string, []string) { + usersToAdd := make([]string, 0) + usersToRemove := make([]string, 0) + + for id := range newUsers { + if _, ok := oldUsers[id]; !ok { + usersToAdd = append(usersToAdd, id) + } + } + + for id := range oldUsers { + if _, ok := newUsers[id]; !ok { + usersToRemove = append(usersToRemove, id) + } + } + + return usersToAdd, usersToRemove +} diff --git a/selectel/resource_selectel_iam_group_membership_v1_test.go b/selectel/resource_selectel_iam_group_membership_v1_test.go new file mode 100644 index 00000000..cc2b04a6 --- /dev/null +++ b/selectel/resource_selectel_iam_group_membership_v1_test.go @@ -0,0 +1,136 @@ +package selectel + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccIAMV1GroupMembershipBasic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccSelectelPreCheck(t) }, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccIAMV1GroupMembershipBasic(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "id"), + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "group_id"), + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "user_ids.0"), + ), + }, + }, + }) +} + +func TestAccIAMV1GroupUpdate(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccSelectelPreCheck(t) }, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccIAMV1GroupMembershipBasic(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "id"), + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "group_id"), + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "user_ids.0"), + ), + }, + { + Config: testAccIAMV1GroupMembershipUpdate(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "id"), + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "group_id"), + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "user_ids.0"), + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "user_ids.1"), + ), + }, + { + Config: testAccIAMV1GroupMembershipBasic(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "id"), + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "group_id"), + resource.TestCheckResourceAttrSet("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "user_ids.0"), + resource.TestCheckNoResourceAttr("selectel_iam_group_membership_v1.membership_tf_acc_test_1", "user_ids.1"), + ), + }, + }, + }) +} + +func testAccIAMV1GroupMembershipBasic() string { + return ` +resource "selectel_iam_serviceuser_v1" "serviceuser_tf_acc_test_1" { + name = "test-service-user-1" + password = "Qazwsxedc123" + role { + role_name = "reader" + scope = "account" + } +} + +resource "selectel_iam_serviceuser_v1" "serviceuser_tf_acc_test_2" { + name = "test-service-user-2" + password = "Qazwsxedc123" + role { + role_name = "reader" + scope = "account" + } +} + +resource "selectel_iam_group_v1" "group_tf_acc_test_1" { + name = "test-group" + role { + role_name = "reader" + scope = "account" + } +} + +resource "selectel_iam_group_membership_v1" "membership_tf_acc_test_1" { + group_id = selectel_iam_group_v1.group_tf_acc_test_1.id + + user_ids = [ + selectel_iam_serviceuser_v1.serviceuser_tf_acc_test_1.id + ] +} +` +} + +func testAccIAMV1GroupMembershipUpdate() string { + return ` +resource "selectel_iam_serviceuser_v1" "serviceuser_tf_acc_test_1" { + name = "test-service-user-1" + password = "Qazwsxedc123" + role { + role_name = "reader" + scope = "account" + } +} + +resource "selectel_iam_serviceuser_v1" "serviceuser_tf_acc_test_2" { + name = "test-service-user-2" + password = "Qazwsxedc123" + role { + role_name = "reader" + scope = "account" + } +} + +resource "selectel_iam_group_v1" "group_tf_acc_test_1" { + name = "test-group" + role { + role_name = "reader" + scope = "account" + } +} + +resource "selectel_iam_group_membership_v1" "membership_tf_acc_test_1" { + group_id = selectel_iam_group_v1.group_tf_acc_test_1.id + + user_ids = [ + selectel_iam_serviceuser_v1.serviceuser_tf_acc_test_1.id, + selectel_iam_serviceuser_v1.serviceuser_tf_acc_test_2.id + ] +} +` +} diff --git a/selectel/resource_selectel_iam_group_v1.go b/selectel/resource_selectel_iam_group_v1.go new file mode 100644 index 00000000..656ca117 --- /dev/null +++ b/selectel/resource_selectel_iam_group_v1.go @@ -0,0 +1,190 @@ +package selectel + +import ( + "context" + "errors" + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/selectel/iam-go" + "github.com/selectel/iam-go/iamerrors" + "github.com/selectel/iam-go/service/groups" + "github.com/selectel/iam-go/service/roles" +) + +func resourceIAMGroupV1() *schema.Resource { + return &schema.Resource{ + Description: "Represents a Group in IAM API", + CreateContext: resourceIAMGroupV1Create, + ReadContext: resourceIAMGroupV1Read, + UpdateContext: resourceIAMGroupV1Update, + DeleteContext: resourceIAMGroupV1Delete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the group.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Description of the group.", + }, + "role": { + Type: schema.TypeSet, + Optional: true, + Description: "Role block of the group.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "role_name": { + Type: schema.TypeString, + Required: true, + }, + "scope": { + Type: schema.TypeString, + Required: true, + }, + "project_id": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func resourceIAMGroupV1Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + iamClient, diagErr := getIAMClient(meta) + if diagErr != nil { + return diagErr + } + + roles, err := convertIAMSetToRoles(d.Get("role").(*schema.Set)) + if err != nil { + return diag.FromErr(err) + } + + opts := groups.CreateRequest{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + } + log.Print(msgCreate(objectGroup, opts)) + + group, err := iamClient.Groups.Create(ctx, opts) + if err != nil { + return diag.FromErr(errCreatingObject(objectGroup, err)) + } + d.SetId(group.ID) + + if len(roles) != 0 { + err = iamClient.Groups.AssignRoles(ctx, group.ID, roles) + if err != nil { + return diag.FromErr(errCreatingObject(objectGroup, err)) + } + } + + return resourceIAMGroupV1Read(ctx, d, meta) +} + +func resourceIAMGroupV1Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + iamClient, diagErr := getIAMClient(meta) + if diagErr != nil { + return diagErr + } + + log.Print(msgGet(objectGroup, d.Id())) + group, err := iamClient.Groups.Get(ctx, d.Id()) + if err != nil { + return diag.FromErr(errGettingObject(objectGroup, d.Id(), err)) + } + + d.Set("role", convertIAMRolesToSet(group.Roles)) + + return nil +} + +func resourceIAMGroupV1Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + iamClient, diagErr := getIAMClient(meta) + if diagErr != nil { + return diagErr + } + + description := d.Get("description").(string) + + opts := groups.UpdateRequest{ + Name: d.Get("name").(string), + Description: &description, + } + + log.Print(msgUpdate(objectGroup, d.Id(), fmt.Sprintf("Name: %+v, description: %+v", opts.Name, opts.Description))) + _, err := iamClient.Groups.Update(ctx, d.Id(), opts) + if err != nil { + return diag.FromErr(errUpdatingObject(objectGroup, d.Id(), err)) + } + + if d.HasChange("role") { + currentGroup, err := iamClient.Groups.Get(ctx, d.Id()) + if err != nil { + return diag.FromErr(errGettingObject(objectGroup, d.Id(), err)) + } + oldRoles := currentGroup.Roles + newRoles, err := convertIAMSetToRoles(d.Get("role").(*schema.Set)) + if err != nil { + return diag.FromErr(err) + } + + rolesToUnassign, rolesToAssign := diffRoles(oldRoles, newRoles) + + log.Print(msgUpdate(objectGroup, d.Id(), fmt.Sprintf("Roles to unassign: %+v, roles to assign: %+v", rolesToUnassign, rolesToAssign))) + err = applyGroupRoles(ctx, d, iamClient, rolesToUnassign, rolesToAssign) + if err != nil { + return diag.FromErr(errUpdatingObject(objectGroup, d.Id(), err)) + } + + return nil + } + + return resourceIAMGroupV1Read(ctx, d, meta) +} + +func resourceIAMGroupV1Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + iamClient, diagErr := getIAMClient(meta) + if diagErr != nil { + return diagErr + } + + log.Print(msgDelete(objectGroup, d.Id())) + err := iamClient.Groups.Delete(ctx, d.Id()) + if err != nil && !errors.Is(err, iamerrors.ErrGroupNotFound) { + return diag.FromErr(errDeletingObject(objectGroup, d.Id(), err)) + } + + d.SetId("") + + return nil +} + +func applyGroupRoles(ctx context.Context, d *schema.ResourceData, iamClient *iam.Client, rolesToUnassign, rolesToAssign []roles.Role) error { + if len(rolesToAssign) != 0 { + err := iamClient.Groups.AssignRoles(ctx, d.Id(), rolesToAssign) + if err != nil { + return err + } + } + + if len(rolesToUnassign) != 0 { + err := iamClient.Groups.UnassignRoles(ctx, d.Id(), rolesToUnassign) + if err != nil { + return err + } + } + + return nil +} diff --git a/selectel/resource_selectel_iam_group_v1_test.go b/selectel/resource_selectel_iam_group_v1_test.go new file mode 100644 index 00000000..d735a8fd --- /dev/null +++ b/selectel/resource_selectel_iam_group_v1_test.go @@ -0,0 +1,157 @@ +package selectel + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/selectel/iam-go/service/groups" +) + +func TestAccIAMV1GroupBasic(t *testing.T) { + var group groups.Group + + testName := "test-name" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccSelectelPreCheck(t) }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckIAMV1GroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccIAMV1GroupBasic(testName), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMV1GroupExists("selectel_iam_group_v1.group_tf_acc_test_1", &group), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "id"), + resource.TestCheckResourceAttr("selectel_iam_group_v1.group_tf_acc_test_1", "name", testName), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "role.0.role_name"), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "role.0.scope"), + ), + }, + }, + }) +} + +func TestAccIAMV1GroupUpdateRoles(t *testing.T) { + var group groups.Group + + testName := "test-name" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccSelectelPreCheck(t) }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckIAMV1GroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccIAMV1GroupBasic(testName), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMV1GroupExists("selectel_iam_group_v1.group_tf_acc_test_1", &group), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "id"), + resource.TestCheckResourceAttr("selectel_iam_group_v1.group_tf_acc_test_1", "name", testName), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "role.0.role_name"), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "role.0.scope"), + ), + }, + { + Config: testAccIAMV1GroupAssignRole(testName), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMV1GroupExists("selectel_iam_group_v1.group_tf_acc_test_1", &group), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "id"), + resource.TestCheckResourceAttr("selectel_iam_group_v1.group_tf_acc_test_1", "name", testName), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "role.0.role_name"), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "role.0.scope"), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "role.1.role_name"), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "role.1.scope"), + ), + }, + { + Config: testAccIAMV1GroupBasic(testName), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMV1GroupExists("selectel_iam_group_v1.group_tf_acc_test_1", &group), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "id"), + resource.TestCheckResourceAttr("selectel_iam_group_v1.group_tf_acc_test_1", "name", testName), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "role.0.role_name"), + resource.TestCheckResourceAttrSet("selectel_iam_group_v1.group_tf_acc_test_1", "role.0.scope"), + resource.TestCheckNoResourceAttr("selectel_iam_group_v1.group_tf_acc_test_1", "role.1.role_name"), + resource.TestCheckNoResourceAttr("selectel_iam_group_v1.group_tf_acc_test_1", "role.1.scope"), + ), + }, + }, + }) +} + +func testAccCheckIAMV1GroupDestroy(s *terraform.State) error { + iamClient, diagErr := getIAMClient(testAccProvider.Meta()) + if diagErr != nil { + return fmt.Errorf("can't get iamclient for test group object") + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "selectel_iam_group_v1" { + continue + } + + _, err := iamClient.Groups.Get(context.Background(), rs.Primary.ID) + if err == nil { + return errors.New("group still exists") + } + } + + return nil +} + +func testAccCheckIAMV1GroupExists(n string, group *groups.Group) 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 errors.New("no ID is set") + } + + iamClient, diagErr := getIAMClient(testAccProvider.Meta()) + if diagErr != nil { + return fmt.Errorf("can't get iamclient for test group object") + } + + g, err := iamClient.Groups.Get(context.Background(), rs.Primary.ID) + if err != nil { + return errors.New("group not found") + } + + *group = g.Group + + return nil + } +} + +func testAccIAMV1GroupBasic(name string) string { + return fmt.Sprintf(` +resource "selectel_iam_group_v1" "group_tf_acc_test_1" { + name = "%s" + role { + role_name = "reader" + scope = "account" + } +}`, name) +} + +func testAccIAMV1GroupAssignRole(name string) string { + return fmt.Sprintf(` + resource "selectel_iam_group_v1" "group_tf_acc_test_1" { + name = "%s" + role { + role_name = "reader" + scope = "account" + } + role { + role_name = "billing" + scope = "account" + } + }`, name) +} diff --git a/selectel/resource_selectel_iam_s3_credentials_v1.go b/selectel/resource_selectel_iam_s3_credentials_v1.go index 0ba0d476..61e9dae8 100644 --- a/selectel/resource_selectel_iam_s3_credentials_v1.go +++ b/selectel/resource_selectel_iam_s3_credentials_v1.go @@ -90,13 +90,13 @@ func resourceIAMS3CredentialsV1Read(ctx context.Context, d *schema.ResourceData, } log.Print(msgGet(objectS3Credentials, d.Id())) - credentials, err := iamClient.S3Credentials.List(ctx, d.Get("user_id").(string)) + response, err := iamClient.S3Credentials.List(ctx, d.Get("user_id").(string)) if err != nil { return diag.FromErr(errGettingObject(objectS3Credentials, d.Id(), err)) } - var credential s3credentials.Credentials - for _, c := range credentials { + var credential s3credentials.Credential + for _, c := range response.Credentials { if d.Id() == c.AccessKey { credential = c break diff --git a/selectel/resource_selectel_iam_s3_credentials_v1_test.go b/selectel/resource_selectel_iam_s3_credentials_v1_test.go index 3ed967d2..a9be4275 100644 --- a/selectel/resource_selectel_iam_s3_credentials_v1_test.go +++ b/selectel/resource_selectel_iam_s3_credentials_v1_test.go @@ -13,7 +13,7 @@ import ( ) func TestAccIAMV1S3CredentialsBasic(t *testing.T) { - var s3credentials s3credentials.Credentials + var s3credential s3credentials.Credential s3CredsName := acctest.RandomWithPrefix("tf-acc") projectName := acctest.RandomWithPrefix("tf-acc") userName := acctest.RandomWithPrefix("tf-acc") @@ -27,7 +27,7 @@ func TestAccIAMV1S3CredentialsBasic(t *testing.T) { { Config: testAccIAMV1S3CredentialsBasic(projectName, userName, userPassword, s3CredsName), Check: resource.ComposeTestCheckFunc( - testAccCheckIAMV1S3CredentialsExists("selectel_iam_s3_credentials_v1.s3_creds_tf_acc_test_1", &s3credentials), + testAccCheckIAMV1S3CredentialsExists("selectel_iam_s3_credentials_v1.s3_creds_tf_acc_test_1", &s3credential), resource.TestCheckResourceAttrSet("selectel_iam_s3_credentials_v1.s3_creds_tf_acc_test_1", "user_id"), resource.TestCheckResourceAttrSet("selectel_iam_s3_credentials_v1.s3_creds_tf_acc_test_1", "project_id"), resource.TestCheckResourceAttrSet("selectel_iam_s3_credentials_v1.s3_creds_tf_acc_test_1", "secret_key"), @@ -50,8 +50,8 @@ func testAccCheckIAMV1S3CredentialsDestroy(s *terraform.State) error { continue } - credentialsList, _ := iamClient.S3Credentials.List(context.Background(), rs.Primary.Attributes["user_id"]) - for _, cred := range credentialsList { + response, _ := iamClient.S3Credentials.List(context.Background(), rs.Primary.Attributes["user_id"]) + for _, cred := range response.Credentials { if cred.AccessKey == rs.Primary.ID { return errors.New("s3 credentials still exist") } @@ -61,7 +61,7 @@ func testAccCheckIAMV1S3CredentialsDestroy(s *terraform.State) error { return nil } -func testAccCheckIAMV1S3CredentialsExists(n string, s3Credential *s3credentials.Credentials) resource.TestCheckFunc { +func testAccCheckIAMV1S3CredentialsExists(n string, s3Credential *s3credentials.Credential) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -77,9 +77,9 @@ func testAccCheckIAMV1S3CredentialsExists(n string, s3Credential *s3credentials. return fmt.Errorf("can't get iamclient for test s3 credentials object") } - credentialsList, _ := iamClient.S3Credentials.List(context.Background(), rs.Primary.Attributes["user_id"]) - var neededS3Credentials s3credentials.Credentials - for _, cred := range credentialsList { + response, _ := iamClient.S3Credentials.List(context.Background(), rs.Primary.Attributes["user_id"]) + var neededS3Credentials s3credentials.Credential + for _, cred := range response.Credentials { if cred.Name == rs.Primary.Attributes["name"] { neededS3Credentials = cred break diff --git a/selectel/resource_selectel_iam_serviceuser_v1_test.go b/selectel/resource_selectel_iam_serviceuser_v1_test.go index bec34bf8..5afd0a37 100644 --- a/selectel/resource_selectel_iam_serviceuser_v1_test.go +++ b/selectel/resource_selectel_iam_serviceuser_v1_test.go @@ -171,7 +171,7 @@ func testAccCheckIAMV1ServiceUserExists(n string, serviceUser *serviceusers.Serv return errors.New("serviceUser not found") } - *serviceUser = *su + *serviceUser = su.ServiceUser return nil } diff --git a/selectel/resource_selectel_iam_user_v1_test.go b/selectel/resource_selectel_iam_user_v1_test.go index e4058af1..e5f73592 100644 --- a/selectel/resource_selectel_iam_user_v1_test.go +++ b/selectel/resource_selectel_iam_user_v1_test.go @@ -123,7 +123,7 @@ func testAccCheckIAMV1UserExists(n string, user *users.User) resource.TestCheckF return errors.New("user not found") } - *user = *u + *user = u.User return nil } diff --git a/website/docs/r/iam_group_membership_v1.html.markdown b/website/docs/r/iam_group_membership_v1.html.markdown new file mode 100644 index 00000000..a787b0c1 --- /dev/null +++ b/website/docs/r/iam_group_membership_v1.html.markdown @@ -0,0 +1,32 @@ +--- +layout: "selectel" +page_title: "Selectel: selectel_iam_group_membership_v1" +sidebar_current: "docs-selectel-resource-iam-group_membership-v1" +description: |- + Creates and manages group membership for Selectel products using public API v1. +--- + +# selectel\_iam\_group_membership\_v1 + +Manages group membership for Selectel products using public API v1. +Selectel products support Identity and Access Management (IAM). +For more information about groups, see the [official Selectel documentation](https://docs.selectel.ru/control-panel-actions/users-and-roles/groups/). + +## Example Usage + +```hcl +resource "selectel_iam_group_membership_v1" "group_membership_1" { + group_id = selectel_iam_group_v1.group_1.id + + user_ids = [ + selectel_iam_user_v1.user_1.keystone_id, + selectel_iam_serviceuser_v1.serviceuser_1.id + ] +} +``` + +## Argument Reference + +* `group_id` - (Required) Unique identifier of the group. Retrieved from the [selectel_iam_group_v1](https://registry.terraform.io/providers/selectel/selectel/latest/docs/resources/iam_group_v1) resource. + +* `user_ids` - (Required) List of unique Keystone identifiers of users. Retrieved from the [selectel_iam_serviceuser_v1](https://registry.terraform.io/providers/selectel/selectel/latest/docs/resources/iam_serviceuser_v1) and [selectel_iam_user_v1](https://registry.terraform.io/providers/selectel/selectel/latest/docs/resources/iam_user_v1) resources. diff --git a/website/docs/r/iam_group_v1.html.markdown b/website/docs/r/iam_group_v1.html.markdown new file mode 100644 index 00000000..0c6e5b35 --- /dev/null +++ b/website/docs/r/iam_group_v1.html.markdown @@ -0,0 +1,77 @@ +--- +layout: "selectel" +page_title: "Selectel: selectel_iam_group_v1" +sidebar_current: "docs-selectel-resource-iam-group-v1" +description: |- + Creates and manages a user group for Selectel products using public API v1. +--- + +# selectel\_iam\_group\_v1 + +Creates and manages a user group for Selectel products using public API v1. +Selectel products support Identity and Access Management (IAM). +For more information about user groups, see the [official Selectel documentation](https://docs.selectel.ru/control-panel-actions/users-and-roles/groups/). + +## Example Usage + +```hcl +resource "selectel_iam_group_v1" "group_1" { + name = "My group" + description = "My test group" + role { + role_name = "member" + scope = "account" + } +} +``` + +## Argument Reference + +* `name` - (Required) Group name. + +* `description` - (Optional) Group description. + +* `role` - (Optional) Manages group roles. You can add multiple roles – each role in a separate block. For more information about roles, see the [Roles](#roles) section. + + * `role_name` - (Required) Role name. Available role names are `iam_admin`, `member`, `reader`, and `billing`. + + * `scope` - (Required) Scope of the role. Available scopes are `account` and `project`. If `scope` is `project`, the `project_id` argument is required. + + * `project_id` - (Optional) Unique identifier of the associated project. If `scope` is `project`, the `project_id` argument is required. Retrieved from the [selectel_vpc_project_v2](https://registry.terraform.io/providers/selectel/selectel/latest/docs/resources/vpc_project_v2) resource. Learn more about [Projects](https://docs.selectel.ru/en/control-panel-actions/projects/about-projects/). + +### Roles + +To assign roles, use the following values for `scope` and `role_name`: + +* Account administrator - `scope` is `account`, `role_name` is `member`. + +* Billing administrator - `scope` is `account`, `role_name` is `billing`. + +* User administrator - `scope` is `account`, `role_name` is `iam_admin`. + +* Project administrator - `scope` is `project`, `role_name` is `member`. + +* Account viewer - `scope` is `account`, `role_name` is `reader`. + +* Project viewer - `scope` is `project`, `role_name` is `reader`. + +## Import + +You can import a group: + +```shell +export OS_DOMAIN_NAME= +export OS_USERNAME= +export OS_PASSWORD= +terraform import selectel_iam_group_v1.group_1 +``` + +where: + +* `` — Selectel account ID. The account ID is in the top right corner of the [Control panel](https://my.selectel.ru/). Learn more about [Registration](https://docs.selectel.ru/en/control-panel-actions/account/registration/). + +* `` — Name of the service user. To get the name, in the [Control panel](https://my.selectel.ru/iam/users_management/users?type=service), go to **Identity & Access Management** ⟶ **User management** ⟶ the **Service users** tab ⟶ copy the name of the required user. Learn more about [Service Users](https://docs.selectel.ru/en/control-panel-actions/users-and-roles/user-types-and-roles/). + +* `` — Password of the service user. + +* `` — Unique identifier of the group, for example, `abc1bb378ac84e1234b869b77aadd2ab`. To get the group ID, use either [iam-go](https://github.com/selectel/iam-go) or [IAM API](https://developers.selectel.ru/docs/control-panel/iam/).