fix team member deprecation (#2072)
This commit is contained in:
		
							parent
							
								
									84fe38a069
								
							
						
					
					
						commit
						ce8b009c66
					
				|  | @ -17,6 +17,8 @@ jobs: | ||||||
|           go-version: "^1.17.4" |           go-version: "^1.17.4" | ||||||
|     - name: Make dependencies |     - name: Make dependencies | ||||||
|       run: make deps mocks |       run: make deps mocks | ||||||
|  |     - name: Code generation | ||||||
|  |       run: make codegen | ||||||
|     - name: Compile |     - name: Compile | ||||||
|       run: make linux |       run: make linux | ||||||
|     - name: Run unit tests |     - name: Run unit tests | ||||||
|  |  | ||||||
|  | @ -250,6 +250,8 @@ class EndToEndTestCase(unittest.TestCase): | ||||||
|         } |         } | ||||||
|         k8s.update_config(enable_postgres_team_crd) |         k8s.update_config(enable_postgres_team_crd) | ||||||
| 
 | 
 | ||||||
|  |         # add team and member to custom-team-membership | ||||||
|  |         # contains already elephant user | ||||||
|         k8s.api.custom_objects_api.patch_namespaced_custom_object( |         k8s.api.custom_objects_api.patch_namespaced_custom_object( | ||||||
|         'acid.zalan.do', 'v1', 'default', |         'acid.zalan.do', 'v1', 'default', | ||||||
|         'postgresteams', 'custom-team-membership', |         'postgresteams', 'custom-team-membership', | ||||||
|  | @ -300,6 +302,13 @@ class EndToEndTestCase(unittest.TestCase): | ||||||
|         self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2, |         self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2, | ||||||
|             "Database role of replaced member in PostgresTeam not renamed", 10, 5) |             "Database role of replaced member in PostgresTeam not renamed", 10, 5) | ||||||
| 
 | 
 | ||||||
|  |         # create fake deletion user so operator fails renaming | ||||||
|  |         # but altering role to NOLOGIN will succeed | ||||||
|  |         create_fake_deletion_user = """ | ||||||
|  |             CREATE USER tester_delete_me NOLOGIN; | ||||||
|  |         """ | ||||||
|  |         self.query_database(leader.metadata.name, "postgres", create_fake_deletion_user) | ||||||
|  | 
 | ||||||
|         # re-add additional member and check if the role is renamed back |         # re-add additional member and check if the role is renamed back | ||||||
|         k8s.api.custom_objects_api.patch_namespaced_custom_object( |         k8s.api.custom_objects_api.patch_namespaced_custom_object( | ||||||
|         'acid.zalan.do', 'v1', 'default', |         'acid.zalan.do', 'v1', 'default', | ||||||
|  | @ -317,11 +326,44 @@ class EndToEndTestCase(unittest.TestCase): | ||||||
|         user_query = """ |         user_query = """ | ||||||
|             SELECT rolname |             SELECT rolname | ||||||
|               FROM pg_catalog.pg_roles |               FROM pg_catalog.pg_roles | ||||||
|              WHERE (rolname = 'kind' AND rolcanlogin) |              WHERE rolname = 'kind' AND rolcanlogin; | ||||||
|                 OR (rolname = 'tester_delete_me' AND NOT rolcanlogin); |         """ | ||||||
|  |         self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 1, | ||||||
|  |             "Database role of recreated member in PostgresTeam not renamed back to original name", 10, 5) | ||||||
|  | 
 | ||||||
|  |         user_query = """ | ||||||
|  |             SELECT rolname | ||||||
|  |               FROM pg_catalog.pg_roles | ||||||
|  |              WHERE rolname IN ('tester','tester_delete_me') AND NOT rolcanlogin; | ||||||
|         """ |         """ | ||||||
|         self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2, |         self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2, | ||||||
|             "Database role of recreated member in PostgresTeam not renamed back to original name", 10, 5) |             "Database role of replaced member in PostgresTeam not denied from login", 10, 5) | ||||||
|  | 
 | ||||||
