import logging import json from data import model logger = logging.getLogger(__name__) MAX_TEAMS_PER_ITERATION = 500 def sync_teams_to_groups(authentication, stale_cutoff): """ Performs team syncing by looking up any stale team(s) found, and performing the sync operation on them. """ logger.debug("Looking up teams to sync to groups") sync_team_tried = set() while len(sync_team_tried) < MAX_TEAMS_PER_ITERATION: # Find a stale team. stale_team_sync = model.team.get_stale_team(stale_cutoff) if not stale_team_sync: logger.debug("No additional stale team found; sleeping") return # Make sure we don't try to reprocess a team on this iteration. if stale_team_sync.id in sync_team_tried: break sync_team_tried.add(stale_team_sync.id) # Sync the team. sync_successful = sync_team(authentication, stale_team_sync) if not sync_successful: return def sync_team(authentication, stale_team_sync): """ Performs synchronization of a team (as referenced by the TeamSync stale_team_sync). Returns True on success and False otherwise. """ sync_config = json.loads(stale_team_sync.config) logger.info( "Syncing team `%s` under organization %s via %s (#%s)", stale_team_sync.team.name, stale_team_sync.team.organization.username, sync_config, stale_team_sync.team_id, extra={"team": stale_team_sync.team_id, "sync_config": sync_config}, ) # Load all the existing members of the team in Quay that are bound to the auth service. existing_users = model.team.get_federated_team_member_mapping( stale_team_sync.team, authentication.federated_service ) logger.debug( "Existing membership of %s for team `%s` under organization %s via %s (#%s)", len(existing_users), stale_team_sync.team.name, stale_team_sync.team.organization.username, sync_config, stale_team_sync.team_id, extra={ "team": stale_team_sync.team_id, "sync_config": sync_config, "existing_member_count": len(existing_users), }, ) # Load all the members of the team from the authenication system. (member_iterator, err) = authentication.iterate_group_members(sync_config) if err is not None: logger.error( "Got error when trying to iterate group members with config %s: %s", sync_config, err ) return False # Collect all the members currently found in the group, adding them to the team as we go # along. group_membership = set() for (member_info, err) in member_iterator: if err is not None: logger.error("Got error when trying to construct a member: %s", err) continue # If the member is already in the team, nothing more to do. if member_info.username in existing_users: logger.debug( "Member %s already in team `%s` under organization %s via %s (#%s)", member_info.username, stale_team_sync.team.name, stale_team_sync.team.organization.username, sync_config, stale_team_sync.team_id, extra={ "team": stale_team_sync.team_id, "sync_config": sync_config, "member": member_info.username, }, ) group_membership.add(existing_users[member_info.username]) continue # Retrieve the Quay user associated with the member info. (quay_user, err) = authentication.get_and_link_federated_user_info( member_info, internal_create=True ) if err is not None: logger.error( "Could not link external user %s to an internal user: %s", member_info.username, err, extra={ "team": stale_team_sync.team_id, "sync_config": sync_config, "member": member_info.username, "error": err, }, ) continue # Add the user to the membership set. group_membership.add(quay_user.id) # Add the user to the team. try: logger.info( "Adding member %s to team `%s` under organization %s via %s (#%s)", quay_user.username, stale_team_sync.team.name, stale_team_sync.team.organization.username, sync_config, stale_team_sync.team_id, extra={ "team": stale_team_sync.team_id, "sync_config": sync_config, "member": quay_user.username, }, ) model.team.add_user_to_team(quay_user, stale_team_sync.team) except model.UserAlreadyInTeam: # If the user is already present, nothing more to do for them. pass # Update the transaction and last_updated time of the team sync. Only if it matches # the current value will we then perform the deletion step. got_transaction_handle = model.team.update_sync_status(stale_team_sync) if not got_transaction_handle: # Another worker updated this team. Nothing more to do. logger.debug( "Another worker synced team `%s` under organization %s via %s (#%s)", stale_team_sync.team.name, stale_team_sync.team.organization.username, sync_config, stale_team_sync.team_id, extra={"team": stale_team_sync.team_id, "sync_config": sync_config}, ) return True # Delete any team members not found in the backing auth system. logger.debug( "Deleting stale members for team `%s` under organization %s via %s (#%s)", stale_team_sync.team.name, stale_team_sync.team.organization.username, sync_config, stale_team_sync.team_id, extra={"team": stale_team_sync.team_id, "sync_config": sync_config}, ) deleted = model.team.delete_members_not_present(stale_team_sync.team, group_membership) # Done! logger.info( "Finishing sync for team `%s` under organization %s via %s (#%s): %s deleted", stale_team_sync.team.name, stale_team_sync.team.organization.username, sync_config, stale_team_sync.team_id, deleted, extra={"team": stale_team_sync.team_id, "sync_config": sync_config}, ) return True