Ejemplo n.º 1
0
def test_relman_approval_status(status, phabdouble):
    """Check only an approval from relman allows landing"""
    relman_group = phabdouble.project("release-managers")
    repo = phabdouble.repo(name="uplift-target")
    repos = get_repos_for_env("localdev")
    assert repos["uplift-target"].approval_required is True

    # Add relman as reviewer with specified status
    revision = phabdouble.revision(repo=repo)
    phabdouble.reviewer(revision, relman_group, status=status)

    # Add a some extra reviewers
    for i in range(3):
        phabdouble.reviewer(revision,
                            phabdouble.user(username=f"reviewer-{i}"))

    phab_revision = phabdouble.api_object_for(
        revision,
        attachments={
            "reviewers": True,
            "reviewers-extra": True,
            "projects": True
        },
    )

    check = check_relman_approval(relman_group["phid"], repos)
    output = check(revision=phab_revision,
                   repo=phabdouble.api_object_for(repo))
    if status == ReviewerStatus.ACCEPTED:
        assert output is None
    else:
        assert (
            output ==
            "The release-managers group did not accept that stack: you need to wait for a group approval from release-managers, or request a new review."  # noqa
        )
Ejemplo n.º 2
0
def _assess_transplant_request(phab, landing_path):
    nodes, edges = _find_stack_from_landing_path(phab, landing_path)
    stack_data = request_extended_revision_data(phab, [phid for phid in nodes])
    landing_path = _convert_path_id_to_phid(landing_path, stack_data)

    supported_repos = get_repos_for_env(current_app.config.get('ENVIRONMENT'))
    landable_repos = get_landable_repos_for_revision_data(
        stack_data, supported_repos)
    landable, blocked = calculate_landable_subgraphs(
        stack_data,
        edges,
        landable_repos,
        other_checks=DEFAULT_OTHER_BLOCKER_CHECKS)

    assessment = check_landing_blockers(
        g.auth0_user,
        landing_path,
        stack_data,
        landable,
        landable_repos,
    )
    if assessment.blocker is not None:
        return (assessment, None, None, None)

    # We have now verified that landable_path is valid and is indeed
    # landable (in the sense that it is a landable_subgraph, with no
    # revisions being blocked). Make this clear by using a different
    # value, and assume it going forward.
    valid_path = landing_path

    # Now that we know this is a valid path we can convert it into a list
    # of (revision, diff) tuples.
    to_land = [stack_data.revisions[r_phid] for r_phid, _ in valid_path]
    to_land = [
        (r, stack_data.diffs[PhabricatorClient.expect(r, 'fields',
                                                      'diffPHID')])
        for r in to_land
    ]

    # To be a landable path the entire path must have the same
    # repository, so we can get away with checking only one.
    repo = stack_data.repositories[to_land[0][0]['fields']['repositoryPHID']]
    landing_repo = landable_repos[repo['phid']]

    involved_phids = set()
    for revision, _ in to_land:
        involved_phids.update(gather_involved_phids(revision))

    involved_phids = list(involved_phids)
    users = lazy_user_search(phab, involved_phids)()
    projects = lazy_project_search(phab, involved_phids)()
    reviewers = {
        revision['phid']: get_collated_reviewers(revision)
        for revision, _ in to_land
    }

    assessment = check_landing_warnings(g.auth0_user, to_land, repo,
                                        landing_repo, reviewers, users,
                                        projects)
    return (assessment, to_land, landing_repo, stack_data)
Ejemplo n.º 3
0
def create(data):
    """Create new uplift requests for requested repository & revision"""

    # Validate repository
    all_repos = get_repos_for_env(current_app.config.get("ENVIRONMENT"))
    repository = all_repos.get(data["repository"])
    if repository is None or not repository.approval_required:
        return problem(
            400,
            "No valid uplift repository",
            "Please select an uplift repository to create that uplift request.",
            type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400",
        )

    logger.info(
        "Checking approval state",
        extra={
            "revision": data["revision_id"],
            "target_repository": data["repository"],
        },
    )
    is_approval, revision, target_repository = check_approval_state(
        g.phabricator,
        revision_id=data["revision_id"],
        target_repository_name=data["repository"],
    )
    logger.info(
        "Approval state is valid",
        extra={"style": "approval" if is_approval else "uplift"},
    )

    try:
        if is_approval:
            output = create_approval_request(g.phabricator, revision,
                                             data["form_content"])
        else:
            output = create_uplift_revision(g.phabricator, revision,
                                            target_repository,
                                            data["form_content"])
    except Exception as e:
        logger.error(
            "Failed to create an uplift request",
            extra={
                "revision": data["revision_id"],
                "repository": repository,
                "error": str(e),
            },
        )
        raise

    return output, 201
