예제 #1
0
def check_approval_state(
    phab: PhabricatorClient, revision_id: int, target_repository_name: str
) -> dict:
    """Helper to load the Phabricator revision and check its approval requirement state

    * if the revision's target repository is the same as its current
      repository, it's an approval
    * otherwise it's an uplift request
    """

    # Load target repo from Phabricator
    target_repo = phab.call_conduit(
        "diffusion.repository.search",
        constraints={"shortNames": [target_repository_name]},
    )
    target_repo = phab.single(target_repo, "data")
    target_repo_phid = phab.expect(target_repo, "phid")

    # Load base revision details from Phabricator
    revision = phab.call_conduit(
        "differential.revision.search", constraints={"ids": [revision_id]}
    )
    revision = phab.single(revision, "data")
    revision_repo_phid = phab.expect(revision, "fields", "repositoryPHID")

    # Lookup if this is an uplift or an approval request
    is_approval = target_repo_phid == revision_repo_phid
    return (is_approval, revision, target_repo)
예제 #2
0
def admin_remove_phab_project(revision_phid: str,
                              project_phid: str,
                              comment: Optional[str] = None):
    """Remove a project tag from the provided revision.

    Note, this uses administrator privileges and should only be called
    if permissions checking is handled elsewhere.

    Args:
        revision_phid: phid of the revision to remove the project tag from.
        project_phid: phid of the project to remove.
        comment: An optional comment to add when removing the project.
    """
    transactions = [{"type": "projects.remove", "value": [project_phid]}]
    if comment is not None:
        transactions.append({"type": "comment", "value": comment})

    privileged_phab = PhabricatorClient(
        current_app.config["PHABRICATOR_URL"],
        current_app.config["PHABRICATOR_ADMIN_API_KEY"],
    )
    # We only retry for PhabricatorCommunicationException, rather than the
    # base PhabricatorAPIException to treat errors in this implementation as
    # fatal.
    privileged_phab.call_conduit(
        "differential.revision.edit",
        objectIdentifier=revision_phid,
        transactions=transactions,
    )
예제 #3
0
def create_approval_request(phab: PhabricatorClient, revision: dict, form_content: str):
    """Update an existing revision with reviewers & form comment"""
    release_managers = get_release_managers(phab)

    rev = phab.call_conduit(
        "differential.revision.edit",
        objectIdentifier=revision["phid"],
        transactions=[
            # 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},
        ],
    )
    rev_id = phab.expect(rev, "object", "id")
    rev_phid = phab.expect(rev, "object", "phid")
    assert rev_id == revision["id"], "Revision id mismatch"

    logger.info("Updated Phabricator revision", extra={"id": rev_id, "phid": rev_phid})

    return {
        "mode": "approval",
        "url": f"{phab.url_base}/D{rev_id}",
        "revision_id": rev_id,
        "revision_phid": rev_phid,
    }
예제 #4
0
def heartbeat():
    """Perform an in-depth service health check.

    This should check all the services that this service depends on
    and return a 200 iff those services and the app itself are
    performing normally. Return a 5XX if something goes wrong.
    """
    phab = PhabricatorClient(
        current_app.config['PHABRICATOR_URL'],
        current_app.config['PHABRICATOR_UNPRIVILEGED_API_KEY'])
    try:
        phab.call_conduit('conduit.ping')
    except PhabricatorAPIException:
        logger.warning({
            'msg': 'problem connecting to Phabricator',
        }, 'heartbeat')
        return 'heartbeat: problem', 502
    logger.info({'msg': 'ok, all services are up'}, 'heartbeat')
    return 'heartbeat: ok', 200
예제 #5
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,
    }
예제 #6
0
def get_release_managers(phab: PhabricatorClient) -> dict:
    """Load the release-managers group details from Phabricator"""
    groups = phab.call_conduit(
        "project.search", constraints={"slugs": ["release-managers"]}
    )
    return phab.single(groups, "data")