Skip to content

Commit 28af550

Browse files
committed
atlas support
1 parent f73d6e5 commit 28af550

File tree

8 files changed

+223
-74
lines changed

8 files changed

+223
-74
lines changed

api/v1beta1/database_types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ type SecretReference struct {
7373
}
7474

7575
type Role struct {
76-
Name string `json:"name"`
77-
Db *string `json:"db,omitempty"`
76+
Name string `json:"name"`
77+
78+
// +optional
79+
Db *string `json:"db,omitempty"`
7880
}
7981

8082
// Extension is a resource representing database extension

common/db/atlas.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package db
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"github.com/mongodb-forks/digest"
8+
"go.mongodb.org/atlas/mongodbatlas"
9+
10+
infrav1beta1 "github.com/doodlescheduling/k8sdb-controller/api/v1beta1"
11+
)
12+
13+
type AtlasRepository struct {
14+
atlas *mongodbatlas.Client
15+
groupId string
16+
}
17+
18+
func NewAtlasRepository(ctx context.Context, groupId, publicKey, privateKey string) (Handler, error) {
19+
t := digest.NewTransport(publicKey, privateKey)
20+
tc, err := t.Client()
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
return &AtlasRepository{
26+
groupId: groupId,
27+
atlas: mongodbatlas.NewClient(tc),
28+
}, nil
29+
}
30+
31+
func (m *AtlasRepository) Close(ctx context.Context) error {
32+
return nil
33+
}
34+
35+
// CreateDatabaseIfNotExists is a dummy to apply to fulfill the contract,
36+
// we don't need to create the database on Atlas
37+
func (m *AtlasRepository) CreateDatabaseIfNotExists(ctx context.Context, database string) error {
38+
return nil
39+
}
40+
41+
func (m *AtlasRepository) SetupUser(ctx context.Context, database string, username string, password string, roles []infrav1beta1.Role) error {
42+
doesUserExist, err := m.doesUserExist(ctx, database, username)
43+
if err != nil {
44+
return err
45+
}
46+
47+
if !doesUserExist {
48+
if err := m.createUser(context.Background(), database, username, password, roles); err != nil {
49+
return err
50+
}
51+
if doesUserExistNow, err := m.doesUserExist(ctx, database, username); err != nil {
52+
return err
53+
} else if !doesUserExistNow {
54+
return errors.New("user doesn't exist after create")
55+
}
56+
} else {
57+
if err := m.updateUserPasswordAndRoles(ctx, database, username, password, roles); err != nil {
58+
return err
59+
}
60+
}
61+
62+
return nil
63+
}
64+
65+
func (m *AtlasRepository) DropUser(ctx context.Context, database string, username string) error {
66+
//not implemented
67+
return errors.New("not yet supported")
68+
}
69+
70+
func (m *AtlasRepository) EnableExtension(ctx context.Context, name string) error {
71+
// NOOP
72+
return nil
73+
}
74+
75+
func (m *AtlasRepository) doesUserExist(ctx context.Context, database string, username string) (bool, error) {
76+
user, _, err := m.atlas.DatabaseUsers.Get(ctx, database, m.groupId, username)
77+
if user == nil {
78+
return false, err
79+
}
80+
81+
return true, err
82+
}
83+
84+
func (m *AtlasRepository) getRoles(database string, roles []infrav1beta1.Role) []mongodbatlas.Role {
85+
// by default, assign readWrite role (backward compatibility)
86+
if len(roles) == 0 {
87+
return []mongodbatlas.Role{{
88+
RoleName: "readWrite",
89+
DatabaseName: database,
90+
}}
91+
}
92+
93+
rs := make([]mongodbatlas.Role, 0)
94+
for _, r := range roles {
95+
db := r.Db
96+
if db == nil {
97+
db = &database
98+
}
99+
100+
rs = append(rs, mongodbatlas.Role{
101+
RoleName: r.Name,
102+
DatabaseName: *db,
103+
})
104+
}
105+
106+
return rs
107+
}
108+
109+
func (m *AtlasRepository) createUser(ctx context.Context, database string, username string, password string, roles []infrav1beta1.Role) error {
110+
user := &mongodbatlas.DatabaseUser{
111+
Username: username,
112+
Password: password,
113+
Roles: m.getRoles(database, roles),
114+
}
115+
116+
_, _, err := m.atlas.DatabaseUsers.Create(ctx, m.groupId, user)
117+
if err != nil {
118+
return err
119+
}
120+
121+
return nil
122+
}
123+
124+
func (m *AtlasRepository) updateUserPasswordAndRoles(ctx context.Context, database string, username string, password string, roles []infrav1beta1.Role) error {
125+
user := &mongodbatlas.DatabaseUser{
126+
Username: username,
127+
Password: password,
128+
Roles: m.getRoles(database, roles),
129+
}
130+
131+
_, _, err := m.atlas.DatabaseUsers.Update(ctx, m.groupId, username, user)
132+
if err != nil {
133+
return err
134+
}
135+
136+
return nil
137+
}