Ejemplo n.º 4
0
def test_get_landable_repos_for_revision_data(phabdouble, mocked_repo_config):
    phab = phabdouble.get_phabricator_client()

    repo1 = phabdouble.repo(name='mozilla-central')
    repo2 = phabdouble.repo(name='not-mozilla-central')
    r1 = phabdouble.revision(repo=repo1)
    r2 = phabdouble.revision(repo=repo2, depends_on=[r1])

    supported_repos = get_repos_for_env('test')
    revision_data = request_extended_revision_data(phab,
                                                   [r1['phid'], r2['phid']])

    landable_repos = get_landable_repos_for_revision_data(
        revision_data, supported_repos)
    assert repo1['phid'] in landable_repos
    assert repo2['phid'] not in landable_repos
    assert landable_repos[repo1['phid']].tree == 'mozilla-central'
Ejemplo n.º 5
0
def test_relman_approval_missing(phabdouble):
    """A repo with an approval required needs relman as reviewer"""
    relman_group = phabdouble.project("release-managers")
    repo = phabdouble.repo(name="uplift-target")
    repos = get_repos_for_env("localdev")
    assert repos["uplift-target"].approval_required is True

    revision = phabdouble.revision(repo=repo)
    phab_revision = phabdouble.api_object_for(
        revision,
        attachments={
            "reviewers": True,
            "reviewers-extra": True,
            "projects": True
        },
    )

    check = check_relman_approval(relman_group["phid"], repos)
    assert (check(revision=phab_revision, repo=phabdouble.api_object_for(repo))
            == "The release-managers group was not requested for review")
Ejemplo n.º 6
0
def lazy_get_landing_repo(repository, env):
    """Return a landoapi.repos.Repo for the provided repository.

    Args:
        repository: A dict of the repository data just as it is returned
            by Phabricator.
        env: The environment Lando API is running in.

    Returns:
        A landoapi.repos.Repo corresponding to the provided Phabricator
        repository data or None if the repository is not configured.

    Raises:
        landoapi.phabricator.PhabricatorCommunicationException:
            If the provided repository data does not adhere to the format
            expected from Phabricator. This should almost never happen
            when passing a repository response from phabricator unless
            something has gone seriously wrong.
    """
    if not repository:
        return None

    shortname = PhabricatorClient.expect(repository, 'fields', 'shortName')
    return get_repos_for_env(env).get(shortname)
Ejemplo n.º 7
0
def create_uplift_revision(
    phab: PhabricatorClient,
    source_revision: dict,
    target_repository: dict,
    form_content: str,
):
    """Create a new revision on a repository, cloning a diff from another repo"""
    # Check the target repository needs an approval
    repos = get_repos_for_env(current_app.config.get("ENVIRONMENT"))
    local_repo = repos.get(target_repository["fields"]["shortName"])
    assert local_repo is not None, f"Unknown repository {target_repository}"
    assert (
        local_repo.approval_required is True
    ), f"No approval required for {target_repository}"

    # Load release managers group for review
    release_managers = get_release_managers(phab)

    # Find the source diff on phabricator
    stack = request_extended_revision_data(phab, [source_revision["phid"]])
    diff = stack.diffs[source_revision["fields"]["diffPHID"]]

    # Get raw diff
    raw_diff = phab.call_conduit("differential.getrawdiff", diffID=diff["id"])
    if not raw_diff:
        raise Exception("Missing raw source diff, cannot uplift revision.")

    # Base revision hash is available on the diff fields
    refs = {ref["type"]: ref for ref in phab.expect(diff, "fields", "refs")}
    base_revision = refs["base"]["identifier"] if "base" in refs else None

    # The first commit in the attachment list is the current HEAD of stack
    # we can use the HEAD to mark the changes being created
    commits = phab.expect(diff, "attachments", "commits", "commits")
    head = commits[0] if commits else None

    # Upload it on target repo
    new_diff = phab.call_conduit(
        "differential.creatediff",
        changes=patch_to_changes(raw_diff, head["identifier"] if head else None),
        sourceMachine=local_repo.url,
        sourceControlSystem="hg",
        sourceControlPath="/",
        sourceControlBaseRevision=base_revision,
        creationMethod="lando-uplift",
        lintStatus="none",
        unitStatus="none",
        repositoryPHID=target_repository["phid"],
        sourcePath=None,  # TODO ? Local path
        branch="HEAD",
    )
    new_diff_id = phab.expect(new_diff, "diffid")
    new_diff_phid = phab.expect(new_diff, "phid")
    logger.info("Created new diff", extra={"id": new_diff_id, "phid": new_diff_phid})

    # Attach commit information to setup the author (needed for landing)
    phab.call_conduit(
        "differential.setdiffproperty",
        diff_id=new_diff_id,
        name="local:commits",
        data=json.dumps(
            {
                commit["identifier"]: {
                    "author": phab.expect(commit, "author", "name"),
                    "authorEmail": phab.expect(commit, "author", "email"),
                    "time": 0,
                    "message": phab.expect(commit, "message"),
                    "commit": phab.expect(commit, "identifier"),
                    "tree": None,
                    "parents": phab.expect(commit, "parents"),
                }
                for commit in commits
            }
        ),
    )

    # Append an uplift mention to the summary
    summary = phab.expect(source_revision, "fields", "summary")
    summary += f"\nNOTE: Uplifted from D{source_revision['id']}"

    # Finally create the revision to link all the pieces
    new_rev = phab.call_conduit(
        "differential.revision.edit",
        transactions=[
            {"type": "update", "value": new_diff_phid},
            # Copy title & summary from source revision
            {"type": "title", "value": phab.expect(source_revision, "fields", "title")},
            {"type": "summary", "value": summary},
            # Set release managers as reviewers
            {"type": "reviewers.add", "value": [release_managers["phid"]]},
            # Post the form as a comment on the revision
            {"type": "comment", "value": form_content},
            # Copy Bugzilla id
            {
                "type": "bugzilla.bug-id",
                "value": phab.expect(source_revision, "fields", "bugzilla.bug-id"),
            },
        ],
    )
    new_rev_id = phab.expect(new_rev, "object", "id")
    new_rev_phid = phab.expect(new_rev, "object", "phid")
    logger.info(
        "Created new Phabricator revision",
        extra={"id": new_rev_id, "phid": new_rev_phid},
    )

    return {
        "mode": "uplift",
        "repository": phab.expect(target_repository, "fields", "shortName"),
        "url": f"{phab.url_base}/D{new_rev_id}",
        "revision_id": new_rev_id,
        "revision_phid": new_rev_phid,
        "diff_id": new_diff_id,
        "diff_phid": new_diff_phid,
    }
