@@ -27,10 +27,11 @@ import (
2727 "github.com/kloeckner-i/db-operator/pkg/utils/kci"
2828 "github.com/sirupsen/logrus"
2929 v1 "k8s.io/api/core/v1"
30+ "k8s.io/utils/strings/slices"
3031)
3132
32- // ConnectionStringFields defines default fields that can be used to generate a connection string
33- type ConnectionStringFields struct {
33+ // SecretsTemplatesFields defines default fields that can be used to generate secrets with db creds
34+ type SecretsTemplatesFields struct {
3435 Protocol string
3536 DatabaseHost string
3637 DatabasePort int32
@@ -39,6 +40,19 @@ type ConnectionStringFields struct {
3940 DatabaseName string
4041}
4142
43+ const (
44+ fieldPostgresDB = "POSTGRES_DB"
45+ fieldPostgresUser = "POSTGRES_USER"
46+ fieldPostgressPassword = "POSTGRES_PASSWORD"
47+ fieldMysqlDB = "DB"
48+ fieldMysqlUser = "USER"
49+ fieldMysqlPassword = "PASSWORD"
50+ )
51+
52+ func getBlockedTempatedKeys () []string {
53+ return []string {fieldMysqlDB , fieldMysqlPassword , fieldMysqlUser , fieldPostgresDB , fieldPostgresUser , fieldPostgressPassword }
54+ }
55+
4256func determinDatabaseType (dbcr * kciv1alpha1.Database , dbCred database.Credentials ) (database.Database , error ) {
4357 instance , err := dbcr .GetInstanceRef ()
4458 if err != nil {
@@ -110,18 +124,43 @@ func determinDatabaseType(dbcr *kciv1alpha1.Database, dbCred database.Credential
110124 }
111125}
112126
113- func parseDatabaseSecretData (dbcr * kciv1alpha1.Database , data map [string ][]byte ) (database.Credentials , error ) {
114- cred := database.Credentials {}
115- engine , err := dbcr .GetEngineType ()
127+ func parseTemplatedSecretsData (dbcr * kciv1alpha1.Database , data map [string ][]byte , useLegacyConnStr bool ) (database.Credentials , error ) {
128+ cred , err := parseDatabaseSecretData (dbcr , data )
116129 if err != nil {
117130 return cred , err
118131 }
132+ cred .TemplatedSecrets = map [string ]string {}
119133
120- // Connection string can be empty
121- if connectionString , ok := data ["CONNECTION_STRING" ]; ok {
122- cred .ConnectionString = string (connectionString )
134+ if useLegacyConnStr {
135+ if connectionString , ok := data ["CONNECTION_STRING" ]; ok {
136+ cred .TemplatedSecrets ["CONNECTION_STRING" ] = string (connectionString )
137+ } else {
138+ logrus .Infof ("DB: namespace=%s, name=%s CONNECTION_STRING key does not exist in the secret data" , dbcr .Namespace , dbcr .Name )
139+ }
123140 } else {
124- logrus .Info ("CONNECTION_STRING key does not exist in secret data" )
141+ for key := range dbcr .Spec .SecretsTemplates {
142+ // Here we can see if there are obsolete entries in the secret data
143+ if secret , ok := data [key ]; ok {
144+ delete (data , key )
145+ cred .TemplatedSecrets [key ] = string (secret )
146+ } else {
147+ logrus .Infof ("DB: namespace=%s, name=%s %s key does not exist in secret data" ,
148+ dbcr .Namespace ,
149+ dbcr .Name ,
150+ key ,
151+ )
152+ }
153+ }
154+ }
155+
156+ return cred , nil
157+ }
158+
159+ func parseDatabaseSecretData (dbcr * kciv1alpha1.Database , data map [string ][]byte ) (database.Credentials , error ) {
160+ cred := database.Credentials {}
161+ engine , err := dbcr .GetEngineType ()
162+ if err != nil {
163+ return cred , err
125164 }
126165
127166 switch engine {
@@ -211,7 +250,7 @@ func generateConnectionString(dbcr *kciv1alpha1.Database, databaseCred database.
211250 // "postgresql://user:password@host:port/database"
212251 const defaultTemplate = "{{ .Protocol }}://{{ .UserName }}:{{ .Password }}@{{ .DatabaseHost }}:{{ .DatabasePort }}/{{ .DatabaseName }}"
213252
214- dbData := ConnectionStringFields {
253+ dbData := SecretsTemplatesFields {
215254 DatabaseHost : dbcr .Status .ProxyStatus .ServiceName ,
216255 DatabasePort : dbcr .Status .ProxyStatus .SQLPort ,
217256 UserName : databaseCred .Username ,
@@ -262,7 +301,99 @@ func generateConnectionString(dbcr *kciv1alpha1.Database, databaseCred database.
262301 return
263302}
264303
304+ func generateTemplatedSecrets (dbcr * kciv1alpha1.Database , databaseCred database.Credentials ) (secrets map [string ]string , err error ) {
305+ secrets = map [string ]string {}
306+ templates := map [string ]string {}
307+ if len (dbcr .Spec .SecretsTemplates ) > 0 {
308+ templates = dbcr .Spec .SecretsTemplates
309+ } else {
310+ const tmpl = "{{ .Protocol }}://{{ .UserName }}:{{ .Password }}@{{ .DatabaseHost }}:{{ .DatabasePort }}/{{ .DatabaseName }}"
311+ templates ["CONNECTION_STRING" ] = tmpl
312+ }
313+ // The string that's going to be generated if the default template is used:
314+ // "postgresql://user:password@host:port/database"
315+ dbData := SecretsTemplatesFields {
316+ DatabaseHost : dbcr .Status .ProxyStatus .ServiceName ,
317+ DatabasePort : dbcr .Status .ProxyStatus .SQLPort ,
318+ UserName : databaseCred .Username ,
319+ Password : databaseCred .Password ,
320+ DatabaseName : databaseCred .Name ,
321+ }
322+
323+ // If proxy is not used, set a real database address
324+ if ! dbcr .Status .ProxyStatus .Status {
325+ db , err := determinDatabaseType (dbcr , databaseCred )
326+ if err != nil {
327+ return nil , err
328+ }
329+ dbAddress := db .GetDatabaseAddress ()
330+ dbData .DatabaseHost = dbAddress .Host
331+ dbData .DatabasePort = int32 (dbAddress .Port )
332+ }
333+ // If engine is 'postgres', the protocol should be postgresql
334+ if dbcr .Status .InstanceRef .Spec .Engine == "postgres" {
335+ dbData .Protocol = "postgresql"
336+ } else {
337+ dbData .Protocol = dbcr .Status .InstanceRef .Spec .Engine
338+ }
339+
340+ logrus .Infof ("DB: namespace=%s, name=%s creating secrets from templates" , dbcr .Namespace , dbcr .Name )
341+ for key , value := range templates {
342+ var tmpl string = value
343+ t , err := template .New ("secret" ).Parse (tmpl )
344+ if err != nil {
345+ return nil , err
346+ }
347+
348+ var secretBytes bytes.Buffer
349+ err = t .Execute (& secretBytes , dbData )
350+ if err != nil {
351+ return nil , err
352+ }
353+ connString := secretBytes .String ()
354+ secrets [key ] = connString
355+ }
356+ return secrets , nil
357+ }
358+
359+ func fillTemplatedSecretData (dbcr * kciv1alpha1.Database , secretData map [string ][]byte , newSecretFields map [string ]string ) (newSecret * v1.Secret ) {
360+ blockedTempatedKeys := getBlockedTempatedKeys ()
361+ for key , value := range newSecretFields {
362+ if slices .Contains (blockedTempatedKeys , key ) {
363+ logrus .Warnf ("DB: namespace=%s, name=%s %s can't be used for templating, because it's used for default secret created by operator" ,
364+ dbcr .Namespace ,
365+ dbcr .Name ,
366+ key ,
367+ )
368+ } else {
369+ newSecret = addTemplatedSecretToSecret (dbcr , secretData , key , value )
370+ }
371+ }
372+ return
373+ }
374+
265375func addConnectionStringToSecret (dbcr * kciv1alpha1.Database , secretData map [string ][]byte , connectionString string ) * v1.Secret {
266376 secretData ["CONNECTION_STRING" ] = []byte (connectionString )
267377 return kci .SecretBuilder (dbcr .Spec .SecretName , dbcr .GetNamespace (), secretData )
268378}
379+
380+ func addTemplatedSecretToSecret (dbcr * kciv1alpha1.Database , secretData map [string ][]byte , secretName string , secretValue string ) * v1.Secret {
381+ secretData [secretName ] = []byte (secretValue )
382+ return kci .SecretBuilder (dbcr .Spec .SecretName , dbcr .GetNamespace (), secretData )
383+ }
384+
385+ func removeObsoleteSecret (dbcr * kciv1alpha1.Database , secretData map [string ][]byte , newSecretFields map [string ]string ) * v1.Secret {
386+ blockedTempatedKeys := getBlockedTempatedKeys ()
387+
388+ for key := range secretData {
389+ if _ , ok := newSecretFields [key ]; ! ok {
390+ // Check if is a untemplatead secret, so it's not removed accidentally
391+ if ! slices .Contains (blockedTempatedKeys , key ) {
392+ logrus .Infof ("DB: namespace=%s, name=%s removing an obsolete field: %s" , dbcr .Namespace , dbcr .Name , key )
393+ delete (secretData , key )
394+ }
395+ }
396+ }
397+
398+ return kci .SecretBuilder (dbcr .Spec .SecretName , dbcr .GetNamespace (), secretData )
399+ }
0 commit comments