Skip to content

Commit

Permalink
feat: database users resource
Browse files Browse the repository at this point in the history
  • Loading branch information
vlnevyhosteny committed Nov 5, 2023
1 parent be1e7b0 commit 0d157cd
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 9 deletions.
1 change: 1 addition & 0 deletions pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func (d *MongoDB) ResourceSyncers(ctx context.Context) []connectorbuilder.Resour
newUserBuilder(d.client),
newTeamBuilder(d.client),
newProjectBuilder(d.client),
newDatabaseUserBuilder(d.client),
}
}

Expand Down
103 changes: 103 additions & 0 deletions pkg/connector/database_users.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package connector

import (
"context"

v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
"github.com/conductorone/baton-sdk/pkg/annotations"
"github.com/conductorone/baton-sdk/pkg/pagination"
rs "github.com/conductorone/baton-sdk/pkg/types/resource"
"go.mongodb.org/atlas-sdk/v20231001002/admin"
)

type databaseUserBuilder struct {
resourceType *v2.ResourceType
client *admin.APIClient
}

func (o *databaseUserBuilder) ResourceType(ctx context.Context) *v2.ResourceType {
return databaseUserResourceType
}

func newDatabaseUserResource(ctx context.Context, projectId *v2.ResourceId, user admin.CloudDatabaseUser) (*v2.Resource, error) {
profile := map[string]interface{}{
"username": user.Username,
"login": user.Username,
"database_name": user.DatabaseName,
}

userTraits := []rs.UserTraitOption{
rs.WithUserProfile(profile),
rs.WithUserLogin(user.Username),
rs.WithStatus(v2.UserTrait_Status_STATUS_UNSPECIFIED),
}

resource, err := rs.NewUserResource(
user.Username,
databaseUserResourceType,
user.Username,
userTraits,
rs.WithParentResourceID(projectId),
)
if err != nil {
return nil, err
}

return resource, nil
}

func newDatabaseUserBuilder(client *admin.APIClient) *databaseUserBuilder {
return &databaseUserBuilder{
resourceType: databaseUserResourceType,
client: client,
}
}

// List returns all the users from the database as resource objects.
// Users include a UserTrait because they are the 'shape' of a standard user.
func (o *databaseUserBuilder) List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
if parentResourceID == nil {
return nil, "", nil, nil
}

bag, page, err := parsePageToken(pToken.Token, &v2.ResourceId{ResourceType: o.resourceType.Id})
if err != nil {
return nil, "", nil, err
}

users, _, err := o.client.DatabaseUsersApi.ListDatabaseUsers(ctx, parentResourceID.Resource).IncludeCount(true).PageNum(page).ItemsPerPage(resourcePageSize).Execute()
if err != nil {
return nil, "", nil, wrapError(err, "failed to list database users")
}

var resources []*v2.Resource
for _, user := range users.Results {
resource, err := newDatabaseUserResource(ctx, parentResourceID, user)
if err != nil {
return nil, "", nil, wrapError(err, "failed to create database user resource")
}

resources = append(resources, resource)
}

if isLastPage(*users.TotalCount, resourcePageSize) {
return resources, "", nil, nil
}

nextPage, err := getPageTokenFromPage(bag, page+1)
if err != nil {
return nil, "", nil, err
}

return resources, nextPage, nil, nil
}

// Entitlements always returns an empty slice for users.
func (o *databaseUserBuilder) Entitlements(_ context.Context, resource *v2.Resource, _ *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) {
return nil, "", nil, nil
}

