Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lando-api: merge fixes from Flask LandoAPI (Bug 1944562) #224

Open
wants to merge 33 commits into
base: api-merge-without-fixes
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 90 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3bb0034
Merge remote-tracking branch 'lando-api-repo/main'
cgsheeh Jan 31, 2025
edabe2c
port tokens_are_equal removal
cgsheeh Jan 31, 2025
6da06ad
remove double import of canned responses
cgsheeh Jan 31, 2025
c87d300
re-add TreeStatusDouble
cgsheeh Jan 31, 2025
dde9c62
remove old import
cgsheeh Jan 31, 2025
de5fc17
mark several tests with django_db
cgsheeh Jan 31, 2025
2c70694
mark many tests as requiring django_db
cgsheeh Jan 31, 2025
5905eb7
add some missing test fixtures
cgsheeh Jan 31, 2025
245ac69
update imports in mocks
cgsheeh Jan 31, 2025
4dd8998
fix test fixtures and add django_db
cgsheeh Jan 31, 2025
2e0a307
add some missing imports to stacks.py
cgsheeh Jan 31, 2025
40fcff1
port new transplant tests
cgsheeh Jan 31, 2025
b02aacd
fix a test
cgsheeh Jan 31, 2025
34f498f
fix another test
cgsheeh Jan 31, 2025
4c04b15
remove auth0_mock from some tests
cgsheeh Jan 31, 2025
bf15f0c
add missing mocked_repo_config fixture
cgsheeh Jan 31, 2025
7858185
remove app fixture
cgsheeh Jan 31, 2025
5334734
fix formatting
cgsheeh Jan 31, 2025
ce9620f
add mocked_repo_config to various places
cgsheeh Feb 4, 2025
718b2c7
update snippets
cgsheeh Feb 4, 2025
7f67826
check message is in blocker
cgsheeh Feb 5, 2025
58897f4
remove check of title since it is removed
cgsheeh Feb 5, 2025
f61e7c8
remove Flask migrations dir
cgsheeh Feb 6, 2025
daaf123
Merge remote-tracking branch 'origin/main' into api-merge
cgsheeh Feb 7, 2025
d9e3ae1
Merge remote-tracking branch 'lando-api-repo/main' into zeid/bug-1944…
zzzeid Feb 19, 2025
6a2dfe7
Merge branch 'zeid/bug-1944562-merge-lando-api' into zeid/bug-1944562…
zzzeid Feb 19, 2025
64fb2fb
remove unused file
cgsheeh Feb 24, 2025
869704b
Merge remote-tracking branch 'lando-api-repo/main' into zeid/bug-1944…
cgsheeh Feb 19, 2025
17d8651
Merge branch 'api-merge-without-fixes' into api-merge
cgsheeh Feb 25, 2025
cd89b13
Merge remote-tracking branch 'lando-api-repo/main' into zeid/bug-1944…
cgsheeh Feb 19, 2025
86e0ddc
Merge remote-tracking branch 'origin/api-merge-without-fixes' into ap…
cgsheeh Feb 26, 2025
492c78d
Merge remote-tracking branch 'origin/api-merge-without-fixes' into ap…
cgsheeh Feb 28, 2025
77f1d90
set landing_repo in `blocker_single_landing_repo` for consistency
cgsheeh Feb 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 113 additions & 108 deletions requirements.txt

Large diffs are not rendered by default.

43 changes: 29 additions & 14 deletions src/lando/api/legacy/api/stacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from lando.api.legacy.commit_message import format_commit_message
from lando.api.legacy.projects import (
get_data_policy_review_phid,
get_release_managers,
get_sec_approval_project_phid,
get_secure_project_phid,
Expand All @@ -27,12 +28,15 @@
serialize_status,
)
from lando.api.legacy.stacks import (
RevisionStack,
build_stack_graph,
calculate_landable_subgraphs,
get_landable_repos_for_revision_data,
get_diffs_for_revision,
request_extended_revision_data,
)
from lando.api.legacy.transplants import get_blocker_checks
from lando.api.legacy.transplants import (
build_stack_assessment_state,
run_landing_checks,
)
from lando.api.legacy.users import user_search
from lando.main.auth import require_phabricator_api_key
from lando.main.models import Repo
Expand Down Expand Up @@ -65,30 +69,39 @@ def get(phab: PhabricatorClient, request: HttpRequest, revision_id: int):
raise Http404(HTTP_404_STRING)