|  |         # re-add other additional member, operator should grant LOGIN back to tester | ||||||
|  |         # but nothing happens to deleted role | ||||||
|  |         k8s.api.custom_objects_api.patch_namespaced_custom_object( | ||||||
|  |         'acid.zalan.do', 'v1', 'default', | ||||||
|  |         'postgresteams', 'custom-team-membership', | ||||||
|  |         { | ||||||
|  |             'spec': { | ||||||
|  |                 'additionalMembers': { | ||||||
|  |                     'e2e': [ | ||||||
|  |                         'kind', | ||||||
|  |                         'tester' | ||||||
|  |                     ] | ||||||
|  |                 }, | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         user_query = """ | ||||||
|  |             SELECT rolname | ||||||
|  |               FROM pg_catalog.pg_roles | ||||||
|  |              WHERE (rolname IN ('tester', 'kind') | ||||||
|  |                AND rolcanlogin) | ||||||
|  |                 OR (rolname = 'tester_delete_me' AND NOT rolcanlogin); | ||||||
|  |         """ | ||||||
|  |         self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 3, | ||||||
|  |             "Database role of deleted member in PostgresTeam not removed when recreated manually", 10, 5) | ||||||
| 
 | 
 | ||||||
|         # revert config change |         # revert config change | ||||||
|         revert_resync = { |         revert_resync = { | ||||||
|  | @ -1204,8 +1246,9 @@ class EndToEndTestCase(unittest.TestCase): | ||||||
|             self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync") |             self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync") | ||||||
| 
 | 
 | ||||||
|             # node affinity change should cause another rolling update and relocation of replica |             # node affinity change should cause another rolling update and relocation of replica | ||||||
|             k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label) |             k8s.wait_for_pod_failover(master_nodes, 'spilo-role=replica,' + cluster_label) | ||||||
|             k8s.wait_for_pod_start('spilo-role=master,' + cluster_label) |             k8s.wait_for_pod_start('spilo-role=master,' + cluster_label) | ||||||
|  |             k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label) | ||||||
| 
 | 
 | ||||||
|         except timeout_decorator.TimeoutError: |         except timeout_decorator.TimeoutError: | ||||||
|             print('Operator log: {}'.format(k8s.get_operator_log())) |             print('Operator log: {}'.format(k8s.get_operator_log())) | ||||||
|  | @ -1956,4 +1999,4 @@ class EndToEndTestCase(unittest.TestCase): | ||||||
|         return result_set |         return result_set | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     unittest.main() |     unittest.main() | ||||||
|  | @ -231,7 +231,8 @@ func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUser | ||||||
| 			parameters[fields[0]] = fields[1] | 			parameters[fields[0]] = fields[1] | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if strings.HasSuffix(rolname, c.OpConfig.RoleDeletionSuffix) { | 		// consider NOLOGIN roles with deleted suffix as deprecated users
 | ||||||
|  | 		if strings.HasSuffix(rolname, c.OpConfig.RoleDeletionSuffix) && !rolcanlogin { | ||||||
| 			roldeleted = true | 			roldeleted = true | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -104,18 +104,19 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error { | ||||||
| 	if !(c.databaseAccessDisabled() || c.getNumberOfInstances(&newSpec.Spec) <= 0 || c.Spec.StandbyCluster != nil) { | 	if !(c.databaseAccessDisabled() || c.getNumberOfInstances(&newSpec.Spec) <= 0 || c.Spec.StandbyCluster != nil) { | ||||||
| 		c.logger.Debug("syncing roles") | 		c.logger.Debug("syncing roles") | ||||||
| 		if err = c.syncRoles(); err != nil { | 		if err = c.syncRoles(); err != nil { | ||||||
| 			err = fmt.Errorf("could not sync roles: %v", err) | 			// remember all cached users in c.pgUsers
 | ||||||
| 			return err | 			for cachedUserName, cachedUser := range c.pgUsersCache { | ||||||
|  | 				c.pgUsers[cachedUserName] = cachedUser | ||||||
|  | 			} | ||||||
|  | 			c.logger.Errorf("could not sync roles: %v", err) | ||||||
| 		} | 		} | ||||||
| 		c.logger.Debug("syncing databases") | 		c.logger.Debug("syncing databases") | ||||||
| 		if err = c.syncDatabases(); err != nil { | 		if err = c.syncDatabases(); err != nil { | ||||||
| 			err = fmt.Errorf("could not sync databases: %v", err) | 			c.logger.Errorf("could not sync databases: %v", err) | ||||||
| 			return err |  | ||||||
| 		} | 		} | ||||||
| 		c.logger.Debug("syncing prepared databases with schemas") | 		c.logger.Debug("syncing prepared databases with schemas") | ||||||
| 		if err = c.syncPreparedDatabases(); err != nil { | 		if err = c.syncPreparedDatabases(); err != nil { | ||||||
| 			err = fmt.Errorf("could not sync prepared database: %v", err) | 			c.logger.Errorf("could not sync prepared database: %v", err) | ||||||
| 			return err |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -933,10 +934,7 @@ func (c *Cluster) syncRoles() (err error) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// copy map for ProduceSyncRequests to include also system users
 | 	// search also for system users
 | ||||||
| 	for userName, pgUser := range c.pgUsers { |  | ||||||
| 		newUsers[userName] = pgUser |  | ||||||
| 	} |  | ||||||
| 	for _, systemUser := range c.systemUsers { | 	for _, systemUser := range c.systemUsers { | ||||||
| 		userNames = append(userNames, systemUser.Name) | 		userNames = append(userNames, systemUser.Name) | ||||||
| 		newUsers[systemUser.Name] = systemUser | 		newUsers[systemUser.Name] = systemUser | ||||||
|  | @ -950,13 +948,21 @@ func (c *Cluster) syncRoles() (err error) { | ||||||
| 	// update pgUsers where a deleted role was found
 | 	// update pgUsers where a deleted role was found
 | ||||||
| 	// so that they are skipped in ProduceSyncRequests
 | 	// so that they are skipped in ProduceSyncRequests
 | ||||||
| 	for _, dbUser := range dbUsers { | 	for _, dbUser := range dbUsers { | ||||||
| 		if originalUser, exists := deletedUsers[dbUser.Name]; exists { | 		originalUsername, foundDeletedUser := deletedUsers[dbUser.Name] | ||||||
| 			recreatedUser := c.pgUsers[originalUser] | 		// check if original user does not exist in dbUsers
 | ||||||
|  | 		_, originalUserAlreadyExists := dbUsers[originalUsername] | ||||||
|  | 		if foundDeletedUser && !originalUserAlreadyExists { | ||||||
|  | 			recreatedUser := c.pgUsers[originalUsername] | ||||||
| 			recreatedUser.Deleted = true | 			recreatedUser.Deleted = true | ||||||
| 			c.pgUsers[originalUser] = recreatedUser | 			c.pgUsers[originalUsername] = recreatedUser | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// last but not least copy pgUsers to newUsers to send to ProduceSyncRequests
 | ||||||
|  | 	for _, pgUser := range c.pgUsers { | ||||||
|  | 		newUsers[pgUser.Name] = pgUser | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	pgSyncRequests := c.userSyncStrategy.ProduceSyncRequests(dbUsers, newUsers) | 	pgSyncRequests := c.userSyncStrategy.ProduceSyncRequests(dbUsers, newUsers) | ||||||
| 	if err = c.userSyncStrategy.ExecuteSyncRequests(pgSyncRequests, c.pgDb); err != nil { | 	if err = c.userSyncStrategy.ExecuteSyncRequests(pgSyncRequests, c.pgDb); err != nil { | ||||||
| 		return fmt.Errorf("error executing sync statements: %v", err) | 		return fmt.Errorf("error executing sync statements: %v", err) | ||||||
|  |  | ||||||
|  | @ -43,7 +43,8 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM | ||||||
| 
 | 
 | ||||||
| 	var reqs []spec.PgSyncUserRequest | 	var reqs []spec.PgSyncUserRequest | ||||||
| 	for name, newUser := range newUsers { | 	for name, newUser := range newUsers { | ||||||
| 		// do not create user that exists in DB with deletion suffix
 | 		// do not create user when there exists a user with the same name plus deletion suffix
 | ||||||
|  | 		// instead request a renaming of the deleted user back to the original name (see * below)
 | ||||||
| 		if newUser.Deleted { | 		if newUser.Deleted { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  | @ -82,22 +83,28 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// No existing roles are deleted or stripped of role membership/flags
 | 	// no existing roles are deleted or stripped of role membership/flags
 | ||||||
| 	// but team roles will be renamed and denied from LOGIN
 | 	// but team roles will be renamed and denied from LOGIN
 | ||||||
| 	for name, dbUser := range dbUsers { | 	for name, dbUser := range dbUsers { | ||||||
| 		if _, exists := newUsers[name]; !exists { | 		if _, exists := newUsers[name]; !exists { | ||||||
| 			// toggle LOGIN flag based on role deletion
 |  | ||||||
| 			userFlags := make([]string, len(dbUser.Flags)) |  | ||||||
| 			userFlags = append(userFlags, dbUser.Flags...) |  | ||||||
| 			if dbUser.Deleted { | 			if dbUser.Deleted { | ||||||
| 				dbUser.Flags = util.StringSliceReplaceElement(dbUser.Flags, constants.RoleFlagNoLogin, constants.RoleFlagLogin) | 				// * user with deletion suffix and NOLOGIN found in database
 | ||||||
|  | 				// grant back LOGIN and rename only if original user is wanted and does not exist in database
 | ||||||
|  | 				originalName := strings.TrimSuffix(name, strategy.RoleDeletionSuffix) | ||||||
|  | 				_, originalUserWanted := newUsers[originalName] | ||||||
|  | 				_, originalUserAlreadyExists := dbUsers[originalName] | ||||||
|  | 				if !originalUserWanted || originalUserAlreadyExists { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 				// a deleted dbUser has no NOLOGIN flag, so we can add the LOGIN flag
 | ||||||
|  | 				dbUser.Flags = append(dbUser.Flags, constants.RoleFlagLogin) | ||||||
| 			} else { | 			} else { | ||||||
|  | 				// user found in database and not wanted in newUsers - replace LOGIN flag with NOLOGIN
 | ||||||
| 				dbUser.Flags = util.StringSliceReplaceElement(dbUser.Flags, constants.RoleFlagLogin, constants.RoleFlagNoLogin) | 				dbUser.Flags = util.StringSliceReplaceElement(dbUser.Flags, constants.RoleFlagLogin, constants.RoleFlagNoLogin) | ||||||
| 			} | 			} | ||||||
| 			if !util.IsEqualIgnoreOrder(userFlags, dbUser.Flags) { | 			// request ALTER ROLE to grant or revoke LOGIN
 | ||||||
| 				reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGsyncUserAlter, User: dbUser}) | 			reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGsyncUserAlter, User: dbUser}) | ||||||
| 			} | 			// request RENAME which will happen on behalf of the pgUser.Deleted field
 | ||||||
| 
 |  | ||||||
| 			reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncUserRename, User: dbUser}) | 			reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncUserRename, User: dbUser}) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -62,6 +62,8 @@ var substractTest = []struct { | ||||||
| }{ | }{ | ||||||
| 	{[]string{"a", "b", "c", "d"}, []string{"a", "b", "c", "d"}, []string{}, true}, | 	{[]string{"a", "b", "c", "d"}, []string{"a", "b", "c", "d"}, []string{}, true}, | ||||||
| 	{[]string{"a", "b", "c", "d"}, []string{"a", "bb", "c", "d"}, []string{"b"}, false}, | 	{[]string{"a", "b", "c", "d"}, []string{"a", "bb", "c", "d"}, []string{"b"}, false}, | ||||||
|  | 	{[]string{""}, []string{"b"}, []string{""}, false}, | ||||||
|  | 	{[]string{"a"}, []string{""}, []string{"a"}, false}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var sliceContaintsTest = []struct { | var sliceContaintsTest = []struct { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue