Пример #1
0
class PhabricatorActions(object):
    '''
    Common Phabricator actions shared across clients
    '''
    def __init__(self, url, api_key, retries=5, sleep=10):
        self.api = PhabricatorAPI(url=url, api_key=api_key)

        # Phabricator secure revision retries configuration
        assert isinstance(retries, int)
        assert isinstance(sleep, int)
        self.retries = collections.defaultdict(lambda: (retries, None))
        self.sleep = sleep
        logger.info('Will retry Phabricator secure revision queries',
                    retries=retries,
                    sleep=sleep)

        # Load secure projects
        projects = self.api.search_projects(slugs=['secure-revision'])
        self.secure_projects = {
            p['phid']: p['fields']['name']
            for p in projects
        }
        logger.info('Loaded secure projects',
                    projects=self.secure_projects.values())

    def update_state(self, build):
        '''
        Check the visibility of the revision, by retrying N times with a specified time
        This method is executed regularly by the client application to check on the status evolution
        as the BMO daemon can take several minutes to update the status
        '''
        assert isinstance(build, PhabricatorBuild)

        # Only when queued
        if build.state != PhabricatorBuildState.Queued:
            return

        # Check this build has some retries left
        retries_left, last_try = self.retries[build.target_phid]
        if retries_left <= 0:
            return

        # Check this build has been awaited between tries
        now = time.time()
        if last_try is not None and now - last_try < self.sleep:
            return

        # Now we can check if this revision is public
        retries_left -= 1
        self.retries[build.target_phid] = (retries_left, now)
        logger.info('Checking visibility status',
                    build=str(build),
                    retries_left=retries_left)

        if self.is_visible(build):
            build.state = PhabricatorBuildState.Public
            logger.info('Revision is public', build=str(build))

        elif retries_left <= 0:
            # Mark as secured when no retries are left
            build.state = PhabricatorBuildState.Secured
            logger.info('Revision is marked as secure', build=str(build))

        else:
            # Enqueue back to retry later
            build.state = PhabricatorBuildState.Queued

    def is_visible(self, build):
        '''
        Check the visibility of the revision by loading its details
        '''
        assert isinstance(build, PhabricatorBuild)
        assert build.state == PhabricatorBuildState.Queued
        try:
            # Load revision with projects
            build.revision = self.api.load_revision(rev_id=build.revision_id,
                                                    attachments={
                                                        'projects': True,
                                                        'reviewers': True
                                                    })
            if not build.revision:
                raise Exception('Not found')

            # Check against secure projects
            projects = set(
                build.revision['attachments']['projects']['projectPHIDs'])
            if projects.intersection(self.secure_projects):
                raise Exception('Secure revision')
        except Exception as e:
            logger.info('Revision not accessible',
                        build=str(build),
                        error=str(e))
            return False

        return True

    def load_patches_stack(self, build):
        '''
        Load a stack of patches for a public Phabricator build
        without hitting a local mercurial repository
        '''
        build.stack = self.api.load_patches_stack(build.diff_id, build.diff)

    def load_reviewers(self, build):
        '''
        Load details for reviewers found on a build
        '''
        assert isinstance(build, PhabricatorBuild)
        assert build.state == PhabricatorBuildState.Public
        assert build.revision is not None

        reviewers = build.revision['attachments']['reviewers']['reviewers']
        build.reviewers = [
            self.api.load_user(user_phid=reviewer['reviewerPHID'])
            for reviewer in reviewers
        ]
Пример #2
0
class PhabricatorActions(object):
    """
    Common Phabricator actions shared across clients
    """
    def __init__(self, url, api_key, retries=5, sleep=10):
        self.api = PhabricatorAPI(url=url, api_key=api_key)

        # Phabricator secure revision retries configuration
        assert isinstance(retries, int)
        assert isinstance(sleep, int)
        self.max_retries = retries
        self.retries = collections.defaultdict(lambda: (retries, None))
        self.sleep = sleep
        logger.info(
            "Will retry Phabricator secure revision queries",
            retries=retries,
            sleep=sleep,
        )

        # Load secure projects
        projects = self.api.search_projects(slugs=["secure-revision"])
        self.secure_projects = {
            p["phid"]: p["fields"]["name"]
            for p in projects
        }
        logger.info("Loaded secure projects",
                    projects=self.secure_projects.values())

    def update_state(self, build):
        """
        Check the visibility of the revision, by retrying N times with an exponential backoff time
        This method is executed regularly by the client application to check on the status evolution
        as the BMO daemon can take several minutes to update the status
        """
        assert isinstance(build, PhabricatorBuild)

        # Only when queued
        if build.state != PhabricatorBuildState.Queued:
            return

        # Check this build has some retries left
        retries_left, last_try = self.retries[build.target_phid]
        if retries_left <= 0:
            return

        # Check this build has been awaited between tries
        exp_backoff = (2**(self.max_retries - retries_left)) * self.sleep
        now = time.time()
        if last_try is not None and now - last_try < exp_backoff:
            return

        # Now we can check if this revision is public
        retries_left -= 1
        self.retries[build.target_phid] = (retries_left, now)
        logger.info("Checking visibility status",
                    build=str(build),
                    retries_left=retries_left)

        if self.is_visible(build):
            build.state = PhabricatorBuildState.Public
            build.revision_url = self.build_revision_url(build)
            logger.info("Revision is public", build=str(build))

        elif retries_left <= 0:
            # Mark as secured when no retries are left
            build.state = PhabricatorBuildState.Secured
            logger.info("Revision is marked as secure", build=str(build))

        else:
            # Enqueue back to retry later
            build.state = PhabricatorBuildState.Queued

    def is_visible(self, build):
        """
        Check the visibility of the revision by loading its details
        """
        assert isinstance(build, PhabricatorBuild)
        assert build.state == PhabricatorBuildState.Queued
        try:
            # Load revision with projects
            build.revision = self.api.load_revision(
                rev_id=build.revision_id,
                attachments={
                    "projects": True,
                    "reviewers": True
                },
            )
            if not build.revision:
                raise Exception("Not found")

            # Check against secure projects
            projects = set(
                build.revision["attachments"]["projects"]["projectPHIDs"])
            if projects.intersection(self.secure_projects):
                raise Exception("Secure revision")
        except Exception as e:
            logger.info("Revision not accessible",
                        build=str(build),
                        error=str(e))
            return False

        return True

    def load_patches_stack(self, build):
        """
        Load a stack of patches for a public Phabricator build
        without hitting a local mercurial repository
        """
        build.stack = self.api.load_patches_stack(build.diff_id, build.diff)

    def load_reviewers(self, build):
        """
        Load details for reviewers found on a build
        """
        assert isinstance(build, PhabricatorBuild)
        assert build.state == PhabricatorBuildState.Public
        assert build.revision is not None

        def load_user(phid):
            if phid.startswith("PHID-USER"):
                return self.api.load_user(user_phid=phid)
            elif phid.startswith("PHID-PROJ"):
                logger.info(f"Skipping group reviewer {phid}")
            else:
                raise Exception(f"Unsupported reviewer {phid}")

        reviewers = build.revision["attachments"]["reviewers"]["reviewers"]
        build.reviewers = list(
            filter(None, [
                load_user(reviewer["reviewerPHID"]) for reviewer in reviewers
            ]))

    def build_revision_url(self, build):
        """
        Build a Phabricator frontend url for a build's revision
        """
        return "https://{}/D{}".format(self.api.hostname, build.revision_id)