common/db/handler.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ import (
44
"context"
55

66
infrav1beta1 "github.com/doodlescheduling/k8sdb-controller/api/v1beta1"
7+
v1 "k8s.io/api/core/v1"
78
)
89

910
// Invoke a database handler
10-
type Invoke func(ctx context.Context, uri, database, username, password string) (Handler, error)
11+
type Invoke func(ctx context.Context, db *infrav1beta1.Database, secret *v1.Secret) (Handler, error)
1112

1213
// Handler is a wrapper arround a certain database client
1314
type Handler interface {
14-
Close() error
15-
SetupUser(database string, username string, password string, roles []infrav1beta1.Role) error
16-
DropUser(database string, username string) error
17-
CreateDatabaseIfNotExists(database string) error
18-
EnableExtension(name string) error
15+
Close(ctx context.Context) error
16+
SetupUser(ctx context.Context, database string, username string, password string, roles []infrav1beta1.Role) error
17+
DropUser(ctx context.Context, database string, username string) error
18+
CreateDatabaseIfNotExists(ctx context.Context, database string) error
19+
EnableExtension(ctx context.Context, name string) error
1920
}

common/db/mongodb.go

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package db
33
import (
44
"context"
55
"errors"
6-
"time"
76

87
"go.mongodb.org/mongo-driver/bson"
98
"go.mongodb.org/mongo-driver/bson/primitive"
@@ -57,69 +56,65 @@ func NewMongoDBRepository(ctx context.Context, uri, database, username, password
5756
}, nil
5857
}
5958

