@@ -57,6 +57,15 @@ const (
57
57
defaultClusterDomain = "cluster.local"
58
58
)
59
59
60
+ // Connection string options that should be ignored as they are set through other means.
61
+ var (
62
+ protectedConnectionStringOptions = map [string ]struct {}{
63
+ "replicaSet" : {},
64
+ "ssl" : {},
65
+ "tls" : {},
66
+ }
67
+ )
68
+
60
69
// MongoDBCommunitySpec defines the desired state of MongoDB
61
70
type MongoDBCommunitySpec struct {
62
71
// Members is the number of members in the replica set
@@ -119,6 +128,69 @@ type MongoDBCommunitySpec struct {
119
128
// Prometheus configurations.
120
129
// +optional
121
130
Prometheus * Prometheus `json:"prometheus,omitempty"`
131
+
132
+ // Additional options to be appended to the connection string. These options apply to the entire resource and to each user.
133
+ // +kubebuilder:validation:Type=object
134
+ // +optional
135
+ // +kubebuilder:pruning:PreserveUnknownFields
136
+ // +nullable
137
+ AdditionalConnectionStringConfig MapWrapper `json:"additionalConnectionStringConfig,omitempty"`
138
+ }
139
+
140
+ // Wrapper for a map to be used by other structs.
141
+ // The CRD generator does not support map[string]interface{}
142
+ // on the top level and hence we need to work around this with
143
+ // a wrapping struct.
144
+ type MapWrapper struct {
145
+ Object map [string ]interface {} `json:"-"`
146
+ }
147
+
148
+ // MarshalJSON defers JSON encoding to the wrapped map
149
+ func (m * MapWrapper ) MarshalJSON () ([]byte , error ) {
150
+ return json .Marshal (m .Object )
151
+ }
152
+
153
+ // UnmarshalJSON will decode the data into the wrapped map
154
+ func (m * MapWrapper ) UnmarshalJSON (data []byte ) error {
155
+ if m .Object == nil {
156
+ m .Object = map [string ]interface {}{}
157
+ }
158
+
159
+ // Handle keys like net.port to be set as nested maps.
160
+ // Without this after unmarshalling there is just key "net.port" which is not
161
+ // a nested map and methods like GetPort() cannot access the value.
162
+ tmpMap := map [string ]interface {}{}
163
+ err := json .Unmarshal (data , & tmpMap )
164
+ if err != nil {
165
+ return err
166
+ }
167
+
168
+ for k , v := range tmpMap {
169
+ m .SetOption (k , v )
170
+ }
171
+
172
+ return nil
173
+ }
174
+
175
+ func (m * MapWrapper ) DeepCopy () * MapWrapper {
176
+ if m != nil && m .Object != nil {
177
+ return & MapWrapper {
178
+ Object : runtime .DeepCopyJSON (m .Object ),
179
+ }
180
+ }
181
+ c := NewMapWrapper ()
182
+ return & c
183
+ }
184
+
185
+ // NewMapWrapper returns an empty MapWrapper
186
+ func NewMapWrapper () MapWrapper {
187
+ return MapWrapper {Object : map [string ]interface {}{}}
188
+ }
189
+
190
+ // SetOption updated the MapWrapper with a new option
191
+ func (m MapWrapper ) SetOption (key string , value interface {}) MapWrapper {
192
+ m .Object = objx .New (m .Object ).Set (key , value )
193
+ return m
122
194
}
123
195
124
196
// ReplicaSetHorizonConfiguration holds the split horizon DNS settings for
@@ -277,10 +349,10 @@ type LogLevel string
277
349
278
350
const (
279
351
LogLevelDebug LogLevel = "DEBUG"
280
- LogLevelInfo = "INFO"
281
- LogLevelWarn = "WARN"
282
- LogLevelError = "ERROR"
283
- LogLevelFatal = "FATAL"
352
+ LogLevelInfo string = "INFO"
353
+ LogLevelWarn string = "WARN"
354
+ LogLevelError string = "ERROR"
355
+ LogLevelFatal string = "FATAL"
284
356
)
285
357
286
358
type AgentConfiguration struct {
@@ -315,60 +387,13 @@ func (m *StatefulSetSpecWrapper) DeepCopy() *StatefulSetSpecWrapper {
315
387
316
388
// MongodConfiguration holds the optional mongod configuration
317
389
// that should be merged with the operator created one.
318
- //
319
- // The CRD generator does not support map[string]interface{}
320
- // on the top level and hence we need to work around this with
321
- // a wrapping struct.
322
390
type MongodConfiguration struct {
323
- Object map [string ]interface {} `json:"-"`
324
- }
325
-
326
- // MarshalJSON defers JSON encoding to the wrapped map
327
- func (m * MongodConfiguration ) MarshalJSON () ([]byte , error ) {
328
- return json .Marshal (m .Object )
329
- }
330
-
331
- // UnmarshalJSON will decode the data into the wrapped map
332
- func (m * MongodConfiguration ) UnmarshalJSON (data []byte ) error {
333
- if m .Object == nil {
334
- m .Object = map [string ]interface {}{}
335
- }
336
-
337
- // Handle keys like net.port to be set as nested maps.
338
- // Without this after unmarshalling there is just key "net.port" which is not
339
- // a nested map and methods like GetPort() cannot access the value.
340
- tmpMap := map [string ]interface {}{}
341
- err := json .Unmarshal (data , & tmpMap )
342
- if err != nil {
343
- return err
344
- }
345
-
346
- for k , v := range tmpMap {
347
- m .SetOption (k , v )
348
- }
349
-
350
- return nil
351
- }
352
-
353
- func (m * MongodConfiguration ) DeepCopy () * MongodConfiguration {
354
- if m != nil && m .Object != nil {
355
- return & MongodConfiguration {
356
- Object : runtime .DeepCopyJSON (m .Object ),
357
- }
358
- }
359
- c := NewMongodConfiguration ()
360
- return & c
391
+ MapWrapper `json:"-"`
361
392
}
362
393
363
394
// NewMongodConfiguration returns an empty MongodConfiguration
364
395
func NewMongodConfiguration () MongodConfiguration {
365
- return MongodConfiguration {Object : map [string ]interface {}{}}
366
- }
367
-
368
- // SetOption updated the MongodConfiguration with a new option
369
- func (m MongodConfiguration ) SetOption (key string , value interface {}) MongodConfiguration {
370
- m .Object = objx .New (m .Object ).Set (key , value )
371
- return m
396
+ return MongodConfiguration {MapWrapper {map [string ]interface {}{}}}
372
397
}
373
398
374
399
// GetDBDataDir returns the db path which should be used.
@@ -424,6 +449,14 @@ type MongoDBUser struct {
424
449
// If provided, this secret must be different for each user in a deployment.
425
450
// +optional
426
451
ConnectionStringSecretName string `json:"connectionStringSecretName"`
452
+
453
+ // Additional options to be appended to the connection string.
454
+ // These options apply only to this user and will override any existing options in the resource.
455
+ // +kubebuilder:validation:Type=object
456
+ // +optional
457
+ // +kubebuilder:pruning:PreserveUnknownFields
458
+ // +nullable
459
+ AdditionalConnectionStringConfig MapWrapper `json:"additionalConnectionStringConfig,omitempty"`
427
460
}
428
461
429
462
func (m MongoDBUser ) GetPasswordSecretKey () string {
@@ -692,6 +725,7 @@ func (m *MongoDBCommunity) GetScramUsers() []scram.User {
692
725
PasswordSecretName : u .PasswordSecretRef .Name ,
693
726
ScramCredentialsSecretName : u .GetScramCredentialsSecretName (),
694
727
ConnectionStringSecretName : u .GetConnectionStringSecretName (m .Name ),
728
+ ConnectionStringOptions : u .AdditionalConnectionStringConfig .Object ,
695
729
}
696
730
}
697
731
return users
@@ -736,29 +770,96 @@ func (m *MongoDBCommunity) AutomationConfigArbitersThisReconciliation() int {
736
770
})
737
771
}
738
772
773
+ // GetOptionsString return a string format of the connection string
774
+ // options that can be appended directly to the connection string.
775
+ //
776
+ // Only takes into account options for the resource and not any user.
777
+ func (m * MongoDBCommunity ) GetOptionsString () string {
778
+ generalOptionsMap := m .Spec .AdditionalConnectionStringConfig .Object
779
+ optionValues := make ([]string , len (generalOptionsMap ))
780
+ i := 0
781
+
782
+ for key , value := range generalOptionsMap {
783
+ if _ , protected := protectedConnectionStringOptions [key ]; ! protected {
784
+ optionValues [i ] = fmt .Sprintf ("%s=%v" , key , value )
785
+ i += 1
786
+ }
787
+ }
788
+
789
+ optionValues = optionValues [:i ]
790
+
791
+ optionsString := ""
792
+ if i > 0 {
793
+ optionsString = "&" + strings .Join (optionValues , "&" )
794
+ }
795
+ return optionsString
796
+ }
797
+
798
+ // GetUserOptionsString return a string format of the connection string
799
+ // options that can be appended directly to the connection string.
800
+ //
801
+ // Takes into account both user options and resource options.
802
+ // User options will override any existing options in the resource.
803
+ func (m * MongoDBCommunity ) GetUserOptionsString (user scram.User ) string {
804
+ generalOptionsMap := m .Spec .AdditionalConnectionStringConfig .Object
805
+ userOptionsMap := user .ConnectionStringOptions
806
+ optionValues := make ([]string , len (generalOptionsMap )+ len (userOptionsMap ))
807
+ i := 0
808
+ for key , value := range userOptionsMap {
809
+ if _ , protected := protectedConnectionStringOptions [key ]; ! protected {
810
+ optionValues [i ] = fmt .Sprintf ("%s=%v" , key , value )
811
+ i += 1
812
+ }
813
+ }
814
+
815
+ for key , value := range generalOptionsMap {
816
+ _ , ok := userOptionsMap [key ]
817
+ if _ , protected := protectedConnectionStringOptions [key ]; ! ok && ! protected {
818
+ optionValues [i ] = fmt .Sprintf ("%s=%v" , key , value )
819
+ i += 1
820
+ }
821
+ }
822
+
823
+ optionValues = optionValues [:i ]
824
+
825
+ optionsString := ""
826
+ if i > 0 {
827
+ optionsString = "&" + strings .Join (optionValues , "&" )
828
+ }
829
+ return optionsString
830
+ }
831
+
739
832
// MongoURI returns a mongo uri which can be used to connect to this deployment
740
833
func (m * MongoDBCommunity ) MongoURI (clusterDomain string ) string {
741
- return fmt .Sprintf ("mongodb://%s/?replicaSet=%s" , strings .Join (m .Hosts (clusterDomain ), "," ), m .Name )
834
+ optionsString := m .GetOptionsString ()
835
+
836
+ return fmt .Sprintf ("mongodb://%s/?replicaSet=%s%s" , strings .Join (m .Hosts (clusterDomain ), "," ), m .Name , optionsString )
742
837
}
743
838
744
839
// MongoSRVURI returns a mongo srv uri which can be used to connect to this deployment
745
840
func (m * MongoDBCommunity ) MongoSRVURI (clusterDomain string ) string {
746
841
if clusterDomain == "" {
747
842
clusterDomain = defaultClusterDomain
748
843
}
749
- return fmt .Sprintf ("mongodb+srv://%s.%s.svc.%s/?replicaSet=%s" , m .ServiceName (), m .Namespace , clusterDomain , m .Name )
844
+
845
+ optionsString := m .GetOptionsString ()
846
+
847
+ return fmt .Sprintf ("mongodb+srv://%s.%s.svc.%s/?replicaSet=%s%s" , m .ServiceName (), m .Namespace , clusterDomain , m .Name , optionsString )
750
848
}
751
849
752
850
// MongoAuthUserURI returns a mongo uri which can be used to connect to this deployment
753
851
// and includes the authentication data for the user
754
852
func (m * MongoDBCommunity ) MongoAuthUserURI (user scram.User , password string , clusterDomain string ) string {
755
- return fmt .Sprintf ("mongodb://%s:%s@%s/%s?replicaSet=%s&ssl=%t" ,
853
+ optionsString := m .GetUserOptionsString (user )
854
+
855
+ return fmt .Sprintf ("mongodb://%s:%s@%s/%s?replicaSet=%s&ssl=%t%s" ,
756
856
url .QueryEscape (user .Username ),
757
857
url .QueryEscape (password ),
758
858
strings .Join (m .Hosts (clusterDomain ), "," ),
759
859
user .Database ,
760
860
m .Name ,
761
- m .Spec .Security .TLS .Enabled )
861
+ m .Spec .Security .TLS .Enabled ,
862
+ optionsString )
762
863
}
763
864
764
865
// MongoAuthUserSRVURI returns a mongo srv uri which can be used to connect to this deployment
@@ -767,15 +868,19 @@ func (m *MongoDBCommunity) MongoAuthUserSRVURI(user scram.User, password string,
767
868
if clusterDomain == "" {
768
869
clusterDomain = defaultClusterDomain
769
870
}
770
- return fmt .Sprintf ("mongodb+srv://%s:%s@%s.%s.svc.%s/%s?replicaSet=%s&ssl=%t" ,
871
+
872
+ optionsString := m .GetUserOptionsString (user )
873
+
874
+ return fmt .Sprintf ("mongodb+srv://%s:%s@%s.%s.svc.%s/%s?replicaSet=%s&ssl=%t%s" ,
771
875
url .QueryEscape (user .Username ),
772
876
url .QueryEscape (password ),
773
877
m .ServiceName (),
774
878
m .Namespace ,
775
879
clusterDomain ,
776
880
user .Database ,
777
881
m .Name ,
778
- m .Spec .Security .TLS .Enabled )
882
+ m .Spec .Security .TLS .Enabled ,
883
+ optionsString )
779
884
}
780
885
781
886
func (m * MongoDBCommunity ) Hosts (clusterDomain string ) []string {
0 commit comments