Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
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
""")
Ejemplo n.º 4
0
    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))
Ejemplo n.º 5
0
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"
            )
Ejemplo n.º 6
0
    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}
Ejemplo n.º 7
0
    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}