Skip to content

Commit 875adf8

Browse files
committed
enhancements
1 parent e984e2d commit 875adf8

23 files changed

+1160
-393
lines changed

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11

22
# Image URL to use all building/pushing image targets
33
IMG ?= controller:latest
4-
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
5-
CRD_OPTIONS ?= "crd:trivialVersions=true"
4+
5+
# Produce CRDs that work back to Kubernetes 1.16
6+
CRD_OPTIONS ?= crd:crdVersions=v1
67

78
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
89
ifeq (,$(shell go env GOBIN))
@@ -71,7 +72,7 @@ ifeq (, $(shell which controller-gen))
7172
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
7273
cd $$CONTROLLER_GEN_TMP_DIR ;\
7374
go mod init tmp ;\
74-
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.5 ;\
75+
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.3.0 ;\
7576
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
7677
}
7778
CONTROLLER_GEN=$(GOBIN)/controller-gen

api/v1beta1/database_types.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import (
77

88
// Status conditions
99
const (
10-
ProvisionedCondition = "Provisioned"
10+
DatabaseReadyConditionType = "DatabaseReady"
11+
UserReadyConditionType = "UserReady"
1112
)
1213

1314
// Status reasons
@@ -24,30 +25,38 @@ const (
2425

2526
// DatabaseSpec defines the desired state of MongoDBDatabase
2627
type DatabaseSpec struct {
27-
// The name of the database, if not set the name is taken from metadata.name
28+
// DatabaseName is by default the same as metata.name
2829
// +optional
2930
DatabaseName string `json:"databaseName"`
3031

31-
// The MongoDB URI
32+
// The connect URI
3233
// +required
3334
Address string `json:"address"`
3435

36+
// Contains a credentials set of a user with enough permission to manage databases and user accounts
3537
// +required
3638
RootSecret *SecretReference `json:"rootSecret"`
3739
}
3840

41+
// DatabaseReference is a named reference to a database kind
3942
type DatabaseReference struct {
43+
// Name referrs to the name of the database kind, mist be located within the same namespace
44+
// +required
4045
Name string `json:"name"`
4146
}
4247

48+
// SecretReference is a named reference to a secret which contains user credentials
4349
type SecretReference struct {
50+
// Name referrs to the name of the secret, must be located whithin the same namespace
4451
// +required
4552
Name string `json:"name"`
4653

4754
// +optional
55+
// +kubebuilder:default:=username
4856
UserField string `json:"userField"`
4957

50-
// +required
58+
// +optional
59+
// +kubebuilder:default:=password
5160
PasswordField string `json:"passwordField"`
5261
}
5362

@@ -56,12 +65,20 @@ type conditionalResource interface {
5665
GetStatusConditions() *[]metav1.Condition
5766
}
5867

59-
func NotProvisioned(in conditionalResource, reason, message string) {
60-
setResourceCondition(in, ProvisionedCondition, metav1.ConditionFalse, reason, message)
68+
func DatabaseNotReadyCondition(in conditionalResource, reason, message string) {
69+
setResourceCondition(in, DatabaseReadyConditionType, metav1.ConditionFalse, reason, message)
70+
}
71+
72+
func DatabaseReadyCondition(in conditionalResource, reason, message string) {
73+
setResourceCondition(in, DatabaseReadyConditionType, metav1.ConditionTrue, reason, message)
74+
}
75+
76+
func UserNotReadyCondition(in conditionalResource, reason, message string) {
77+
setResourceCondition(in, UserReadyConditionType, metav1.ConditionFalse, reason, message)
6178
}
6279

63-
func Provisioned(in conditionalResource, reason, message string) {
64-
setResourceCondition(in, ProvisionedCondition, metav1.ConditionTrue, reason, message)
80+
func UserReadyCondition(in conditionalResource, reason, message string) {
81+
setResourceCondition(in, UserReadyConditionType, metav1.ConditionTrue, reason, message)
6582
}
6683

6784
// setResourceCondition sets the given condition with the given status,

api/v1beta1/mongodbdatabase_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,14 @@ type MongoDBDatabaseStatus struct {
5050
Conditions []metav1.Condition `json:"conditions,omitempty"`
5151
}
5252

53+
// +genclient
54+
// +genclient:Namespaced
5355
// +kubebuilder:object:root=true
56+
// +kubebuilder:resource:shortName=mdb
5457
// +kubebuilder:subresource:status
58+
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Provisioned\")].status",description=""
59+
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Provisioned\")].message",description=""
60+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
5561

5662
// MongoDBDatabase is the Schema for the mongodbs API
5763
type MongoDBDatabase struct {

api/v1beta1/mongodbuser_type.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,24 @@ import (
2222
)
2323

2424
type MongoDBUserSpec struct {
25-
Database *DatabaseReference `json:"database"`
26-
Credentials *SecretReference `json:"credentials"`
25+
// +required
26+
Database *DatabaseReference `json:"database"`
27+
28+
// +required
29+
Credentials *SecretReference `json:"credentials"`
30+
31+
// +required
32+
Roles []*MongoDBRole `json:"roles"`
33+
34+
// +optional
35+
CustomData map[string]string `json:"customData"`
36+
}
37+
38+
// MongoDBRole see https://docs.mongodb.com/manual/reference/method/db.createUser/#create-user-with-roles
39+
type MongoDBRole struct {
40+
// +optional
41+
// +kubebuilder:default:=readWrite
42+
Role string `json:"role"`
2743
}
2844

2945
// GetStatusConditions returns a pointer to the Status.Conditions slice
@@ -39,8 +55,14 @@ type MongoDBUserStatus struct {
3955
Conditions []metav1.Condition `json:"conditions,omitempty"`
4056
}
4157

58+
// +genclient
59+
// +genclient:Namespaced
4260
// +kubebuilder:object:root=true
61+
// +kubebuilder:resource:shortName=mdu
4362
// +kubebuilder:subresource:status
63+
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Provisioned\")].status",description=""
64+
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Provisioned\")].message",description=""
65+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
4466

4567
// MongoDBUser is the Schema for the mongodbs API
4668
type MongoDBUser struct {

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/db/interface.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package db
2+
3+
import (
4+
"context"
5+
)
6+
7+
type Invoke (func(ctx context.Context, uri, username, password string) (Interface, error))
8+
9+
type Interface interface {
10+
Close() error
11+
SetupUser(database string, username string, password string) error
12+
CreateDatabaseIfNotExists(database string) error
13+
}

common/db/mongodb/handler.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package mongodb
2+
3+
import (
4+
"context"
5+
"errors"
6+
"time"
7+
8+
"go.mongodb.org/mongo-driver/bson"
9+
"go.mongodb.org/mongo-driver/bson/primitive"
10+
"go.mongodb.org/mongo-driver/mongo"
11+
"go.mongodb.org/mongo-driver/mongo/options"
12+
"go.mongodb.org/mongo-driver/mongo/readpref"
13+
)
14+
15+
type Roles []Role
16+
type Role struct {
17+
Role string `json:"role" bson:"role"`
18+
DB string `json:"db" bson:"db"`
19+
}
20+
21+
type Users []User
22+
type User struct {
23+
User string `json:"user" bson:"user"`
24+
DB string `json:"db" bson:"db"`
25+
Roles Roles `json:"roles" bson:"roles"`
26+
}
27+
28+
type MongoDBServer struct {
29+
client *mongo.Client
30+
uri string
31+
authenticationDatabase string
32+
}
33+
34+
func NewMongoDBServer(ctx context.Context, uri, username, password string) (*MongoDBServer, error) {
35+
o := options.Client().ApplyURI(uri)
36+
o.SetAuth(options.Credential{
37+
Username: username,
38+
Password: password,
39+
})
40+
41+
client, err := mongo.Connect(ctx, o)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
if err := client.Ping(ctx, readpref.Primary()); err != nil {
47+
return nil, err
48+
}
49+
50+
return &MongoDBServer{
51+
client: client,
52+
uri: uri,
53+
authenticationDatabase: "admin",
54+
}, nil
55+
}
56+
57+
func (m *MongoDBServer) Close() error {
58+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
59+
defer cancel()
60+
return m.client.Disconnect(ctx)
61+
}
62+
63+
func (m *MongoDBServer) SetupUser(database string, username string, password string) error {
64+
doesUserExist, err := m.doesUserExist(database, username)
65+
if err != nil {
66+
return err
67+
}
68+
69+
if !doesUserExist {
70+
if err := m.createUser(database, username, password); err != nil {
71+
return err
72+
}
73+
if doesUserExistNow, err := m.doesUserExist(database, username); err != nil {
74+
return err
75+
} else if !doesUserExistNow {
76+
return errors.New("user doesn't exist after create")
77+
}
78+
} else {
79+
if err := m.updateUserPasswordAndRoles(database, username, password); err != nil {
80+
return err
81+
}
82+
}
83+
84+
return nil
85+
}
86+
87+
func (m *MongoDBServer) doesUserExist(database string, username string) (bool, error) {
88+
users, err := m.getAllUsers(database, username)
89+
if err != nil {
90+
return false, err
91+
}
92+
93+
return users != nil && len(users) > 0, nil
94+
}
95+
96+
func (m *MongoDBServer) getAllUsers(database string, username string) (Users, error) {
97+
users := make(Users, 0)
98+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
99+
defer cancel()
100+
101+
collection := m.client.Database(m.authenticationDatabase).Collection("system.users")
102+
cursor, err := collection.Find(ctx, bson.D{primitive.E{Key: "user", Value: username}, primitive.E{Key: "db", Value: database}})
103+
if err != nil {
104+
return users, err
105+
}
106+
107+
defer cursor.Close(ctx)
108+
109+
for cursor.Next(ctx) {
110+
var user User
111+
if err := cursor.Decode(&user); err != nil {
112+
return users, err
113+
}
114+
users = append(users, user)
115+
}
116+
117+
return users, nil
118+
}
119+
120+
func (m *MongoDBServer) createUser(database string, username string, password string) error {
121+
command := &bson.D{primitive.E{Key: "createUser", Value: username}, primitive.E{Key: "pwd", Value: password},
122+
primitive.E{Key: "roles", Value: []bson.M{{"role": "readWrite", "db": database}}}}
123+
r := m.runCommand(database, command)
124+
if _, err := r.DecodeBytes(); err != nil {
125+
return err
126+
}
127+
return nil
128+
}
129+
130+
func (m *MongoDBServer) updateUserPasswordAndRoles(database string, username string, password string) error {
131+
command := &bson.D{primitive.E{Key: "updateUser", Value: username}, primitive.E{Key: "pwd", Value: password},
132+
primitive.E{Key: "roles", Value: []bson.M{{"role": "readWrite", "db": database}}}}
133+
r := m.runCommand(database, command)
134+
if _, err := r.DecodeBytes(); err != nil {
135+
return err
136+
}
137+
return nil
138+
}
139+
140+
func (m *MongoDBServer) DropUser(database string, username string) error {
141+
command := &bson.D{primitive.E{Key: "dropUser", Value: username}}
142+
r := m.runCommand(database, command)
143+
if _, err := r.DecodeBytes(); err != nil {
144+
return err
145+
}
146+
return nil
147+
}
148+
149+
func (m *MongoDBServer) runCommand(database string, command *bson.D) *mongo.SingleResult {
150+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
151+
defer cancel()
152+
return m.client.Database(database).RunCommand(ctx, *command)
153+
}

0 commit comments

Comments
 (0)