Ejemplo n.º 8
0
def get(revision_id):
    """Get the stack a revision is part of.

    Args:
        revision_id: (string) ID of the revision in 'D{number}' format
    """
    revision_id = revision_id_to_int(revision_id)

    phab = g.phabricator
    revision = phab.call_conduit("differential.revision.search",
                                 constraints={"ids": [revision_id]})
    revision = phab.single(revision, "data", none_when_empty=True)
    if revision is None:
        return problem(
            404,
            "Revision not found",
            "The requested revision does not exist",
            type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404",
        )

    # TODO: This assumes that all revisions and related objects in the stack
    # have uniform view permissions for the requesting user. Some revisions
    # being restricted could cause this to fail.
    nodes, edges = build_stack_graph(phab, phab.expect(revision, "phid"))
    stack_data = request_extended_revision_data(phab, [phid for phid in nodes])

    supported_repos = get_repos_for_env(current_app.config.get("ENVIRONMENT"))
    landable_repos = get_landable_repos_for_revision_data(
        stack_data, supported_repos)

    other_checks = get_blocker_checks(
        repositories=supported_repos,
        relman_group_phid=get_relman_group_phid(phab))

    landable, blocked = calculate_landable_subgraphs(stack_data,
                                                     edges,
                                                     landable_repos,
                                                     other_checks=other_checks)
    uplift_repos = [
        name for name, repo in supported_repos.items()
        if repo.approval_required
    ]

    involved_phids = set()
    for revision in stack_data.revisions.values():
        involved_phids.update(gather_involved_phids(revision))

    involved_phids = list(involved_phids)

    users = user_search(phab, involved_phids)
    projects = project_search(phab, involved_phids)

    secure_project_phid = get_secure_project_phid(phab)
    sec_approval_project_phid = get_sec_approval_project_phid(phab)

    revisions_response = []
    for _phid, revision in stack_data.revisions.items():
        revision_phid = PhabricatorClient.expect(revision, "phid")
        fields = PhabricatorClient.expect(revision, "fields")
        diff_phid = PhabricatorClient.expect(fields, "diffPHID")
        diff = stack_data.diffs[diff_phid]
        human_revision_id = "D{}".format(
            PhabricatorClient.expect(revision, "id"))
        revision_url = urllib.parse.urljoin(
            current_app.config["PHABRICATOR_URL"], human_revision_id)
        secure = revision_is_secure(revision, secure_project_phid)
        commit_description = find_title_and_summary_for_display(
            phab, revision, secure)
        bug_id = get_bugzilla_bug(revision)
        reviewers = get_collated_reviewers(revision)
        accepted_reviewers = reviewers_for_commit_message(
            reviewers, users, projects, sec_approval_project_phid)
        commit_message_title, commit_message = format_commit_message(
            commit_description.title,
            bug_id,
            accepted_reviewers,
            commit_description.summary,
            revision_url,
        )
        author_response = serialize_author(phab.expect(fields, "authorPHID"),
                                           users)

        revisions_response.append({
            "id":
            human_revision_id,
            "phid":
            revision_phid,
            "status":
            serialize_status(revision),
            "blocked_reason":
            blocked.get(revision_phid, ""),
            "bug_id":
            bug_id,
            "title":
            commit_description.title,
            "url":
            revision_url,
            "date_created":
            PhabricatorClient.to_datetime(
                PhabricatorClient.expect(revision, "fields",
                                         "dateCreated")).isoformat(),
            "date_modified":
            PhabricatorClient.to_datetime(
                PhabricatorClient.expect(revision, "fields",
                                         "dateModified")).isoformat(),
            "summary":
            commit_description.summary,
            "commit_message_title":
            commit_message_title,
            "commit_message":
            commit_message,
            "repo_phid":
            PhabricatorClient.expect(fields, "repositoryPHID"),
            "diff":
            serialize_diff(diff),
            "author":
            author_response,
            "reviewers":
            serialize_reviewers(reviewers, users, projects, diff_phid),
            "is_secure":
            secure,
            "is_using_secure_commit_message":
            commit_description.sanitized,
        })

    repositories = []
    for phid in stack_data.repositories.keys():
        short_name = PhabricatorClient.expect(stack_data.repositories[phid],
                                              "fields", "shortName")
        repo = supported_repos.get(short_name)
        if repo is None:
            landing_supported, approval_required = False, None
        else:
            landing_supported, approval_required = True, repo.approval_required
        url = ("{phabricator_url}/source/{short_name}".format(
            phabricator_url=current_app.config["PHABRICATOR_URL"],
            short_name=short_name,
        ) if not landing_supported else supported_repos[short_name].url)
        repositories.append({
            "phid": phid,
            "short_name": short_name,
            "url": url,
            "landing_supported": landing_supported,
            "approval_required": approval_required,
        })

    return {
        "repositories": repositories,
        "revisions": revisions_response,
        "edges": [e for e in edges],
        "landable_paths": landable,
        "uplift_repositories": uplift_repos,
    }
