Skip to content

feat(integrations): Add bulk code mappings API endpoint#109785

Merged
romtsn merged 20 commits intomasterfrom
rz/feat/bulk-code-mappings-endpoint
Mar 17, 2026
Merged

feat(integrations): Add bulk code mappings API endpoint#109785
romtsn merged 20 commits intomasterfrom
rz/feat/bulk-code-mappings-endpoint

Conversation

@romtsn
Copy link
Copy Markdown
Member

@romtsn romtsn commented Mar 3, 2026

Summary

  • Add POST /api/0/organizations/{org}/code-mappings/bulk/ endpoint that accepts human-readable identifiers (project slug, repo name) instead of internal IDs
  • Uses update_or_create loop for best-effort upsert with per-mapping status reporting
  • Supports up to 300 mappings per request, enabling sentry-cli to upload all mappings for multi-module projects
  • Validates paths, branch names, project/repo resolution, and integration association

Depends on #109783
Closes getsentry/sentry-android-gradle-plugin#1074

Test plan

  • 20 tests covering happy path (single/multiple/update/mixed create+update), validation errors, and resolution errors
  • Pre-commit passes
  • Existing code mappings tests unaffected

@romtsn romtsn requested review from a team as code owners March 3, 2026 16:03
@github-actions github-actions bot added Scope: Backend Automatically applied to PRs that change backend components Scope: Frontend Automatically applied to PRs that change frontend components labels Mar 3, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 3, 2026

🚨 Warning: This pull request contains Frontend and Backend changes!

It's discouraged to make changes to Sentry's Frontend and Backend in a single pull request. The Frontend and Backend are not atomically deployed. If the changes are interdependent of each other, they must be separated into two pull requests and be made forward or backwards compatible, such that the Backend or Frontend can be safely deployed independently.

Have questions? Please ask in the #discuss-dev-infra channel.

@romtsn romtsn force-pushed the rz/feat/bulk-code-mappings-endpoint branch from 388490d to f859e09 Compare March 3, 2026 23:22
@romtsn romtsn force-pushed the rz/feat/bulk-code-mappings-endpoint branch from 59cf81a to 0b46407 Compare March 5, 2026 20:38
@romtsn romtsn requested a review from a team as a code owner March 5, 2026 20:38
romtsn and others added 2 commits March 17, 2026 11:12
When a repository doesn't exist in Sentry but the provider field is
specified, the endpoint now verifies the repo exists on the integration
(e.g. GitHub) and auto-creates the Repository record. This follows the
pattern used by derived code mappings.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The identifier-then-name matching pattern was duplicated in both
_auto_create_repository and get_repository_default_branch. Extract
it into a shared static method on RepositoryIntegration.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…or_create by provider

region_silo_endpoint was renamed to cell_silo_endpoint on master.
Also add provider to get_or_create lookup fields in _auto_create_repository
to prevent MultipleObjectsReturned when repos with the same name exist
for different providers.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@romtsn
Copy link
Copy Markdown
Member Author

romtsn commented Mar 17, 2026

Unless the Issues team has any objections, we could get this PR merged as-is, however, I want to see the endpoint take the provider name as a follow-up. Doing so will be the grounds for auto-adding repositories and determining the default branch for new repositories.

This would greatly improve the user experience for this endpoint.

--- a/src/sentry/integrations/api/endpoints/organization_code_mappings_bulk.py
+++ b/src/sentry/integrations/api/endpoints/organization_code_mappings_bulk.py
@@ -42,6 +42,7 @@ class MappingItemSerializer(serializers.Serializer):
 class BulkCodeMappingsRequestSerializer(CamelSnakeSerializer):
     project = serializers.CharField(required=True)
     repository = serializers.CharField(required=True)
+    provider = serializers.CharField(required=False, allow_null=True, allow_blank=True)
     default_branch = serializers.RegexField(
         r"^(^(?![\/]))([\w\.\/-]+)(?<![\/])$",
         required=False,
@@ -94,25 +95,30 @@ class OrganizationCodeMappingsBulkEndpoint(OrganizationEndpoint):
                 status=status.HTTP_403_FORBIDDEN,
             )
 
-        # Resolve repository by name
-        try:
-            repo = Repository.objects.get(
-                organization_id=organization.id,
-                name=data["repository"],
-                status=ObjectStatus.ACTIVE,
-            )
-        except Repository.DoesNotExist:
+        # Resolve repository by name (and optionally provider when multiple repos share a name)
+        repo_filter = Repository.objects.filter(
+            organization_id=organization.id,
+            name=data["repository"],
+            status=ObjectStatus.ACTIVE,
+        )
+        provider = (data.get("provider") or "").strip()
+        if provider:
+            # Match both "github" and "integrations:github" (how integration repos are stored)
+            repo_filter = repo_filter.filter(provider__in=[provider, f"integrations:{provider}"])
+        repos = list(repo_filter)
+        if len(repos) == 0:
             return Response(
                 {"detail": f"Repository not found: {data['repository']}"},
                 status=status.HTTP_404_NOT_FOUND,
             )
