diff --git a/readthedocs/builds/tasks.py b/readthedocs/builds/tasks.py index 27c2ba822a8..41c4bc60cdf 100644 --- a/readthedocs/builds/tasks.py +++ b/readthedocs/builds/tasks.py @@ -16,7 +16,9 @@ ) from readthedocs.builds.models import Build, Version from readthedocs.builds.utils import memcache_lock -from readthedocs.projects.tasks import send_build_status +from readthedocs.oauth.models import RemoteRepository +from readthedocs.oauth.notifications import GitBuildStatusFailureNotification +from readthedocs.projects.constants import GITHUB_BRAND, GITLAB_BRAND from readthedocs.worker import app log = logging.getLogger(__name__) @@ -206,3 +208,100 @@ def delete_inactive_external_versions(limit=200, days=30 * 3): version.project.slug, version.slug, ) version.delete() + + +@app.task(queue='web') +def send_build_status(build_pk, commit, status, link_to_build=False): + """ + Send Build Status to Git Status API for project external versions. + + It tries using these services' account in order: + + 1. user's account that imported the project + 2. each user's account from the project's maintainers + + :param build_pk: Build primary key + :param commit: commit sha of the pull/merge request + :param status: build status failed, pending, or success to be sent. + """ + # TODO: Send build status for BitBucket. + service = None + success = None + build = Build.objects.get(pk=build_pk) + provider_name = build.project.git_provider_name + + log.info('Sending build status. build=%s, project=%s', build.pk, build.project.slug) + + if provider_name in [GITHUB_BRAND, GITLAB_BRAND]: + # get the service class for the project e.g: GitHubService. + service_class = build.project.git_service_class() + + # First, try using user who imported the project's account + try: + service = service_class( + build.project.remote_repository.users.first(), + build.project.remote_repository.account + ) + + except RemoteRepository.DoesNotExist: + log.warning( + 'Project does not have a RemoteRepository. project=%s', + build.project.slug, + ) + + if service is not None: + # Send status report using the API. + success = service.send_build_status( + build=build, + commit=commit, + state=status, + link_to_build=link_to_build, + ) + + if success: + log.info( + 'Build status report sent correctly. project=%s build=%s status=%s commit=%s', + build.project.slug, + build.pk, + status, + commit, + ) + return True + + # Try using any of the users' maintainer accounts + # Try to loop through all project users to get their social accounts + users = build.project.users.all() + for user in users: + user_accounts = service_class.for_user(user) + # Try to loop through users all social accounts to send a successful request + for account in user_accounts: + if account.provider_name == provider_name: + success = account.send_build_status(build, commit, status) + if success: + log.info( + 'Build status report sent correctly using an user account. ' + 'project=%s build=%s status=%s commit=%s user=%s', + build.project.slug, + build.pk, + status, + commit, + user.username, + ) + return True + + for user in users: + # Send Site notification about Build status reporting failure + # to all the users of the project. + notification = GitBuildStatusFailureNotification( + context_object=build.project, + extra_context={'provider_name': provider_name}, + user=user, + success=False, + ) + notification.send() + + log.info( + 'No social account or repository permission available for %s', + build.project.slug + ) + return False diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py index 0c19afa8833..c28e7ad0dfc 100644 --- a/readthedocs/projects/tasks.py +++ b/readthedocs/projects/tasks.py @@ -45,6 +45,7 @@ ) from readthedocs.builds.models import APIVersion, Build, Version from readthedocs.builds.signals import build_complete +from readthedocs.builds.tasks import send_build_status from readthedocs.config import ConfigError from readthedocs.core.resolver import resolve_path from readthedocs.core.utils import send_email @@ -67,9 +68,6 @@ ) from readthedocs.doc_builder.loader import get_builder_class from readthedocs.doc_builder.python_environments import Conda, Virtualenv -from readthedocs.oauth.models import RemoteRepository -from readthedocs.oauth.notifications import GitBuildStatusFailureNotification -from readthedocs.projects.constants import GITHUB_BRAND, GITLAB_BRAND from readthedocs.projects.models import APIProject, Feature from readthedocs.search.utils import index_new_files, remove_indexed_files from readthedocs.sphinx_domains.models import SphinxDomain @@ -1891,103 +1889,6 @@ def retry_domain_verification(domain_pk): ) -@app.task(queue='web') -def send_build_status(build_pk, commit, status, link_to_build=False): - """ - Send Build Status to Git Status API for project external versions. - - It tries using these services' account in order: - - 1. user's account that imported the project - 2. each user's account from the project's maintainers - - :param build_pk: Build primary key - :param commit: commit sha of the pull/merge request - :param status: build status failed, pending, or success to be sent. - """ - # TODO: Send build status for BitBucket. - service = None - success = None - build = Build.objects.get(pk=build_pk) - provider_name = build.project.git_provider_name - - log.info('Sending build status. build=%s, project=%s', build.pk, build.project.slug) - - if provider_name in [GITHUB_BRAND, GITLAB_BRAND]: - # get the service class for the project e.g: GitHubService. - service_class = build.project.git_service_class() - - # First, try using user who imported the project's account - try: - service = service_class( - build.project.remote_repository.users.first(), - build.project.remote_repository.account - ) - - except RemoteRepository.DoesNotExist: - log.warning( - 'Project does not have a RemoteRepository. project=%s', - build.project.slug, - ) - - if service is not None: - # Send status report using the API. - success = service.send_build_status( - build=build, - commit=commit, - state=status, - link_to_build=link_to_build, - ) - - if success: - log.info( - 'Build status report sent correctly. project=%s build=%s status=%s commit=%s', - build.project.slug, - build.pk, - status, - commit, - ) - return True - - # Try using any of the users' maintainer accounts - # Try to loop through all project users to get their social accounts - users = build.project.users.all() - for user in users: - user_accounts = service_class.for_user(user) - # Try to loop through users all social accounts to send a successful request - for account in user_accounts: - if account.provider_name == provider_name: - success = account.send_build_status(build, commit, status) - if success: - log.info( - 'Build status report sent correctly using an user account. ' - 'project=%s build=%s status=%s commit=%s user=%s', - build.project.slug, - build.pk, - status, - commit, - user.username, - ) - return True - - for user in users: - # Send Site notification about Build status reporting failure - # to all the users of the project. - notification = GitBuildStatusFailureNotification( - context_object=build.project, - extra_context={'provider_name': provider_name}, - user=user, - success=False, - ) - notification.send() - - log.info( - 'No social account or repository permission available for %s', - build.project.slug - ) - return False - - def send_external_build_status(version_type, build_pk, commit, status): """ Check if build is external and Send Build Status for project external versions. diff --git a/readthedocs/rtd_tests/tests/test_celery.py b/readthedocs/rtd_tests/tests/test_celery.py index 2144354483c..3d656ff9aae 100644 --- a/readthedocs/rtd_tests/tests/test_celery.py +++ b/readthedocs/rtd_tests/tests/test_celery.py @@ -21,6 +21,7 @@ from readthedocs.doc_builder.exceptions import VersionLockedError from readthedocs.oauth.models import RemoteRepository from readthedocs.projects import tasks +from readthedocs.builds import tasks as build_tasks from readthedocs.projects.exceptions import RepositoryError from readthedocs.projects.models import Project from readthedocs.rtd_tests.mocks.mock_api import mock_api @@ -352,7 +353,7 @@ def test_send_build_status_with_remote_repo_github(self, send_build_status): external_build = get( Build, project=self.project, version=external_version ) - tasks.send_build_status( + build_tasks.send_build_status( external_build.id, external_build.commit, BUILD_STATUS_SUCCESS ) @@ -375,7 +376,7 @@ def test_send_build_status_with_social_account_github(self, send_build_status): external_build = get( Build, project=self.project, version=external_version ) - tasks.send_build_status( + build_tasks.send_build_status( external_build.id, external_build.commit, BUILD_STATUS_SUCCESS ) @@ -392,7 +393,7 @@ def test_send_build_status_no_remote_repo_or_social_account_github(self, send_bu external_build = get( Build, project=self.project, version=external_version ) - tasks.send_build_status( + build_tasks.send_build_status( external_build.id, external_build.commit, BUILD_STATUS_SUCCESS ) @@ -412,7 +413,7 @@ def test_send_build_status_with_remote_repo_gitlab(self, send_build_status): external_build = get( Build, project=self.project, version=external_version ) - tasks.send_build_status( + build_tasks.send_build_status( external_build.id, external_build.commit, BUILD_STATUS_SUCCESS ) @@ -435,7 +436,7 @@ def test_send_build_status_with_social_account_gitlab(self, send_build_status): external_build = get( Build, project=self.project, version=external_version ) - tasks.send_build_status( + build_tasks.send_build_status( external_build.id, external_build.commit, BUILD_STATUS_SUCCESS ) @@ -452,7 +453,7 @@ def test_send_build_status_no_remote_repo_or_social_account_gitlab(self, send_bu external_build = get( Build, project=self.project, version=external_version ) - tasks.send_build_status( + build_tasks.send_build_status( external_build.id, external_build.commit, BUILD_STATUS_SUCCESS )