@@ -20,6 +20,7 @@ const (
2020 alterRoleSetSQL = `ALTER ROLE "%s" SET %s TO %s`
2121 dropUserSQL = `SET LOCAL synchronous_commit = 'local'; DROP ROLE "%s";`
2222 grantToUserSQL = `GRANT %s TO "%s"`
23+ revokeFromUserSQL = `REVOKE %s FROM %s`
2324 doBlockStmt = `SET LOCAL synchronous_commit = 'local'; DO $$ BEGIN %s; END;$$;`
2425 passwordTemplate = "ENCRYPTED PASSWORD '%s'"
2526 inRoleTemplate = `IN ROLE %s`
@@ -31,8 +32,9 @@ const (
3132// an existing roles of another role membership, nor it removes the already assigned flag
3233// (except for the NOLOGIN). TODO: process other NOflags, i.e. NOSUPERUSER correctly.
3334type DefaultUserSyncStrategy struct {
34- PasswordEncryption string
35- RoleDeletionSuffix string
35+ PasswordEncryption string
36+ RoleDeletionSuffix string
37+ AdditionalOwnerRoles []string
3638}
3739
3840// ProduceSyncRequests figures out the types of changes that need to happen with the given users.
@@ -53,30 +55,27 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM
5355 }
5456 } else {
5557 r := spec.PgSyncUserRequest {}
56- r .User = dbUser
5758 newMD5Password := util .NewEncryptor (strategy .PasswordEncryption ).PGUserPassword (newUser )
5859
5960 // do not compare for roles coming from docker image
60- if newUser .Origin != spec .RoleOriginSpilo {
61- if dbUser .Password != newMD5Password {
62- r .User .Password = newMD5Password
63- r .Kind = spec .PGsyncUserAlter
64- }
65- if addNewFlags , equal := util .SubstractStringSlices (newUser .Flags , dbUser .Flags ); ! equal {
66- r .User .Flags = addNewFlags
67- r .Kind = spec .PGsyncUserAlter
68- }
61+ if dbUser .Password != newMD5Password {
62+ r .User .Password = newMD5Password
63+ r .Kind = spec .PGsyncUserAlter
6964 }
7065 if addNewRoles , equal := util .SubstractStringSlices (newUser .MemberOf , dbUser .MemberOf ); ! equal {
7166 r .User .MemberOf = addNewRoles
67+ r .User .IsDbOwner = newUser .IsDbOwner
68+ r .Kind = spec .PGsyncUserAlter
69+ }
70+ if addNewFlags , equal := util .SubstractStringSlices (newUser .Flags , dbUser .Flags ); ! equal {
71+ r .User .Flags = addNewFlags
7272 r .Kind = spec .PGsyncUserAlter
7373 }
7474 if r .Kind == spec .PGsyncUserAlter {
7575 r .User .Name = newUser .Name
7676 reqs = append (reqs , r )
7777 }
78- if newUser .Origin != spec .RoleOriginSpilo &&
79- len (newUser .Parameters ) > 0 &&
78+ if len (newUser .Parameters ) > 0 &&
8079 ! reflect .DeepEqual (dbUser .Parameters , newUser .Parameters ) {
8180 reqs = append (reqs , spec.PgSyncUserRequest {Kind : spec .PGSyncAlterSet , User : newUser })
8281 }
@@ -120,6 +119,15 @@ func (strategy DefaultUserSyncStrategy) ExecuteSyncRequests(requests []spec.PgSy
120119 if err := strategy .alterPgUser (request .User , db ); err != nil {
121120 reqretries = append (reqretries , request )
122121 errors = append (errors , fmt .Sprintf ("could not alter user %q: %v" , request .User .Name , err ))
122+ // XXX: we do not allow additional owner roles to be members of database owners
123+ // if ALTER fails it could be because of the wrong memberhip (check #1862 for details)
124+ // so in any case try to revoke the database owner from the additional owner roles
125+ // the initial ALTER statement will be retried once and should work then
126+ if request .User .IsDbOwner && len (strategy .AdditionalOwnerRoles ) > 0 {
127+ if err := resolveOwnerMembership (request .User , strategy .AdditionalOwnerRoles , db ); err != nil {
128+ errors = append (errors , fmt .Sprintf ("could not resolve owner membership for %q: %v" , request .User .Name , err ))
129+ }
130+ }
123131 }
124132 case spec .PGSyncAlterSet :
125133 if err := strategy .alterPgUserSet (request .User , db ); err != nil {
@@ -152,6 +160,21 @@ func (strategy DefaultUserSyncStrategy) ExecuteSyncRequests(requests []spec.PgSy
152160 return nil
153161}
154162
163+ func resolveOwnerMembership (dbOwner spec.PgUser , additionalOwners []string , db * sql.DB ) error {
164+ errors := make ([]string , 0 )
165+ for _ , additionalOwner := range additionalOwners {
166+ if err := revokeRole (dbOwner .Name , additionalOwner , db ); err != nil {
167+ errors = append (errors , fmt .Sprintf ("could not revoke %q from %q: %v" , dbOwner .Name , additionalOwner , err ))
168+ }
169+ }
170+
171+ if len (errors ) > 0 {
172+ return fmt .Errorf ("could not resolve membership between %q and additional owner roles: %v" , dbOwner .Name , strings .Join (errors , `', '` ))
173+ }
174+
175+ return nil
176+ }
177+
155178func (strategy DefaultUserSyncStrategy ) alterPgUserSet (user spec.PgUser , db * sql.DB ) error {
156179 queries := produceAlterRoleSetStmts (user )
157180 query := fmt .Sprintf (doBlockStmt , strings .Join (queries , ";" ))
@@ -272,6 +295,16 @@ func quoteMemberList(user spec.PgUser) string {
272295 return strings .Join (memberof , "," )
273296}
274297
298+ func revokeRole (groupRole , role string , db * sql.DB ) error {
299+ revokeStmt := fmt .Sprintf (revokeFromUserSQL , groupRole , role )
300+
301+ if _ , err := db .Exec (fmt .Sprintf (doBlockStmt , revokeStmt )); err != nil {
302+ return err
303+ }
304+
305+ return nil
306+ }
307+
275308// quoteVal quotes values to be used at ALTER ROLE SET param = value if necessary
276309func quoteParameterValue (name , val string ) string {
277310 start := val [0 ]
0 commit comments