Beispiel #1
0
    def get_revision(self, revision_id, diff_id=None):
        """Queries Lando API's GET /revisions/{id} endpoint.

        Args:
            revision_id: The revision id to lookup in 'D123' format.
            diff_id: A specific diff id belonging to the revision whose info
                 will be included in the response. If None, then the most
                 recent diff of the revision will be included.

        Returns:
            If successful, a dictionary containing the revision information.
            Does not return if unsuccessful.

        Exceptions:
            If the revision/diff combination is not found or the user does not
            have permission to view it, then an errorhandlers.RevisionNotFound
            exception will be raised. If this is not caught, a default handler
            will show a 404 page.

            If there are any other exceptions with communicating with lando-api
            and creating the response then an errorhandlers.UIError will be
            raised. If this is not caught, a default handler will show an error
            message page.
        """
        get_revision_url = '{host}/revisions/{revision_id}'.format(
            host=self.landoapi_url, revision_id=revision_id)

        try:
            headers = {
                'X-Phabricator-API-Key': self.phabricator_api_token
            } if self.phabricator_api_token else None

            response = requests.get(get_revision_url,
                                    params={'diff_id': diff_id},
                                    headers=headers)

            if response.status_code == 404:
                raise RevisionNotFound(revision_id, diff_id)
            else:
                response.raise_for_status()
                return response.json()
        except (requests.RequestException, json.JSONDecodeError) as e:
            logger.exception('Exception querying revision',
                             extra={
                                 'url': get_revision_url,
                                 'revision_id': revision_id,
                                 'diff_id': diff_id,
                             })

            error_details = {
                'title': 'Failed to reach Lando API',
                'message': (
                    'Lando API is unable to process the request right now. '
                    'Try again later.'
                ),
            }  # yapf: disable
            if hasattr(e, 'response') and hasattr(e.response, 'status_code'):
                error_details['status_code'] = e.response.status_code
            raise UIError(**error_details)
Beispiel #2
0
    def get_landings(self, revision_id):
        """Queries Lando API's GET /landings endpoint.

        Args:
            revision_id: The revision id, in 'D123' format, to filter landings
                by.

        Returns:
            An array of dictionaries for each landing. Or, an empty array if
            no landings are found. Does not return if unsuccessful in making
            the request and getting a valid response.

        Exceptions:
            If the revision does not exist or the user does not have permission
            to view it, then an errorhandlers.RevisionNotFound exception will
            be raised. If this is not caught, a default handler will show a 404
            page.

            If there are any other exceptions with communicating with lando-api
            and creating the response then the landoui.errorhandlers.UIError
            will be raised. If this is not caught, a default handler will show
            a default error message page.
        """
        get_landings_url = '{host}/landings'.format(host=self.landoapi_url)

        try:
            headers = {
                'X-Phabricator-API-Key': self.phabricator_api_token
            } if self.phabricator_api_token else None

            response = requests.get(get_landings_url,
                                    params={'revision_id': revision_id},
                                    headers=headers)

            if response.status_code == 404:
                raise RevisionNotFound(revision_id)
            else:
                response.raise_for_status()
                return response.json()
        except (requests.RequestException, json.JSONDecodeError) as e:
            logger.exception('Exception querying landings',
                             extra={
                                 'url': get_landings_url,
                                 'revision_id': revision_id,
                             })

            error_details = {
                'title': 'Failed to reach Lando API',
                'message': (
                    'Lando API is unable to process the request right now. '
                    'Try again later.'
                ),
            }  # yapf: disable
            if hasattr(e, 'response') and hasattr(e.response, 'status_code'):
                error_details['status_code'] = e.response.status_code
            raise UIError(**error_details)
Beispiel #3
0
 def bad_route():
     raise RevisionNotFound("D9000")
Beispiel #4
0
 def bad_route():
     raise RevisionNotFound('D9000')