supported_repos = Repo.get_mapping()
landable_repos = get_landable_repos_for_revision_data(stack_data, supported_repos)

release_managers = get_release_managers(phab)
if not release_managers:
raise Exception("Could not find `#release-managers` project on Phabricator.")

relman_group_phid = str(phab.expect(release_managers, "phid"))
data_policy_review_phid = get_data_policy_review_phid(phab)
if not data_policy_review_phid:
raise Exception(
"Could not find `#needs-data-classification` project on Phabricator."
)

other_checks = get_blocker_checks(
relman_group_phid=relman_group_phid,
repositories=supported_repos,
stack_data=stack_data,
)
relman_group_phid = str(phab.expect(release_managers, "phid"))

landable, blocked = calculate_landable_subgraphs(
stack_data, edges, landable_repos, other_checks=other_checks
stack = RevisionStack(set(stack_data.revisions.keys()), edges)
stack_state = build_stack_assessment_state(
phab,
supported_repos,
stack_data,
stack,
relman_group_phid,
data_policy_review_phid,
)
# Run landing checks and update the stack state.
run_landing_checks(stack_state)
landable = stack_state.landable_stack.landable_paths()
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))
revision_diffs = get_diffs_for_revision(revision, stack_data.diffs)
involved_phids.update(gather_involved_phids(revision, revision_diffs))

involved_phids = list(involved_phids)

Expand Down Expand Up @@ -154,12 +167,14 @@ def get(phab: PhabricatorClient, request: HttpRequest, revision_id: int):
)
author_response = serialize_author(phab.expect(fields, "authorPHID"), users)

blocked_reasons = stack_state.stack.nodes[revision_phid].get("blocked")