60-
func (m *MongoDBRepository) Close() error {
61-
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
62-
defer cancel()
59+
func (m *MongoDBRepository) Close(ctx context.Context) error {
6360
return m.client.Disconnect(ctx)
6461
}
6562

6663
// CreateDatabaseIfNotExists is a dummy to apply to fulfill the contract,
6764
// we don't need to create the database on MongoDB
68-
func (m *MongoDBRepository) CreateDatabaseIfNotExists(database string) error {
65+
func (m *MongoDBRepository) CreateDatabaseIfNotExists(ctx context.Context, database string) error {
6966
return nil
7067
}
7168

72-
func (m *MongoDBRepository) SetupUser(database string, username string, password string, roles []infrav1beta1.Role) error {
73-
doesUserExist, err := m.doesUserExist(database, username)
69+
func (m *MongoDBRepository) SetupUser(ctx context.Context, database string, username string, password string, roles []infrav1beta1.Role) error {
70+
doesUserExist, err := m.doesUserExist(ctx, database, username)
7471
if err != nil {
7572
return err
7673
}
7774

7875
if !doesUserExist {
79-
if err := m.createUser(database, username, password, roles); err != nil {
76+
if err := m.createUser(ctx, database, username, password, roles); err != nil {
8077
return err
8178
}
82-
if doesUserExistNow, err := m.doesUserExist(database, username); err != nil {
79+
if doesUserExistNow, err := m.doesUserExist(ctx, database, username); err != nil {
8380
return err
8481
} else if !doesUserExistNow {
8582
return errors.New("user doesn't exist after create")
8683
}
8784
} else {
88-
if err := m.updateUserPasswordAndRoles(database, username, password, roles); err != nil {
85+
if err := m.updateUserPasswordAndRoles(ctx, database, username, password, roles); err != nil {
8986
return err
9087
}
9188
}
9289

9390
return nil
9491
}
9592

96-
func (m *MongoDBRepository) DropUser(database string, username string) error {
93+
func (m *MongoDBRepository) DropUser(ctx context.Context, database string, username string) error {
9794
command := &bson.D{primitive.E{Key: "dropUser", Value: username}}
98-
r := m.runCommand(database, command)
95+
r := m.runCommand(ctx, database, command)
9996
if _, err := r.DecodeBytes(); err != nil {
10097
return err
10198
}
10299
return nil
103100
}
104101

105-
func (m *MongoDBRepository) EnableExtension(name string) error {
102+
func (m *MongoDBRepository) EnableExtension(ctx context.Context, name string) error {
106103
// NOOP
107104
return nil
108105
}
109106

110-
func (m *MongoDBRepository) doesUserExist(database string, username string) (bool, error) {
111-
users, err := m.getAllUsers(database, username)
107+
func (m *MongoDBRepository) doesUserExist(ctx context.Context, database string, username string) (bool, error) {
108+
users, err := m.getAllUsers(ctx, database, username)
112109
if err != nil {
113110
return false, err
114111
}
115112

116113
return users != nil && len(users) > 0, nil
117114
}
118115

119-
func (m *MongoDBRepository) getAllUsers(database string, username string) (Users, error) {
116+
func (m *MongoDBRepository) getAllUsers(ctx context.Context, database string, username string) (Users, error) {
120117
users := make(Users, 0)
121-
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
122-
defer cancel()
123118

124119
collection := m.client.Database(adminDatabase).Collection(usersCollection)
125120
cursor, err := collection.Find(ctx, bson.D{primitive.E{Key: "user", Value: username}, primitive.E{Key: "db", Value: database}})
@@ -163,28 +158,26 @@ func (m *MongoDBRepository) getRoles(database string, roles []infrav1beta1.Role)
163158
return rs
164159
}
165160

166-
func (m *MongoDBRepository) createUser(database string, username string, password string, roles []infrav1beta1.Role) error {
161+
func (m *MongoDBRepository) createUser(ctx context.Context, database string, username string, password string, roles []infrav1beta1.Role) error {
167162
command := &bson.D{primitive.E{Key: "createUser", Value: username}, primitive.E{Key: "pwd", Value: password},
168163
primitive.E{Key: "roles", Value: m.getRoles(database, roles)}}
169-
r := m.runCommand(database, command)
164+
r := m.runCommand(ctx, database, command)
170165
if _, err := r.DecodeBytes(); err != nil {
171166
return err
172167
}
173168
return nil
174169
}
175170

176-
func (m *MongoDBRepository) updateUserPasswordAndRoles(database string, username string, password string, roles []infrav1beta1.Role) error {
171+
func (m *MongoDBRepository) updateUserPasswordAndRoles(ctx context.Context, database string, username string, password string, roles []infrav1beta1.Role) error {
177172
command := &bson.D{primitive.E{Key: "updateUser", Value: username}, primitive.E{Key: "pwd", Value: password},
178173
primitive.E{Key: "roles", Value: m.getRoles(database, roles)}}
179-
r := m.runCommand(database, command)
174+
r := m.runCommand(ctx, database, command)
180175
if _, err := r.DecodeBytes(); err != nil {
181176
return err
182177
}
183178
return nil
184179
}
185180

186-
func (m *MongoDBRepository) runCommand(database string, command *bson.D) *mongo.SingleResult {
187-
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
188-
defer cancel()
181+
func (m *MongoDBRepository) runCommand(ctx context.Context, database string, command *bson.D) *mongo.SingleResult {
189182
return m.client.Database(database).RunCommand(ctx, *command)
190183
}

0 commit comments

Comments
 (0)