Beispiel #5
0
def revision(revision_id):
    api = LandoAPI(current_app.config['LANDO_API_URL'],
                   auth0_access_token=session.get('access_token'),
                   phabricator_api_token=get_phabricator_api_token())

    form = TransplantRequestForm()
    errors = []
    if form.is_submitted():
        if not is_user_authenticated():
            errors.append('You must be logged in to request a landing')

        elif not form.validate():
            for _, field_errors in form.errors.items():
                errors.extend(field_errors)

        else:
            try:
                api.request('POST',
                            'transplants',
                            require_auth0=True,
                            json={
                                'landing_path':
                                json.loads(form.landing_path.data),
                                'confirmation_token':
                                form.confirmation_token.data,
                            })
                # We don't actually need any of the data from the
                # the submission. As long as an exception wasn't
                # raised we're successful.
                return redirect(
                    url_for('revisions.revision', revision_id=revision_id))
            except LandoAPIError as e:
                if not e.detail:
                    raise

                errors.append(e.detail)

    # Request the entire stack.
    try:
        stack = api.request('GET', 'stacks/D{}'.format(revision_id))
    except LandoAPIError as e:
        if e.status_code == 404:
            raise RevisionNotFound(revision_id)
        else:
            raise

    # Build a mapping from phid to revision and identify
    # the data for the revision used to load this page.
    revision = None
    revisions = {}
    for r in stack['revisions']:
        revisions[r['phid']] = r
        if r['id'] == 'D{}'.format(revision_id):
            revision = r['phid']

    # Request all previous transplants for the stack.
    transplants = api.request(
        'GET',
        'transplants',
        params={'stack_revision_id': 'D{}'.format(revision_id)})

    # TODO: support displaying the full DAG, and landing *past* the
    # current revision.
    #
    # The revision may appear in many `landable_paths`` if it has
    # multiple children, or any of its landable descendents have
    # multiple children. That being said, there should only be a
    # single unique path up to this revision, so find the first
    # it appears in. The revisions up to the target one in this
    # path form the landable series.
    series = None
    for p in stack['landable_paths']:
        try:
            series = p[:p.index(revision) + 1]
            break
        except ValueError:
            pass

    dryrun = None
    if series and is_user_authenticated():
        landing_path = [{
            'revision_id': revisions[phid]['id'],
            'diff_id': revisions[phid]['diff']['id'],
        } for phid in series]
        form.landing_path.data = json.dumps(landing_path)

        dryrun = api.request('POST',
                             'transplants/dryrun',
                             require_auth0=True,
                             json={'landing_path': landing_path})
        form.confirmation_token.data = dryrun.get('confirmation_token')

        series = list(reversed(series))

    return render_template(
        'stack/stack.html',
        revision_id='D{}'.format(revision_id),
        series=series,
        dryrun=dryrun,
        stack=stack,
        transplants=transplants,
        revisions=revisions,
        revision_phid=revision,
        errors=errors,
        form=form,
    )
Beispiel #6
0
def revision(revision_id):
    api = LandoAPI(
        current_app.config["LANDO_API_URL"],
        auth0_access_token=session.get("access_token"),
        phabricator_api_token=get_phabricator_api_token(),
    )

    form = TransplantRequestForm()
    sec_approval_form = SecApprovalRequestForm()

    errors = []
    if form.is_submitted():
        if not is_user_authenticated():
            errors.append("You must be logged in to request a landing")

        elif not form.validate():
            for _, field_errors in form.errors.items():
                errors.extend(field_errors)

        else:
            try:
                api.request(
                    "POST",
                    "transplants",
                    require_auth0=True,
                    json={
                        "landing_path": json.loads(form.landing_path.data),
                        "confirmation_token": form.confirmation_token.data,
                    },
                )
                # We don't actually need any of the data from the
                # the submission. As long as an exception wasn't
                # raised we're successful.
                return redirect(url_for("revisions.revision", revision_id=revision_id))
            except LandoAPIError as e:
                if not e.detail:
                    raise

                errors.append(e.detail)

    # Request the entire stack.
    try:
        stack = api.request("GET", "stacks/D{}".format(revision_id))
    except LandoAPIError as e:
        if e.status_code == 404:
            raise RevisionNotFound(revision_id)
        else:
            raise

    # Build a mapping from phid to revision and identify
    # the data for the revision used to load this page.
    revision = None
    revisions = {}
    for r in stack["revisions"]:
        revisions[r["phid"]] = r
        if r["id"] == "D{}".format(revision_id):
            revision = r["phid"]

    # Build a mapping from phid to repository.
    repositories = {}
    for r in stack["repositories"]:
        repositories[r["phid"]] = r

    # Request all previous transplants for the stack.
    transplants = api.request(
        "GET", "transplants", params={"stack_revision_id": "D{}".format(revision_id)}
    )

    # The revision may appear in many `landable_paths`` if it has
    # multiple children, or any of its landable descendents have
    # multiple children. That being said, there should only be a
    # single unique path up to this revision, so find the first
    # it appears in. The revisions up to the target one in this
    # path form the landable series.
    #
    # We also form a set of all the revisions that are landable
    # so we can present selection for what to land.
    series = None
    landable = set()
    for p in stack["landable_paths"]:
        for phid in p:
            landable.add(phid)

        try:
            series = p[: p.index(revision) + 1]
        except ValueError:
            pass

    dryrun = None
    target_repo = None
    if series and is_user_authenticated():
        landing_path = [
            {
                "revision_id": revisions[phid]["id"],
                "diff_id": revisions[phid]["diff"]["id"],
            }
            for phid in series
        ]
        form.landing_path.data = json.dumps(landing_path)

        dryrun = api.request(
            "POST",
            "transplants/dryrun",
            require_auth0=True,
            json={"landing_path": landing_path},
        )
        form.confirmation_token.data = dryrun.get("confirmation_token")

        series = list(reversed(series))
        target_repo = repositories.get(revisions[series[0]]["repo_phid"])

    phids = set(revisions.keys())
    edges = set(Edge(child=e[0], parent=e[1]) for e in stack["edges"])
    order = sort_stack_topological(
        phids, edges, key=lambda x: int(revisions[x]["id"][1:])
    )
    drawing_width, drawing_rows = draw_stack_graph(phids, edges, order)

    annotate_sec_approval_workflow_info(revisions)

    # Are we showing the "sec-approval request submitted" dialog?
    # If we are then fill in its values.
    submitted_revision = request.args.get("show_approval_success")
    submitted_rev_url = None
    if submitted_revision:
        for rev in revisions.values():
            if rev["id"] == submitted_revision:
                submitted_rev_url = rev["url"]
                break

    return render_template(
        "stack/stack.html",
        revision_id="D{}".format(revision_id),
        series=series,
        landable=landable,
        dryrun=dryrun,
        stack=stack,
        rows=list(zip(reversed(order), reversed(drawing_rows))),
        drawing_width=drawing_width,
        transplants=transplants,
        revisions=revisions,
        revision_phid=revision,
        sec_approval_form=sec_approval_form,
        submitted_rev_url=submitted_rev_url,
        target_repo=target_repo,
        errors=errors,
        form=form,
    )
