def update_schema(self): """ Updating the schema goes through the following steps: 1. parsing the original codeowner file to get the associations 2. convert the codeowner file to the ownership syntax 3. convert the ownership syntax to the schema """ associations, _ = self.validate_codeowners_associations( self.raw, self.project) issue_owner_rules = convert_codeowners_syntax( codeowners=self.raw, associations=associations, code_mapping=self.repository_project_path_config, ) # Convert IssueOwner syntax into schema syntax try: schema = create_schema_from_issue_owners( issue_owners=issue_owner_rules, project_id=self.project.id) # Convert IssueOwner syntax into schema syntax if schema: self.schema = schema self.save() except ValidationError: return
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 # Ignore association errors and continue parsing CODEOWNERS for valid lines. # Allow users to incrementally fix association errors; for CODEOWNERS with many external mappings. associations, _ = ProjectCodeOwners.validate_codeowners_associations( attrs["raw"], self.context["project"] ) issue_owner_rules = convert_codeowners_syntax( attrs["raw"], associations, attrs["code_mapping_id"] ) # Convert IssueOwner syntax into schema syntax try: validated_data = create_schema_from_issue_owners( issue_owners=issue_owner_rules, project_id=self.context["project"].id ) return { **attrs, "schema": validated_data, } except ValidationError as e: raise serializers.ValidationError(e)
def test_convert_codeowners_syntax_excludes_invalid(): code_mapping = type("", (), {})() code_mapping.stack_root = "webpack://static/" code_mapping.source_root = "" codeowners = (codeowners_fixture_data + r""" # some invalid rules debug[0-9].log @NisanthanNanthakumar !important/*.log @NisanthanNanthakumar file 1.txt @NisanthanNanthakumar @getsentry/ecosystem \#somefile.txt @NisanthanNanthakumar # some anchored paths /scripts/test.js @getsentry/ops config/hooks @getsentry/ops config/relay/ @getsentry/relay # not anchored path docs-ui/ @getsentry/docs @getsentry/ecosystem """) assert (convert_codeowners_syntax( codeowners, { "@getsentry/frontend": "front-sentry", "@getsentry/docs": "docs-sentry", "@getsentry/ecosystem": "ecosystem", "@getsentry/ops": "ops", "@getsentry/relay": "relay", "@NisanthanNanthakumar": "*****@*****.**", "@AnotherUser": "******", "*****@*****.**": "*****@*****.**", }, code_mapping, ) == """ # cool stuff comment codeowners:*.js front-sentry [email protected] # good comment codeowners:webpack://static/docs/* docs-sentry ecosystem codeowners:webpack://static/src/sentry/* [email protected] codeowners:webpack://static/api/* [email protected] # some invalid rules codeowners:file [email protected] ecosystem # some anchored paths codeowners:webpack://static/scripts/test.js ops codeowners:webpack://static/config/hooks ops codeowners:webpack://static/config/relay/ relay # not anchored path codeowners:docs-ui/ docs-sentry ecosystem """)
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 # We want to limit `raw` to a reasonable length, so that people don't end up with values # that are several megabytes large. To not break this functionality for existing customers # we temporarily allow rows that already exceed this limit to still be updated. # We do something similar with ProjectOwnership at the API level. existing_raw = self.instance.raw if self.instance else "" if len(attrs["raw"]) > MAX_RAW_LENGTH and len( existing_raw) <= MAX_RAW_LENGTH: raise serializers.ValidationError({ "raw": f"Raw needs to be <= {MAX_RAW_LENGTH} characters in length" }) # Ignore association errors and continue parsing CODEOWNERS for valid lines. # Allow users to incrementally fix association errors; for CODEOWNERS with many external mappings. associations, _ = validate_codeowners_associations( attrs["raw"], self.context["project"]) issue_owner_rules = convert_codeowners_syntax(attrs["raw"], associations, attrs["code_mapping_id"]) # Convert IssueOwner syntax into schema syntax try: validated_data = create_schema_from_issue_owners( issue_owners=issue_owner_rules, project_id=self.context["project"].id) return { **attrs, "schema": validated_data, } except ValidationError as e: raise serializers.ValidationError(str(e))
def test_convert_codeowners_syntax(): code_mapping = type("", (), {})() code_mapping.stack_root = "webpack://docs" code_mapping.source_root = "docs" assert (convert_codeowners_syntax( codeowners_fixture_data, { "@getsentry/frontend": "front-sentry", "@getsentry/docs": "docs-sentry", "@getsentry/ecosystem": "ecosystem", "@NisanthanNanthakumar": "*****@*****.**", "@AnotherUser": "******", "*****@*****.**": "*****@*****.**", }, code_mapping, ) == "\n# cool stuff comment\npath:*.js front-sentry [email protected]\n# good comment\n\n\npath:webpack://docs/* docs-sentry ecosystem\npath:src/sentry/* [email protected]\npath:api/* [email protected]\n" )
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 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}