-        except Repository.MultipleObjectsReturned:
+        if len(repos) > 1:
             return Response(
                 {
-                    "detail": f"Multiple repositories found with name: {data['repository']}. Please ensure repository names are unique."
+                    "detail": f"Multiple repositories found with name: {data['repository']}. Provide provider (e.g. 'github', 'gitlab') to specify which integration's:

thanks for the suggestions! Added it in this PR too as well as inferring the branch name and creating a new repo if it doesn't exist

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

@romtsn romtsn force-pushed the rz/feat/bulk-code-mappings-endpoint branch from de34ccc to d64c9f8 Compare March 17, 2026 12:40
get_or_create on (name, organization_id, provider) can raise
MultipleObjectsReturned since the unique constraint is on
(organization_id, provider, external_id). Also filter by
status=ACTIVE to avoid returning inactive repos.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@romtsn romtsn force-pushed the rz/feat/bulk-code-mappings-endpoint branch from d64c9f8 to a042869 Compare March 17, 2026 12:45
organization_id=organization.id,
provider=repo_provider,
integration_id=org_int.integration_id,
external_id=repo_info.get("identifier", ""),
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armenzg I noticed you don't set external_id -- is that intentional or is actually an oversight and we should set it?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sigh. That was an oversight.

@romtsn romtsn requested a review from armenzg March 17, 2026 13:31
Copy link
Copy Markdown
Member

@armenzg armenzg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

organization_id=organization.id,
provider=repo_provider,
integration_id=org_int.integration_id,
external_id=repo_info.get("identifier", ""),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sigh. That was an oversight.

@romtsn romtsn merged commit c2047b8 into master Mar 17, 2026
81 checks passed
@romtsn romtsn deleted the rz/feat/bulk-code-mappings-endpoint branch March 17, 2026 14:58
romtsn added a commit that referenced this pull request Mar 17, 2026
…k code mappings (#109786)

## Summary
- Add tests for `org:ci` token authentication (sentry-cli CI use case)
- Add IDOR prevention tests (cross-org project and repo access)
- Add permission tests (project access check, org member access)
- Add edge case tests (duplicate stackRoots in request, multiple repos
with same name)

Depends on #109785
Closes
getsentry/sentry-android-gradle-plugin#1075

## Test plan
- [x] 7 additional tests (27 total), all passing
- [x] Pre-commit passes
romtsn added a commit to getsentry/sentry-cli that referenced this pull request Mar 20, 2026
_#skip-changelog_

Add the `sentry-cli code-mappings upload` subcommand group and the
`upload`
subcommand with file parsing and validation.

This is the first in a stack of 4 PRs to support bulk uploading code
mappings
from a JSON file — useful for Java/Android multi-module projects that
need
dozens of mappings.

This PR adds:
- Command scaffold following the `repos`/`deploys` pattern
- JSON file reading and validation (empty arrays, empty
stackRoot/sourceRoot)
- CLI args: positional `PATH`, `--repo`, `--default-branch`
- Help and no-subcommand trycmd integration tests

Stack: **#3207** → #3208#3209#3210

Backend PRs: getsentry/sentry#109783, getsentry/sentry#109785,
getsentry/sentry#109786

Closes getsentry/sentry-android-gradle-plugin#1076
Closes getsentry/sentry-android-gradle-plugin#1077

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
romtsn added a commit to getsentry/sentry-cli that referenced this pull request Mar 24, 2026
…3208)

*#skip-changelog*

When `--repo` or `--default-branch` are not provided, infer them from
the<br>local git repository. Uses the configured VCS remote
(SENTRY_VCS_REMOTE / ini)<br>first, then falls back to best-effort
remote detection (upstream > origin > first).

Also extracts `find_best_remote()` as a shared utility in
`src/utils/vcs.rs`,<br>replacing the inline logic that was duplicated in
`git_repo_base_repo_name_preserve_case`.

Stack: #3207 →
[#3208](<#3208>) →
[#3209](<#3209>) →
[#3210](<#3210>)

Backend PRs:
[getsentry/sentry#109783](<getsentry/sentry#109783>),
[getsentry/sentry#109785](<getsentry/sentry#109785>),
[getsentry/sentry#109786](<getsentry/sentry#109786>)

Closes
[GRADLE-79](https://linear.app/getsentry/issue/GRADLE-79/add-git-inference-for-repo-name-and-default-branch)

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
romtsn added a commit to getsentry/sentry-cli that referenced this pull request Mar 24, 2026
…#3209)

_#skip-changelog_

Connect the `code-mappings upload` command to the bulk code mappings API
endpoint (`POST /api/0/organizations/{org}/code-mappings/bulk/`).

Adds:
- `bulk_upload_code_mappings()` method on `AuthenticatedApi`
- Request/response data types in `src/api/data_types/code_mappings.rs`
- Summary table and error reporting in the command output
- Happy-path integration test with mock endpoint

Stack: #3207#3208 → **#3209** → #3210

Backend PRs: getsentry/sentry#109783, getsentry/sentry#109785,
getsentry/sentry#109786

Closes getsentry/sentry-android-gradle-plugin#1079

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
romtsn added a commit to getsentry/sentry-cli that referenced this pull request Mar 24, 2026
_#skip-changelog_

Split large mapping files into batches of 300 (the backend limit) per
request.
Each batch is sent sequentially with progress reporting, and results are
merged
into a single summary.

Also changes the output table to only show error rows — for large
uploads
(hundreds of mappings), printing every row would flood the terminal.
Successful
mappings are reflected in the summary counts instead.

Stack: #3207#3208#3209 → **#3210**

Backend PRs: getsentry/sentry#109783, getsentry/sentry#109785,
getsentry/sentry#109786

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement bulk code mappings API endpoint

4 participants