def validate_codeowners_associations(self, attrs, project): from sentry.api.endpoints.project_codeowners import validate_association from sentry.models import ExternalActor, UserEmail, actor_type_to_string from sentry.ownership.grammar import parse_code_owners # Get list of team/user names from CODEOWNERS file team_names, usernames, emails = parse_code_owners(attrs["raw"]) # Check if there exists Sentry users with the emails listed in CODEOWNERS user_emails = UserEmail.objects.filter( email__in=emails, user__sentry_orgmember_set__organization=project.organization, ) # Check if the usernames/teamnames have an association external_actors = ExternalActor.objects.filter( external_name__in=usernames + team_names, organization=project.organization, ) # Convert CODEOWNERS into IssueOwner syntax users_dict = {} teams_dict = {} teams_without_access = [] for external_actor in external_actors: type = actor_type_to_string(external_actor.actor.type) if type == "user": user = external_actor.actor.resolve() users_dict[external_actor.external_name] = user.email elif type == "team": team = external_actor.actor.resolve() # make sure the sentry team has access to the project # tied to the codeowner if project in team.get_projects(): teams_dict[external_actor.external_name] = f"#{team.slug}" else: teams_without_access.append(f"#{team.slug}") emails_dict = {item.email: item.email for item in user_emails} associations = {**users_dict, **teams_dict, **emails_dict} errors = { "missing_user_emails": validate_association(emails, user_emails, "emails"), "missing_external_users": validate_association(usernames, external_actors, "usernames"), "missing_external_teams": validate_association(team_names, external_actors, "team names"), "teams_without_access": teams_without_access, } return associations, errors
def validate(self, attrs: Mapping[str, Any]) -> Mapping[str, Any]: # If it already exists, set default attrs with existing values if self.instance: attrs = { "raw": self.instance.raw, "code_mapping_id": self.instance.repository_project_path_config, **attrs, } if not attrs.get("raw", "").strip(): return attrs external_association_err: List[str] = [] # Get list of team/user names from CODEOWNERS file team_names, usernames, emails = parse_code_owners(attrs["raw"]) # Check if there exists Sentry users with the emails listed in CODEOWNERS user_emails = UserEmail.objects.filter( email__in=emails, user__sentry_orgmember_set__organization=self.context["project"]. organization, ) user_emails_diff = validate_association(emails, user_emails, "emails") external_association_err.extend(user_emails_diff) # Check if the usernames have an association external_actors = ExternalActor.objects.filter( external_name__in=usernames + team_names, organization=self.context["project"].organization, ) external_users_diff = validate_association(usernames, external_actors, "usernames") external_association_err.extend(external_users_diff) external_teams_diff = validate_association(team_names, external_actors, "team names") external_association_err.extend(external_teams_diff) if len(external_association_err): raise serializers.ValidationError( {"raw": "\n".join(external_association_err)}) # Convert CODEOWNERS into IssueOwner syntax users_dict = {} teams_dict = {} for external_actor in external_actors: type = actor_type_to_string(external_actor.actor.type) if type == "user": user = external_actor.actor.resolve() users_dict[external_actor.external_name] = user.email elif type == "team": team = external_actor.actor.resolve() teams_dict[external_actor.external_name] = f"#{team.slug}" emails_dict = {email: email for email in emails} associations = {**users_dict, **teams_dict, **emails_dict} issue_owner_rules = convert_codeowners_syntax(attrs["raw"], associations, attrs["code_mapping_id"]) # Convert IssueOwner syntax into schema syntax validated_data = ProjectOwnershipSerializer( context=self.context).validate({"raw": issue_owner_rules}) return {**validated_data, **attrs}
def test_parse_code_owners(): assert parse_code_owners(codeowners_fixture_data) == ( ["@getsentry/frontend", "@getsentry/docs", "@getsentry/ecosystem"], ["@NisanthanNanthakumar", "@AnotherUser"], ["*****@*****.**"], )
def validate_codeowners_associations(self, codeowners, project): from sentry.api.endpoints.project_codeowners import validate_association from sentry.models import ( ExternalActor, OrganizationMember, OrganizationMemberTeam, Project, UserEmail, actor_type_to_string, ) from sentry.ownership.grammar import parse_code_owners from sentry.types.integrations import ExternalProviders # Get list of team/user names from CODEOWNERS file team_names, usernames, emails = parse_code_owners(codeowners) # Check if there exists Sentry users with the emails listed in CODEOWNERS user_emails = UserEmail.objects.filter( email__in=emails, user__sentry_orgmember_set__organization=project.organization, ) # Check if the usernames/teamnames have an association external_actors = ExternalActor.objects.filter( external_name__in=usernames + team_names, organization=project.organization, provider__in=[ ExternalProviders.GITHUB.value, ExternalProviders.GITLAB.value ], ) # Convert CODEOWNERS into IssueOwner syntax users_dict = {} teams_dict = {} teams_without_access = [] users_without_access = [] for external_actor in external_actors: type = actor_type_to_string(external_actor.actor.type) if type == "user": user = external_actor.actor.resolve() organization_members_ids = OrganizationMember.objects.filter( user_id=user.id, organization_id=project.organization_id).values_list( "id", flat=True) team_ids = OrganizationMemberTeam.objects.filter( organizationmember_id__in=Subquery( organization_members_ids)).values_list("team_id", flat=True) projects = Project.objects.get_for_team_ids(Subquery(team_ids)) if project in projects: users_dict[external_actor.external_name] = user.email else: users_without_access.append(f"{user.get_display_name()}") elif type == "team": team = external_actor.actor.resolve() # make sure the sentry team has access to the project # tied to the codeowner if project in team.get_projects(): teams_dict[external_actor.external_name] = f"#{team.slug}" else: teams_without_access.append(f"#{team.slug}") emails_dict = {item.email: item.email for item in user_emails} associations = {**users_dict, **teams_dict, **emails_dict} errors = { "missing_user_emails": validate_association(emails, user_emails, "emails"), "missing_external_users": validate_association(usernames, external_actors, "usernames"), "missing_external_teams": validate_association(team_names, external_actors, "team names"), "teams_without_access": teams_without_access, "users_without_access": users_without_access, } return associations, errors
def validate(self, attrs): # If it already exists, set default attrs with existing values if self.instance: attrs = { "raw": self.instance.raw, "code_mapping_id": self.instance.repository_project_path_config, **attrs, } if not attrs.get("raw", "").strip(): return attrs external_association_err = [] # Get list of team/user names from CODEOWNERS file teamnames, usernames, emails = parse_code_owners(attrs["raw"]) # Check if there exists Sentry users with the emails listed in CODEOWNERS user_emails = UserEmail.objects.filter(email__in=emails) user_emails_diff = self._validate_association(emails, user_emails, "emails") external_association_err.extend(user_emails_diff) # Check if the usernames have an association external_users = ExternalUser.objects.filter( external_name__in=usernames, organizationmember__organization=self.context["project"]. organization, ) external_users_diff = self._validate_association( usernames, external_users, "usernames") external_association_err.extend(external_users_diff) # Check if the team names have an association external_teams = ExternalTeam.objects.filter( external_name__in=teamnames, team__organization=self.context["project"].organization, ) external_teams_diff = self._validate_association( teamnames, external_teams, "team names") external_association_err.extend(external_teams_diff) if len(external_association_err): raise serializers.ValidationError( {"raw": "\n".join(external_association_err)}) # Convert CODEOWNERS into IssueOwner syntax users_dict = { user.external_name: user.organizationmember.user.email for user in external_users } teams_dict = { team.external_name: f"#{team.team.slug}" for team in external_teams } emails_dict = {email: email for email in emails} associations = {**users_dict, **teams_dict, **emails_dict} issue_owner_rules = convert_codeowners_syntax(attrs["raw"], associations, attrs["code_mapping_id"]) # Convert IssueOwner syntax into schema syntax validated_data = ProjectOwnershipSerializer( context=self.context).validate({"raw": issue_owner_rules}) return {**validated_data, **attrs}