revisions_response.append(
{
"id": human_revision_id,
"phid": revision_phid,
"status": serialize_status(phab_revision),
"blocked_reason": blocked.get(revision_phid, ""),
"blocked_reasons": blocked_reasons,
"bug_id": bug_id,
"title": commit_description.title,
"url": revision_url,
Expand Down
167 changes: 63 additions & 104 deletions src/lando/api/legacy/api/transplants.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import logging
import urllib.parse
from datetime import datetime
from typing import Optional

import kombu
from django.conf import settings
from django.contrib.auth.models import User
from django.http import HttpRequest

from lando.api.legacy.api.stacks import HTTP_404_STRING
from lando.api.legacy.commit_message import format_commit_message
from lando.api.legacy.projects import (
CHECKIN_PROJ_SLUG,
get_checkin_project_phid,
get_data_policy_review_phid,
get_release_managers,
get_sec_approval_project_phid,
get_secure_project_phid,
get_testing_policy_phid,
get_testing_tag_project_phids,
project_search,
)
from lando.api.legacy.reviews import (
Expand All @@ -34,18 +31,16 @@
select_diff_author,
)
from lando.api.legacy.stacks import (
RevisionData,
RevisionStack,
build_stack_graph,
calculate_landable_subgraphs,
get_landable_repos_for_revision_data,
get_diffs_for_revision,
request_extended_revision_data,
)
from lando.api.legacy.transplants import (
TransplantAssessment,
check_landing_blockers,
check_landing_warnings,
convert_path_id_to_phid,
get_blocker_checks,
LandingAssessmentState,
StackAssessment,
build_stack_assessment_state,
run_landing_checks,
)
from lando.api.legacy.users import user_search
from lando.api.legacy.validation import (
Expand Down Expand Up @@ -118,90 +113,6 @@ def _find_stack_from_landing_path(
return build_stack_graph(revision)


def _assess_transplant_request(
phab: PhabricatorClient,
lando_user: User,
landing_path: list[tuple[int, int]],
relman_group_phid: str,
) -> tuple[
TransplantAssessment,
Optional[list[tuple[dict, dict]]],
Optional[Repo],
Optional[RevisionData],
]:
nodes, edges = _find_stack_from_landing_path(phab, landing_path)
stack_data = request_extended_revision_data(phab, list(nodes))
landing_path_phid = convert_path_id_to_phid(landing_path, stack_data)

supported_repos = Repo.get_mapping()
landable_repos = get_landable_repos_for_revision_data(stack_data, supported_repos)

other_checks = get_blocker_checks(
repositories=supported_repos,
relman_group_phid=relman_group_phid,
stack_data=stack_data,
)

landable, blocked = calculate_landable_subgraphs(
stack_data, edges, landable_repos, other_checks=other_checks
)

assessment = check_landing_blockers(
lando_user, landing_path_phid, 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_phid

# 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"]]
set_repo = landable_repos[repo["phid"]]

# If the landing repo on the current revision is set as a legacy repo
# of another repo, then change the landing repo to match the target.
landing_repo = set_repo if not set_repo.is_legacy else set_repo.new_target

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

involved_phids = list(involved_phids)
users = user_search(phab, involved_phids)
projects = project_search(phab, involved_phids)
reviewers = {
revision["phid"]: get_collated_reviewers(revision) for revision, _ in to_land
}

assessment = check_landing_warnings(
phab,
lando_user,
to_land,
repo,
landing_repo,
reviewers,
users,
projects,
get_secure_project_phid(phab),
get_testing_tag_project_phids(phab),
get_testing_policy_phid(phab),
)
return (assessment, to_land, landing_repo, stack_data)


@require_authenticated_user
@require_phabricator_api_key(optional=True)
def dryrun(phab: PhabricatorClient, request: HttpRequest, data: dict):
Expand All @@ -212,10 +123,31 @@ def dryrun(phab: PhabricatorClient, request: HttpRequest, data: dict):
if not release_managers:
raise Exception("Could not find `#release-managers` project on Phabricator.")

data_policy_review_phid = get_data_policy_review_phid(phab)
if not data_policy_review_phid:
raise Exception(
"Could not find `#needs-data-classification` project on Phabricator."
)

supported_repos = Repo.get_mapping()

relman_group_phid = phab.expect(release_managers, "phid")
assessment, *_ = _assess_transplant_request(
phab, lando_user, landing_path, relman_group_phid
nodes, edges = _find_stack_from_landing_path(phab, landing_path)
stack_data = request_extended_revision_data(phab, list(nodes))
stack = RevisionStack(set(stack_data.revisions.keys()), edges)
landing_assessment = LandingAssessmentState.from_landing_path(
landing_path, stack_data, lando_user
)
stack_state = build_stack_assessment_state(
phab,
supported_repos,
stack_data,
stack,
relman_group_phid,
data_policy_review_phid,
landing_assessment=landing_assessment,
)
assessment = run_landing_checks(stack_state)
return assessment.to_dict()


Expand All @@ -241,10 +173,36 @@ def post(phab: PhabricatorClient, request: HttpRequest, data: dict):
if not release_managers:
raise Exception("Could not find `#release-managers` project on Phabricator.")

data_policy_review_phid = get_data_policy_review_phid(phab)
if not data_policy_review_phid:
raise Exception(
"Could not find `#needs-data-classification` project on Phabricator."
)

relman_group_phid = phab.expect(release_managers, "phid")

assessment, to_land, landing_repo, stack_data = _assess_transplant_request(
phab, lando_user, landing_path, relman_group_phid
supported_repos = Repo.get_mapping()

nodes, edges = _find_stack_from_landing_path(phab, landing_path)
stack_data = request_extended_revision_data(phab, list(nodes))
stack = RevisionStack(set(stack_data.revisions.keys()), edges)

landing_assessment = LandingAssessmentState.from_landing_path(
landing_path, stack_data, request.user
)
stack_state = build_stack_assessment_state(
phab,
supported_repos,
stack_data,
stack,
relman_group_phid,
data_policy_review_phid,
landing_assessment=landing_assessment,
)
assessment = run_landing_checks(stack_state)
to_land, landing_repo = (
landing_assessment.to_land,
landing_assessment.landing_repo,
)

assessment.raise_if_blocked_or_unacknowledged(confirmation_token)
Expand Down Expand Up @@ -282,7 +240,8 @@ def post(phab: PhabricatorClient, request: HttpRequest, data: dict):
revisions = [r[0] for r in to_land]

for revision in revisions:
involved_phids.update(gather_involved_phids(revision))
revision_diffs = get_diffs_for_revision(revision, stack_data.diffs)
involved_phids.update(gather_involved_phids(revision, revision_diffs))

involved_phids = list(involved_phids)
users = user_search(phab, involved_phids)
Expand Down Expand Up @@ -370,10 +329,10 @@ def post(phab: PhabricatorClient, request: HttpRequest, data: dict):

ldap_username = lando_user.email

submitted_assessment = TransplantAssessment(
blocker=(
submitted_assessment = StackAssessment(
blockers=[
"This stack was submitted for landing by another user at the same time."
)
]
)
stack_ids = [revision.revision_id for revision in lando_revisions]
with LandingJob.lock_table:
Expand Down
Loading