Exemplo n.º 1
0
    def test_dynamodb_lock_different_pull_request_ids(self):
        dummy_counter = 0
        with dynamodb_lock("pull_request_1"):
            dummy_counter += 1
            with dynamodb_lock("pull_request_2"):
                dummy_counter += 1

        self.assertEqual(dummy_counter, 2)
Exemplo n.º 2
0
    def test_dynamodb_lock_consecutive_same_lock(self):
        # Just make sure the lock is released properly after the block
        lock_name = "pull_request_id"
        dummy_counter = 0
        with dynamodb_lock(lock_name):
            dummy_counter += 1

        with dynamodb_lock(lock_name):
            dummy_counter += 1

        self.assertEqual(dummy_counter, 2)
Exemplo n.º 3
0
    def test_dynamodb_lock_blocks_others_from_acquiring_lock(self):
        lock_name = "pull_request_id"
        dummy_counter = 0
        with dynamodb_lock(lock_name):
            dummy_counter += 1
            with self.assertRaises(DynamoDBLockError):
                with dynamodb_lock(lock_name,
                                   retry_timeout=timedelta(
                                       milliseconds=0.001)):  # same lock name
                    dummy_counter += 1

        self.assertEqual(dummy_counter, 1)
Exemplo n.º 4
0
    def test_dynamodb_lock_raises_exception_thrown_in_block(self):
        class DemoException(Exception):
            pass

        lock_name = "pull_request_id"

        with self.assertRaises(DemoException):
            with dynamodb_lock(lock_name):
                raise DemoException("oops")

        # Lock should still be released after the exception was raised
        dummy_counter = 0
        with dynamodb_lock(lock_name):
            dummy_counter += 1
        self.assertEqual(dummy_counter, 1)
Exemplo n.º 5
0
def _handle_pull_request_webhook(payload: dict) -> HttpResponse:
    pull_request_id = payload["pull_request"]["node_id"]
    with dynamodb_lock(pull_request_id):
        pull_request = graphql_client.get_pull_request(pull_request_id)
        # a label change will trigger this webhook, so it may trigger automerge
        github_logic.maybe_automerge_pull_request(pull_request)
        github_logic.maybe_add_automerge_warning_comment(pull_request)
        github_controller.upsert_pull_request(pull_request)
        return HttpResponse("200")
Exemplo n.º 6
0
def _handle_pull_request_review_webhook(payload: dict) -> HttpResponse:
    pull_request_id = payload["pull_request"]["node_id"]
    review_id = payload["review"]["node_id"]

    with dynamodb_lock(pull_request_id):
        pull_request, review = graphql_client.get_pull_request_and_review(
            pull_request_id, review_id)
        github_logic.maybe_automerge_pull_request(pull_request)
        github_controller.upsert_review(pull_request, review)
    return HttpResponse("200")
Exemplo n.º 7
0
def _handle_pull_request_review_comment(payload: dict):
    """Handle when a pull request review comment is edited or removed.
    When comments are added it either hits:
        1 _handle_issue_comment_webhook (if the comment is on PR itself)
        2 _handle_pull_request_review_webhook (if the comment is on the "Files Changed" tab)
    Note that it hits (2) even if the comment is inline, and doesn't contain a review;
        in those cases Github still creates a review object for it.

    Unfortunately, this payload doesn't contain the node id of the review.
    Instead, it includes a separate, numeric id
    which is stored as `databaseId` on each GraphQL object.

    To get the review, we either:
        (1) query for the comment, and use the `review` edge in GraphQL.
        (2) Iterate through all reviews on the pull request, and find the one whose databaseId matches.
            See get_review_for_database_id()

    We do (1) for comments that were added or edited, but if a comment was just deleted, we have to do (2).

    See https://developer.github.com/v4/object/repository/#fields.
    """
    pull_request_id = payload["pull_request"]["node_id"]
    action = payload["action"]
    comment_id = payload["comment"]["node_id"]

    # This is NOT the node_id, but is a numeric string (the databaseId field).
    review_database_id = payload["comment"]["pull_request_review_id"]

    with dynamodb_lock(pull_request_id):
        if action in ("created", "edited"):
            pull_request, comment = graphql_client.get_pull_request_and_comment(
                pull_request_id, comment_id)
            if not isinstance(comment, PullRequestReviewComment):
                raise Exception(
                    f"Unexpected comment type {type(PullRequestReviewComment)} for pull request review"
                )
            review: Optional[Review] = Review.from_comment(comment)
        elif action == "deleted":
            pull_request = graphql_client.get_pull_request(pull_request_id)
            review = graphql_client.get_review_for_database_id(
                pull_request_id, review_database_id)
            if review is None:
                # If we deleted the last comment from a review, Github might have deleted the review.
                # If so, we should delete the Asana comment.
                logger.info(
                    "No review found in Github. Deleting the Asana comment.")
                github_controller.delete_comment(comment_id)
        else:
            raise ValueError(f"Unexpected action: {action}")

        if review is not None:
            github_controller.upsert_review(pull_request, review)

        return HttpResponse("200")
Exemplo n.º 8
0
def _handle_status_webhook(payload: dict) -> HttpResponse:
    commit_id = payload["commit"]["node_id"]
    pull_request = graphql_client.get_pull_request_for_commit(commit_id)
    if pull_request is None:
        # This could happen for commits that get pushed outside of the normal
        # pull request flow. These should just be silently ignored.
        logger.warn(f"No pull request found for commit id {commit_id}")
        return HttpResponse("200")

    with dynamodb_lock(pull_request.id()):
        github_logic.maybe_automerge_pull_request(pull_request)
        github_controller.upsert_pull_request(pull_request)
        return HttpResponse("200")
Exemplo n.º 9
0
def _handle_issue_comment_webhook(payload: dict) -> HttpResponse:
    action, issue, comment = itemgetter("action", "issue", "comment")(payload)

    issue_id = issue["node_id"]
    comment_id = comment["node_id"]
    with dynamodb_lock(issue_id):
        if action in ("created", "edited"):
            pull_request, comment = graphql_client.get_pull_request_and_comment(
                issue_id, comment_id)
            github_controller.upsert_comment(pull_request, comment)
            return HttpResponse("200")
        elif action == "deleted":
            logger.info(f"Deleting comment {comment_id}")
            github_controller.delete_comment(comment_id)
            return HttpResponse("200")
        else:
            error_text = f"Unknown action for issue_comment: {action}"
            logger.info(error_text)
            return HttpResponse("400", error_text)