Ejemplo n.º 9
0
def get(revision_id):
    """Get the stack a revision is part of.

    Args:
        revision_id: (string) ID of the revision in 'D{number}' format
    """
    revision_id = revision_id_to_int(revision_id)

    phab = g.phabricator
    revision = phab.call_conduit(
        'differential.revision.search',
        constraints={'ids': [revision_id]},
    )
    revision = phab.single(revision, 'data', none_when_empty=True)
    if revision is None:
        return problem(
            404,
            'Revision not found',
            'The requested revision does not exist',
            type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404'
        )

    # TODO: This assumes that all revisions and related objects in the stack
    # have uniform view permissions for the requesting user. Some revisions
    # being restricted could cause this to fail.
    nodes, edges = build_stack_graph(phab, phab.expect(revision, 'phid'))
    stack_data = request_extended_revision_data(phab, [phid for phid in nodes])

    supported_repos = get_repos_for_env(current_app.config.get('ENVIRONMENT'))
    landable_repos = get_landable_repos_for_revision_data(
        stack_data, supported_repos
    )
    landable, blocked = calculate_landable_subgraphs(
        stack_data,
        edges,
        landable_repos,
        other_checks=DEFAULT_OTHER_BLOCKER_CHECKS
    )

    involved_phids = set()
    for revision in stack_data.revisions.values():
        involved_phids.update(gather_involved_phids(revision))

    involved_phids = list(involved_phids)

    users = lazy_user_search(phab, involved_phids)()
    projects = lazy_project_search(phab, involved_phids)()

    revisions_response = []
    for phid, revision in stack_data.revisions.items():
        revision_phid = PhabricatorClient.expect(revision, 'phid')
        fields = PhabricatorClient.expect(revision, 'fields')
        diff_phid = PhabricatorClient.expect(fields, 'diffPHID')
        diff = stack_data.diffs[diff_phid]
        human_revision_id = 'D{}'.format(
            PhabricatorClient.expect(revision, 'id')
        )
        revision_url = urllib.parse.urljoin(
            current_app.config['PHABRICATOR_URL'], human_revision_id
        )
        title = PhabricatorClient.expect(fields, 'title')
        summary = PhabricatorClient.expect(fields, 'summary')
        bug_id = get_bugzilla_bug(revision)
        reviewers = get_collated_reviewers(revision)
        accepted_reviewers = [
            reviewer_identity(phid, users, projects).identifier
            for phid, r in reviewers.items()
            if r['status'] is ReviewerStatus.ACCEPTED
        ]
        commit_message_title, commit_message = format_commit_message(
            title, bug_id, accepted_reviewers, summary, revision_url
        )
        author_response = serialize_author(
            phab.expect(fields, 'authorPHID'), users
        )

        revisions_response.append({
            'id': human_revision_id,
            'phid': revision_phid,
            'status': serialize_status(revision),
            'blocked_reason': blocked.get(revision_phid, ''),
            'bug_id': bug_id,
            'title': title,
            'url': revision_url,
            'date_created': PhabricatorClient.to_datetime(
                PhabricatorClient.expect(revision, 'fields', 'dateCreated')
            ).isoformat(),
            'date_modified': PhabricatorClient.to_datetime(
                PhabricatorClient.expect(revision, 'fields', 'dateModified')
            ).isoformat(),
            'summary': summary,
            'commit_message_title': commit_message_title,
            'commit_message': commit_message,
            'diff': serialize_diff(diff),
            'author': author_response,
            'reviewers': serialize_reviewers(
                reviewers, users, projects, diff_phid
            ),
        })  # yapf: disable

    return {
        'revisions': revisions_response,
        'edges': [e for e in edges],
        'landable_paths': landable,
    }
