Skip to content

Commit

Permalink
Add metrics for GH Trusted Publishers with reusable workflows (#16364)
Browse files Browse the repository at this point in the history
* Add metrics for GH Trusted Publishers with reusable workflows

* Add comment explaining metrics collection

---------

Co-authored-by: William Woodruff <[email protected]>
  • Loading branch information
facutuesca and woodruffw authored Aug 9, 2024
1 parent 94cd484 commit cc96064
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 2 deletions.
103 changes: 102 additions & 1 deletion tests/unit/oidc/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@
import pytest

from tests.common.db.accounts import UserFactory
from tests.common.db.oidc import GitHubPublisherFactory, PendingGitHubPublisherFactory
from tests.common.db.oidc import (
GitHubPublisherFactory,
GitLabPublisherFactory,
PendingGitHubPublisherFactory,
)
from tests.common.db.packaging import ProhibitedProjectFactory, ProjectFactory
from warehouse.events.tags import EventTag
from warehouse.macaroons import caveats
from warehouse.macaroons.interfaces import IMacaroonService
from warehouse.metrics import IMetricsService
from warehouse.oidc import errors, views
from warehouse.oidc.interfaces import IOIDCPublisherService
from warehouse.packaging import services
Expand Down Expand Up @@ -677,3 +682,99 @@ def test_mint_token_with_invalid_name_fails(
assert err["description"] == (
f"The name {pending_publisher.project_name!r} is invalid."
)


@pytest.mark.parametrize(
("claims_in_token", "is_reusable", "is_github"),
[
(
{
"ref": "someref",
"sha": "somesha",
"workflow_ref": "org/repo/.github/workflows/parent.yml@someref",
"job_workflow_ref": "org2/repo2/.github/workflows/reusable.yml@v1",
},
True,
True,
),
(
{
"ref": "someref",
"sha": "somesha",
"workflow_ref": "org/repo/.github/workflows/workflow.yml@someref",
"job_workflow_ref": "org/repo/.github/workflows/workflow.yml@someref",
},
False,
True,
),
(
{
"ref": "someref",
"sha": "somesha",
},
False,
False,
),
],
)
def test_mint_token_github_reusable_workflow_metrics(
monkeypatch,
db_request,
claims_in_token,
is_reusable,
is_github,
dummy_github_oidc_jwt,
metrics,
):
time = pretend.stub(time=pretend.call_recorder(lambda: 0))
monkeypatch.setattr(views, "time", time)

project = pretend.stub(
id="fakeprojectid",
record_event=pretend.call_recorder(lambda **kw: None),
)

publisher = GitHubPublisherFactory() if is_github else GitLabPublisherFactory()
monkeypatch.setattr(publisher.__class__, "projects", [project])
publisher.publisher_url = pretend.call_recorder(lambda **kw: "https://fake/url")
# NOTE: Can't set __str__ using pretend.stub()
monkeypatch.setattr(publisher.__class__, "__str__", lambda s: "fakespecifier")

def _find_publisher(claims, pending=False):
if pending:
return None
else:
return publisher

oidc_service = pretend.stub(
verify_jwt_signature=pretend.call_recorder(lambda token: claims_in_token),
find_publisher=pretend.call_recorder(_find_publisher),
)

db_macaroon = pretend.stub(description="fakemacaroon")
macaroon_service = pretend.stub(
create_macaroon=pretend.call_recorder(
lambda *a, **kw: ("raw-macaroon", db_macaroon)
)
)

def find_service(iface, **kw):
if iface == IOIDCPublisherService:
return oidc_service
elif iface == IMacaroonService:
return macaroon_service
elif iface == IMetricsService:
return metrics
assert False, iface

monkeypatch.setattr(db_request, "find_service", find_service)
monkeypatch.setattr(db_request, "domain", "fakedomain")

views.mint_token(oidc_service, dummy_github_oidc_jwt, db_request)

if is_reusable:
assert metrics.increment.calls == [
pretend.call("warehouse.oidc.mint_token.github_reusable_workflow"),
]
else:
assert not metrics.increment.calls
20 changes: 19 additions & 1 deletion warehouse/oidc/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from warehouse.metrics.interfaces import IMetricsService
from warehouse.oidc.errors import InvalidPublisherError
from warehouse.oidc.interfaces import IOIDCPublisherService
from warehouse.oidc.models import OIDCPublisher, PendingOIDCPublisher
from warehouse.oidc.models import GitHubPublisher, OIDCPublisher, PendingOIDCPublisher
from warehouse.oidc.services import OIDCPublisherService
from warehouse.oidc.utils import OIDC_ISSUER_ADMIN_FLAGS, OIDC_ISSUER_SERVICE_NAMES
from warehouse.packaging.interfaces import IProjectService
Expand Down Expand Up @@ -284,4 +284,22 @@ def mint_token(
"publisher_url": publisher.publisher_url(),
},
)

# NOTE: This is for temporary metrics collection of GitHub Trusted Publishers
# that use reusable workflows. Since support for reusable workflows is accidental
# and not correctly implemented, we need to understand how widely it's being
# used before changing its behavior.
# ref: https://github.com/pypi/warehouse/pull/16364
if isinstance(publisher, GitHubPublisher) and claims:
job_workflow_ref = claims.get("job_workflow_ref")
workflow_ref = claims.get("workflow_ref")

# When using reusable workflows, `job_workflow_ref` contains the reusable (
# called) workflow and `workflow_ref` contains the parent (caller) workflow.
# With non-reusable workflows they are the same, so we count reusable
# workflows by checking if they are different.
if job_workflow_ref and workflow_ref and job_workflow_ref != workflow_ref:
metrics = request.find_service(IMetricsService, context=None)
metrics.increment("warehouse.oidc.mint_token.github_reusable_workflow")

return {"success": True, "token": serialized}

0 comments on commit cc96064

Please sign in to comment.