def revision(revision_id):
    api = LandoAPI(current_app.config['LANDO_API_URL'],
                   auth0_access_token=session.get('access_token'),
                   phabricator_api_token=get_phabricator_api_token())

    form = TransplantRequestForm()
    errors = []
    if form.is_submitted():
        if not is_user_authenticated():
            errors.append('You must be logged in to request a landing')

        elif not form.validate():
            for _, field_errors in form.errors.items():
                errors.extend(field_errors)

        else:
            try:
                api.request('POST',
                            'transplants',
                            require_auth0=True,
                            json={
                                'landing_path':
                                json.loads(form.landing_path.data),
                                'confirmation_token':
                                form.confirmation_token.data,
                            })
                # We don't actually need any of the data from the
                # the submission. As long as an exception wasn't
                # raised we're successful.
                return redirect(
                    url_for('revisions.revision', revision_id=revision_id))
            except LandoAPIError as e:
                if not e.detail:
                    raise

                errors.append(e.detail)

    # Request the entire stack.
    try:
        stack = api.request('GET', 'stacks/D{}'.format(revision_id))
    except LandoAPIError as e:
        if e.status_code == 404:
            raise RevisionNotFound(revision_id)
        else:
            raise

    # Build a mapping from phid to revision and identify
    # the data for the revision used to load this page.
    revision = None
    revisions = {}
    for r in stack['revisions']:
        revisions[r['phid']] = r
        if r['id'] == 'D{}'.format(revision_id):
            revision = r['phid']

    # Build a mapping from phid to repository.
    repositories = {}
    for r in stack['repositories']:
        repositories[r['phid']] = r

    # Request all previous transplants for the stack.
    transplants = api.request(
        'GET',
        'transplants',
        params={'stack_revision_id': 'D{}'.format(revision_id)})

    # The revision may appear in many `landable_paths`` if it has
    # multiple children, or any of its landable descendents have
    # multiple children. That being said, there should only be a
    # single unique path up to this revision, so find the first
    # it appears in. The revisions up to the target one in this
    # path form the landable series.
    #
    # We also form a set of all the revisions that are landable
    # so we can present selection for what to land.
    series = None
    landable = set()
    for p in stack['landable_paths']:
        for phid in p:
            landable.add(phid)

        try:
            series = p[:p.index(revision) + 1]
        except ValueError:
            pass

    dryrun = None
    target_repo = None
    if series and is_user_authenticated():
        landing_path = [{
            'revision_id': revisions[phid]['id'],
            'diff_id': revisions[phid]['diff']['id'],
        } for phid in series]
        form.landing_path.data = json.dumps(landing_path)

        dryrun = api.request('POST',
                             'transplants/dryrun',
                             require_auth0=True,
                             json={'landing_path': landing_path})
        form.confirmation_token.data = dryrun.get('confirmation_token')

        series = list(reversed(series))
        target_repo = repositories.get(revisions[series[0]]['repo_phid'])

    phids = set(revisions.keys())
    edges = set(Edge(child=e[0], parent=e[1]) for e in stack['edges'])
    order = sort_stack_topological(phids,
                                   edges,
                                   key=lambda x: int(revisions[x]['id'][1:]))
    drawing_width, drawing_rows = draw_stack_graph(phids, edges, order)

    return render_template(
        'stack/stack.html',
        revision_id='D{}'.format(revision_id),
        series=series,
        landable=landable,
        dryrun=dryrun,
        stack=stack,
        rows=list(zip(reversed(order), reversed(drawing_rows))),
        drawing_width=drawing_width,
        transplants=transplants,
        revisions=revisions,
        revision_phid=revision,
        target_repo=target_repo,
        errors=errors,
        form=form,
    )