Skip to content

Commit

Permalink
Add IAM resources (#258)
Browse files Browse the repository at this point in the history
* Added selectel_iam_user_v1 resource
* Added selectel_iam_serviceuser_v1 resource
* Added selectel_iam_s3_credentials_v1 resource
* Deprecated selectel_vpc_user_v2 resource
* Deprecated selectel_vpc_role_v2 resource
  • Loading branch information
Icerzack authored Apr 17, 2024
1 parent cb5f1e8 commit 05fa93d
Show file tree
Hide file tree
Showing 27 changed files with 1,745 additions and 520 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +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/mks-go v0.14.0
github.com/selectel/secretsmanager-go v0.2.1
github.com/stretchr/testify v1.8.4
Expand All @@ -24,7 +25,7 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gophercloud/gophercloud v1.5.0 // indirect
github.com/gophercloud/gophercloud v1.10.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/gophercloud/gophercloud v1.5.0 h1:cDN6XFCLKiiqvYpjQLq9AiM7RDRbIC9450WpPH+yvXo=
github.com/gophercloud/gophercloud v1.5.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
github.com/gophercloud/gophercloud v1.10.0 h1:watRMsaMDlSLuLkpLeLSQ87yvcuwIajNg6A5uLcjoIU=
github.com/gophercloud/gophercloud v1.10.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
Expand Down Expand Up @@ -167,6 +167,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/mks-go v0.14.0 h1:huNq/oTutPc3ezB8HRqlGN9WJubTDETpNKuIVqcZOn0=
github.com/selectel/mks-go v0.14.0/go.mod h1:VxtV3dzwgOEzZc+9VMQb9DvxfSlej2ZQ8jnT8kqIGgU=
github.com/selectel/secretsmanager-go v0.2.1 h1:OSBrA/07lm/Ecpwg59IJHFAoUHZR29oyfwUgTpr/dos=
Expand Down
164 changes: 164 additions & 0 deletions selectel/iam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package selectel

import (
"errors"
"fmt"
"slices"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/selectel/go-selvpcclient/v3/selvpcclient"
"github.com/selectel/iam-go"
"github.com/selectel/iam-go/service/roles"
"github.com/selectel/iam-go/service/users"
)

const (
importIAMUndefined = "UNDEFINED_WHILE_IMPORTING"
)

func getIAMClient(meta interface{}) (*iam.Client, diag.Diagnostics) {
config := meta.(*Config)

selvpcClient, err := config.GetSelVPCClient()
if err != nil {
return nil, diag.FromErr(fmt.Errorf("can't get selvpc client for iam: %w", err))
}

apiURL, err := getEndpointForIAM(selvpcClient, config.AuthRegion)
if err != nil {
return nil, diag.FromErr(err)
}
iamClient, err := iam.New(
iam.WithAuthOpts(&iam.AuthOpts{
KeystoneToken: selvpcClient.GetXAuthToken(),
}),
iam.WithAPIUrl(apiURL),
)
if err != nil {
return nil, diag.FromErr(fmt.Errorf("can't create iam client: %w", err))
}

return iamClient, nil
}

func getEndpointForIAM(selvpcClient *selvpcclient.Client, region string) (string, error) {
endpoint, err := selvpcClient.Catalog.GetEndpoint(IAM, region)
if err != nil {
return "", fmt.Errorf("can't get endpoint to for iam: %w", err)
}

return endpoint.URL, nil
}

func diffRoles(oldRoles, newRoles []roles.Role) ([]roles.Role, []roles.Role) {
rolesToUnassign := make([]roles.Role, 0)
rolesToAssign := make([]roles.Role, 0)

for _, oldRole := range oldRoles {
if !slices.Contains(newRoles, oldRole) {
rolesToUnassign = append(rolesToUnassign, oldRole)
}
}

for _, newRole := range newRoles {
if !slices.Contains(oldRoles, newRole) {
rolesToAssign = append(rolesToAssign, newRole)
}
}

return rolesToUnassign, rolesToAssign
}

func convertIAMListToUserFederation(federationList []interface{}) (*users.Federation, error) {
if len(federationList) == 0 {
return nil, nil
}
if len(federationList) > 1 {
return nil, errors.New("more than one federation value provided")
}
var idRaw, externalIDRaw interface{}
var ok bool

if idRaw, ok = federationList[0].(map[string]interface{})["id"]; !ok {
return nil, errors.New("federation.id value isn't provided")
}
if externalIDRaw, ok = federationList[0].(map[string]interface{})["external_id"]; !ok {
return nil, errors.New("federation.external_id value isn't provided")
}

id := idRaw.(string)
externalID := externalIDRaw.(string)

federation := &users.Federation{
ExternalID: externalID,
ID: id,
}

return federation, nil
}

func convertIAMSetToRoles(rolesSet *schema.Set) ([]roles.Role, error) {
rolesList := rolesSet.List()

output := make([]roles.Role, len(rolesList))
var roleNameRaw, scopeRaw, projectIDRaw interface{}

for i := range rolesList {
var roleName, scope, projectID string
resourceRoleMap := rolesList[i].(map[string]interface{})

if roleNameRaw = resourceRoleMap["role_name"]; roleNameRaw == "" {
return nil, errors.New("role_name value isn't provided")
}
if scopeRaw = resourceRoleMap["scope"]; scopeRaw == "" {
return nil, errors.New("scope value isn't provided")
}

roleName = roleNameRaw.(string)
scope = scopeRaw.(string)

if projectIDRaw = resourceRoleMap["project_id"]; projectIDRaw == "" && scope == string(roles.Project) {
return nil, errors.New("project_id must be set for project scope")
} else if projectIDRaw != "" {
if scope != string(roles.Project) {
return nil, errors.New("project_id can be set only for project scope")
}
projectID = projectIDRaw.(string)
}

output[i] = roles.Role{
RoleName: roles.Name(roleName),
Scope: roles.Scope(scope),
ProjectID: projectID,
}
}

return output, nil
}

func convertIAMRolesToSet(roles []roles.Role) []interface{} {
result := make([]interface{}, 0, len(roles))
for _, role := range roles {
result = append(result, map[string]interface{}{
"role_name": role.RoleName,
"scope": role.Scope,
"project_id": role.ProjectID,
})
}

return result
}

func convertIAMFederationToList(federation *users.Federation) []interface{} {
if federation == nil {
return nil
}

return []interface{}{
map[string]interface{}{
"id": federation.ID,
"external_id": federation.ExternalID,
},
}
}
142 changes: 142 additions & 0 deletions selectel/iam_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package selectel

import (
"testing"

"github.com/selectel/iam-go/service/roles"
"github.com/stretchr/testify/assert"
)

func TestIAMDiffRoles(t *testing.T) {
type args struct {
oldRoles []roles.Role
newRoles []roles.Role
}
tests := map[string]struct {
args args
wantToUnassign []roles.Role
wantToAssign []roles.Role
}{
"Test assigning new roles": {
args: args{
oldRoles: []roles.Role{},
newRoles: []roles.Role{
{
RoleName: "role1",
Scope: "scope1",
ProjectID: "project1",
},
{
RoleName: "role2",
Scope: "scope2",
ProjectID: "project2",
},
},
},
wantToUnassign: []roles.Role{},
wantToAssign: []roles.Role{
{
RoleName: "role1",
Scope: "scope1",
ProjectID: "project1",
},
{
RoleName: "role2",
Scope: "scope2",
ProjectID: "project2",
},
},
},
"Test unassigning all roles": {
args: args{
oldRoles: []roles.Role{
{
RoleName: "role1",
Scope: "scope1",
ProjectID: "project1",
},
{
RoleName: "role2",
Scope: "scope2",
ProjectID: "project2",
},
},
newRoles: []roles.Role{},
},
wantToUnassign: []roles.Role{
{
RoleName: "role1",
Scope: "scope1",
ProjectID: "project1",
},
{
RoleName: "role2",
Scope: "scope2",
ProjectID: "project2",
},
},
wantToAssign: []roles.Role{},
},
"Test mix of assigning and unassigning roles": {
args: args{
oldRoles: []roles.Role{
{
RoleName: "role1",
Scope: "scope1",
ProjectID: "project1",
},
{
RoleName: "role2",
Scope: "scope2",
ProjectID: "project2",
},
},
newRoles: []roles.Role{
{
RoleName: "role2",
Scope: "scope2",
ProjectID: "project2",
},
{
RoleName: "role3",
Scope: "scope3",
ProjectID: "project3",
},
{
RoleName: "role4",
Scope: "scope4",
ProjectID: "project4",
},
},
},
wantToUnassign: []roles.Role{
{
RoleName: "role1",
Scope: "scope1",
ProjectID: "project1",
},
},
wantToAssign: []roles.Role{
{
RoleName: "role3",
Scope: "scope3",
ProjectID: "project3",
},
{
RoleName: "role4",
Scope: "scope4",
ProjectID: "project4",
},
},
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
actualRolesToUnassign, actualRolesToAssign := diffRoles(tt.args.oldRoles, tt.args.newRoles)
assert := assert.New(t)
assert.Equal(tt.wantToUnassign, actualRolesToUnassign)
assert.Equal(tt.wantToAssign, actualRolesToAssign)
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccVPCV2UserImportBasic(t *testing.T) {
resourceName := "selectel_vpc_user_v2.user_tf_acc_test_1"
userName := acctest.RandomWithPrefix("tf-acc")
userPassword := acctest.RandString(8)
func TestAccIAMV1ServiceUserImportBasic(t *testing.T) {
resourceName := "selectel_iam_serviceuser_v1.serviceuser_tf_acc_test_1"
serviceUserName := acctest.RandomWithPrefix("tf-acc")
serviceUserPassword := "A" + acctest.RandString(8) + "1"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccSelectelPreCheck(t) },
ProviderFactories: testAccProviders,
CheckDestroy: testAccCheckVPCV2UserDestroy,
CheckDestroy: testAccCheckIAMV1ServiceUserDestroy,
Steps: []resource.TestStep{
{
Config: testAccVPCV2UserBasic(userName, userPassword),
Config: testAccIAMV1ServiceUserBasic(serviceUserName, serviceUserPassword),
},
{
ResourceName: resourceName,
Expand Down
Loading

0 comments on commit 05fa93d

Please sign in to comment.