// Grants always returns an empty slice for users since they don't have any entitlements.
func (o *databaseUserBuilder) Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
return nil, "", nil, nil
}
82 changes: 73 additions & 9 deletions pkg/connector/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ func newProjectResource(ctx context.Context, organizationId *v2.ResourceId, proj
projectId,
projectTraits,
rs.WithParentResourceID(organizationId),
rs.WithAnnotation(
&v2.ChildResourceType{ResourceTypeId: databaseUserResourceType.Id},
),
)
if err != nil {
return nil, err
Expand All @@ -56,6 +59,10 @@ func newProjectBuilder(client *admin.APIClient) *projectBuilder {
}

func (p *projectBuilder) List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
if parentResourceID == nil {
return nil, "", nil, nil
}

bag, page, err := parsePageToken(pToken.Token, &v2.ResourceId{ResourceType: p.resourceType.Id})
if err != nil {
return nil, "", nil, err
Expand Down Expand Up @@ -101,39 +108,96 @@ func (p *projectBuilder) Entitlements(_ context.Context, resource *v2.Resource,
entitlement := ent.NewAssignmentEntitlement(resource, memberEntitlement, assigmentOptions...)
rv = append(rv, entitlement)

assigmentOptions = []ent.EntitlementOption{
ent.WithGrantableTo(databaseUserResourceType),
ent.WithDescription(fmt.Sprintf("Member of %s team", resource.DisplayName)),
ent.WithDisplayName(fmt.Sprintf("%s team %s", resource.DisplayName, memberEntitlement)),
}

entitlement = ent.NewAssignmentEntitlement(resource, memberEntitlement, assigmentOptions...)
rv = append(rv, entitlement)

return rv, "", nil, nil
}

// Grants always returns an empty slice for users since they don't have any entitlements.
func (p *projectBuilder) Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
bag, page, err := parsePageToken(pToken.Token, &v2.ResourceId{ResourceType: p.resourceType.Id})
bag, page, err := parsePageToken(pToken.Token, &v2.ResourceId{ResourceType: databaseUserResourceType.Id}, &v2.ResourceId{ResourceType: userResourceType.Id})
if err != nil {
return nil, "", nil, err
}

var rv []*v2.Grant
var count int
switch bag.Current().ResourceTypeID {
case databaseUserResourceType.Id:
grants, c, err := p.GrantDatabaseUsers(ctx, resource, page)
if err != nil {
return nil, "", nil, err
}
count = c
rv = append(rv, grants...)
case userResourceType.Id:
grants, c, err := p.GrantUsers(ctx, resource, page)
if err != nil {
return nil, "", nil, err
}
count = c
rv = append(rv, grants...)
}

if isLastPage(count, resourcePageSize) {
nextPage, err := bag.NextToken("")
if err != nil {
return nil, "", nil, err
}

// Process the next resource type.
return rv, nextPage, nil, nil
}

nextPage, err := getPageTokenFromPage(bag, page+1)
if err != nil {
return nil, "", nil, err
}

return rv, nextPage, nil, nil
}

func (p *projectBuilder) GrantUsers(ctx context.Context, resource *v2.Resource, page int) ([]*v2.Grant, int, error) {
members, _, err := p.client.ProjectsApi.ListProjectUsers(ctx, resource.Id.Resource).PageNum(page).ItemsPerPage(resourcePageSize).IncludeCount(true).Execute()
if err != nil {
return nil, "", nil, wrapError(err, "failed to list team members")
return nil, 0, wrapError(err, "failed to list project users")
}

var rv []*v2.Grant
for _, member := range members.Results {
userResource, err := newUserResource(ctx, resource.ParentResourceId, member)
if err != nil {
return nil, "", nil, wrapError(err, "failed to create user resource")
return nil, *members.TotalCount, wrapError(err, "failed to create user resource")
}

rv = append(rv, grant.NewGrant(resource, memberEntitlement, userResource.Id))
}

if isLastPage(*members.TotalCount, resourcePageSize) {
return rv, "", nil, nil
}
return rv, *members.TotalCount, nil
}

nextPage, err := getPageTokenFromPage(bag, page+1)
func (p *projectBuilder) GrantDatabaseUsers(ctx context.Context, resource *v2.Resource, page int) ([]*v2.Grant, int, error) {
members, _, err := p.client.DatabaseUsersApi.ListDatabaseUsers(ctx, resource.Id.Resource).PageNum(page).ItemsPerPage(resourcePageSize).IncludeCount(true).Execute()
if err != nil {
return nil, "", nil, err
return nil, 0, wrapError(err, "failed to list project database users")
}

var rv []*v2.Grant
for _, member := range members.Results {
userResource, err := newDatabaseUserResource(ctx, resource.ParentResourceId, member)
if err != nil {
return nil, *members.TotalCount, wrapError(err, "failed to create database user resource")
}

rv = append(rv, grant.NewGrant(resource, memberEntitlement, userResource.Id))
}

return nil, nextPage, nil, nil
return rv, *members.TotalCount, nil
}
7 changes: 7 additions & 0 deletions pkg/connector/resource_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,11 @@ var (
Description: "A MongoDB Atlas Project",
Traits: []v2.ResourceType_Trait{v2.ResourceType_TRAIT_GROUP},
}
databaseUserResourceType = &v2.ResourceType{
Id: "database_user",
DisplayName: "Database User",
Description: "A MongoDB Atlas Database User",
Traits: []v2.ResourceType_Trait{v2.ResourceType_TRAIT_USER},
Annotations: getSkippEntitlementsAndGrantsAnnotations(),
}
)

0 comments on commit 0d157cd

Please sign in to comment.