Ejemplo n.º 10
0
def get(revision_id):
    """Get the stack a revision is part of.

    Args:
        revision_id: (string) ID of the revision in 'D{number}' format
    """
    revision_id = revision_id_to_int(revision_id)

    phab = g.phabricator
    revision = phab.call_conduit("differential.revision.search",
                                 constraints={"ids": [revision_id]})
    revision = phab.single(revision, "data", none_when_empty=True)
    if revision is None:
        return not_found_problem

    try:
        nodes, edges = build_stack_graph(phab, phab.expect(revision, "phid"))
    except PhabricatorAPIException:
        # If a revision within the stack causes an API exception, treat the whole stack
        # as not found.
        return not_found_problem
    stack_data = request_extended_revision_data(phab, [phid for phid in nodes])

    supported_repos = get_repos_for_env(current_app.config.get("ENVIRONMENT"))
    landable_repos = get_landable_repos_for_revision_data(
        stack_data, supported_repos)

    other_checks = get_blocker_checks(
        repositories=supported_repos,
        relman_group_phid=get_relman_group_phid(phab))

    landable, blocked = calculate_landable_subgraphs(stack_data,
                                                     edges,
                                                     landable_repos,
                                                     other_checks=other_checks)
    uplift_repos = [
        name for name, repo in supported_repos.items()
        if repo.approval_required
    ]

    involved_phids = set()
    for revision in stack_data.revisions.values():
        involved_phids.update(gather_involved_phids(revision))

    involved_phids = list(involved_phids)

    users = user_search(phab, involved_phids)
    projects = project_search(phab, involved_phids)

    secure_project_phid = get_secure_project_phid(phab)
    sec_approval_project_phid = get_sec_approval_project_phid(phab)

    revisions_response = []
    for _phid, revision in stack_data.revisions.items():
        revision_phid = PhabricatorClient.expect(revision, "phid")
        fields = PhabricatorClient.expect(revision, "fields")
        diff_phid = PhabricatorClient.expect(fields, "diffPHID")
        diff = stack_data.diffs[diff_phid]
        human_revision_id = "D{}".format(
            PhabricatorClient.expect(revision, "id"))
        revision_url = urllib.parse.urljoin(
            current_app.config["PHABRICATOR_URL"], human_revision_id)
        secure = revision_is_secure(revision, secure_project_phid)
        commit_description = find_title_and_summary_for_display(
            phab, revision, secure)
        bug_id = get_bugzilla_bug(revision)
        reviewers = get_collated_reviewers(revision)
        accepted_reviewers = reviewers_for_commit_message(
            reviewers, users, projects, sec_approval_project_phid)
        commit_message_title, commit_message = format_commit_message(
            commit_description.title,
            bug_id,
            accepted_reviewers,
            commit_description.summary,
            revision_url,
        )
        author_response = serialize_author(phab.expect(fields, "authorPHID"),
                                           users)

        revisions_response.append({
            "id":
            human_revision_id,
            "phid":
            revision_phid,
            "status":
            serialize_status(revision),
            "blocked_reason":
            blocked.get(revision_phid, ""),
            "bug_id":
            bug_id,
            "title":
            commit_description.title,
            "url":
            revision_url,
            "date_created":
            PhabricatorClient.to_datetime(
                PhabricatorClient.expect(revision, "fields",
                                         "dateCreated")).isoformat(),
            "date_modified":
            PhabricatorClient.to_datetime(
                PhabricatorClient.expect(revision, "fields",
                                         "dateModified")).isoformat(),
            "summary":
            commit_description.summary,
            "commit_message_title":
            commit_message_title,
            "commit_message":
            commit_message,
            "repo_phid":
            PhabricatorClient.expect(fields, "repositoryPHID"),
            "diff":
            serialize_diff(diff),
            "author":
            author_response,
            "reviewers":
            serialize_reviewers(reviewers, users, projects, diff_phid),
            "is_secure":
            secure,
            "is_using_secure_commit_message":
            commit_description.sanitized,
        })

    repositories = []
    for phid in stack_data.repositories.keys():
        short_name = PhabricatorClient.expect(stack_data.repositories[phid],
                                              "fields", "shortName")

        repo = supported_repos.get(short_name)
        landing_supported = repo is not None
        url = (repo.url if landing_supported else
               f"{current_app.config['PHABRICATOR_URL']}/source/{short_name}")

        repositories.append({
            "approval_required": landing_supported and repo.approval_required,
            "commit_flags": repo.commit_flags if repo else [],
            "landing_supported": landing_supported,
            "phid": phid,
            "short_name": short_name,
            "url": url,
        })

    return {
        "repositories": repositories,
        "revisions": revisions_response,
        "edges": [e for e in edges],
        "landable_paths": landable,
        "uplift_repositories": uplift_repos,
    }