diff --git a/migrations_lockfile.txt b/migrations_lockfile.txt index de2e628ddedb50..fe138ef50e9314 100644 --- a/migrations_lockfile.txt +++ b/migrations_lockfile.txt @@ -13,7 +13,7 @@ feedback: 0006_safe_del_feedback_model flags: 0001_squashed_0004_add_flag_audit_log_provider_column -hybridcloud: 0023_correct_webhook_payload_constraint +hybridcloud: 0024_add_project_distribution_scope insights: 0002_backfill_team_starred @@ -31,7 +31,7 @@ releases: 0001_release_models replays: 0006_add_bulk_delete_job -sentry: 0999_add_extrapolation_mode_to_snuba_query +sentry: 1000_add_project_distribution_scope social_auth: 0003_social_auth_json_field diff --git a/src/sentry/conf/server.py b/src/sentry/conf/server.py index 8a29eefa5946ec..4bb096521b451a 100644 --- a/src/sentry/conf/server.py +++ b/src/sentry/conf/server.py @@ -1729,6 +1729,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]: "project:write", "project:admin", "project:releases", + "project:distribution", "event:read", "event:write", "event:admin", @@ -1767,6 +1768,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]: "project:write": {"project:read", "project:write"}, "project:admin": {"project:read", "project:write", "project:admin"}, "project:releases": {"project:releases"}, + "project:distribution": {"project:distribution"}, "event:read": {"event:read"}, "event:write": {"event:read", "event:write"}, "event:admin": {"event:read", "event:write", "event:admin"}, diff --git a/src/sentry/hybridcloud/migrations/0024_add_project_distribution_scope.py b/src/sentry/hybridcloud/migrations/0024_add_project_distribution_scope.py new file mode 100644 index 00000000000000..92fbe2c0f782bf --- /dev/null +++ b/src/sentry/hybridcloud/migrations/0024_add_project_distribution_scope.py @@ -0,0 +1,89 @@ +# Generated by Django 5.2.1 on 2025-10-27 10:46 + +from django.db import migrations + +import bitfield.models +from sentry.new_migrations.migrations import CheckedMigration + + +class Migration(CheckedMigration): + # This flag is used to mark that a migration shouldn't be automatically run in production. + # This should only be used for operations where it's safe to run the migration after your + # code has deployed. So this should not be used for most operations that alter the schema + # of a table. + # Here are some things that make sense to mark as post deployment: + # - Large data migrations. Typically we want these to be run manually so that they can be + # monitored and not block the deploy for a long period of time while they run. + # - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to + # run this outside deployments so that we don't block them. Note that while adding an index + # is a schema change, it's completely safe to run the operation after the code has deployed. + # Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment + + is_post_deployment = False + + dependencies = [ + ("hybridcloud", "0023_correct_webhook_payload_constraint"), + ] + + operations = [ + migrations.AlterField( + model_name="apikeyreplica", + name="scopes", + field=bitfield.models.BitField( + [ + "project:read", + "project:write", + "project:admin", + "project:releases", + "team:read", + "team:write", + "team:admin", + "event:read", + "event:write", + "event:admin", + "org:read", + "org:write", + "org:admin", + "member:read", + "member:write", + "member:admin", + "org:integrations", + "alerts:read", + "alerts:write", + "member:invite", + "project:distribution", + ], + default=None, + ), + ), + migrations.AlterField( + model_name="apitokenreplica", + name="scopes", + field=bitfield.models.BitField( + [ + "project:read", + "project:write", + "project:admin", + "project:releases", + "team:read", + "team:write", + "team:admin", + "event:read", + "event:write", + "event:admin", + "org:read", + "org:write", + "org:admin", + "member:read", + "member:write", + "member:admin", + "org:integrations", + "alerts:read", + "alerts:write", + "member:invite", + "project:distribution", + ], + default=None, + ), + ), + ] diff --git a/src/sentry/migrations/1000_add_project_distribution_scope.py b/src/sentry/migrations/1000_add_project_distribution_scope.py new file mode 100644 index 00000000000000..a0451d0809b7cb --- /dev/null +++ b/src/sentry/migrations/1000_add_project_distribution_scope.py @@ -0,0 +1,149 @@ +# Generated by Django 5.2.1 on 2025-10-27 10:46 + +from django.db import migrations + +import bitfield.models +from sentry.new_migrations.migrations import CheckedMigration + + +class Migration(CheckedMigration): + # This flag is used to mark that a migration shouldn't be automatically run in production. + # This should only be used for operations where it's safe to run the migration after your + # code has deployed. So this should not be used for most operations that alter the schema + # of a table. + # Here are some things that make sense to mark as post deployment: + # - Large data migrations. Typically we want these to be run manually so that they can be + # monitored and not block the deploy for a long period of time while they run. + # - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to + # run this outside deployments so that we don't block them. Note that while adding an index + # is a schema change, it's completely safe to run the operation after the code has deployed. + # Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment + + is_post_deployment = False + + dependencies = [ + ("sentry", "0999_add_extrapolation_mode_to_snuba_query"), + ] + + operations = [ + migrations.AlterField( + model_name="apiauthorization", + name="scopes", + field=bitfield.models.BitField( + [ + "project:read", + "project:write", + "project:admin", + "project:releases", + "team:read", + "team:write", + "team:admin", + "event:read", + "event:write", + "event:admin", + "org:read", + "org:write", + "org:admin", + "member:read", + "member:write", + "member:admin", + "org:integrations", + "alerts:read", + "alerts:write", + "member:invite", + "project:distribution", + ], + default=None, + ), + ), + migrations.AlterField( + model_name="apikey", + name="scopes", + field=bitfield.models.BitField( + [ + "project:read", + "project:write", + "project:admin", + "project:releases", + "team:read", + "team:write", + "team:admin", + "event:read", + "event:write", + "event:admin", + "org:read", + "org:write", + "org:admin", + "member:read", + "member:write", + "member:admin", + "org:integrations", + "alerts:read", + "alerts:write", + "member:invite", + "project:distribution", + ], + default=None, + ), + ), + migrations.AlterField( + model_name="apitoken", + name="scopes", + field=bitfield.models.BitField( + [ + "project:read", + "project:write", + "project:admin", + "project:releases", + "team:read", + "team:write", + "team:admin", + "event:read", + "event:write", + "event:admin", + "org:read", + "org:write", + "org:admin", + "member:read", + "member:write", + "member:admin", + "org:integrations", + "alerts:read", + "alerts:write", + "member:invite", + "project:distribution", + ], + default=None, + ), + ), + migrations.AlterField( + model_name="sentryapp", + name="scopes", + field=bitfield.models.BitField( + [ + "project:read", + "project:write", + "project:admin", + "project:releases", + "team:read", + "team:write", + "team:admin", + "event:read", + "event:write", + "event:admin", + "org:read", + "org:write", + "org:admin", + "member:read", + "member:write", + "member:admin", + "org:integrations", + "alerts:read", + "alerts:write", + "member:invite", + "project:distribution", + ], + default=None, + ), + ), + ] diff --git a/src/sentry/models/apiscopes.py b/src/sentry/models/apiscopes.py index 0785b364354f45..3bd4acdf2f9133 100644 --- a/src/sentry/models/apiscopes.py +++ b/src/sentry/models/apiscopes.py @@ -21,7 +21,13 @@ def add_scope_hierarchy(curr_scopes: Sequence[str]) -> list[str]: class ApiScopes(Sequence): - project = (("project:read"), ("project:write"), ("project:admin"), ("project:releases")) + project = ( + ("project:read"), + ("project:write"), + ("project:admin"), + ("project:releases"), + ("project:distribution"), + ) team = (("team:read"), ("team:write"), ("team:admin")) @@ -85,6 +91,7 @@ class Meta: "alerts:read": bool, "alerts:write": bool, "member:invite": bool, + "project:distribution": bool, }, ) assert set(ScopesDict.__annotations__) == set(ApiScopes()) diff --git a/tests/sentry/api/serializers/test_organization.py b/tests/sentry/api/serializers/test_organization.py index 7a658c509bfcbe..16ea1fcb20b95e 100644 --- a/tests/sentry/api/serializers/test_organization.py +++ b/tests/sentry/api/serializers/test_organization.py @@ -28,7 +28,7 @@ pytestmark = [requires_snuba] -non_default_owner_scopes = ["org:ci", "openid", "email", "profile"] +non_default_owner_scopes = ["org:ci", "openid", "email", "profile", "project:distribution"] default_owner_scopes = frozenset( filter(lambda scope: scope not in non_default_owner_scopes, settings.SENTRY_SCOPES) ) diff --git a/tools/migrations/squash.py b/tools/migrations/squash.py index 096c97e002fbd3..faf1a94da539bb 100644 --- a/tools/migrations/squash.py +++ b/tools/migrations/squash.py @@ -81,7 +81,7 @@ def _migration_root(app: str) -> str: def _migrations(root: str) -> Generator[str]: for fname in os.listdir(root): - if fname.startswith("0") and fname.endswith(".py"): + if fname[0].isdigit() and fname.endswith(".py"): yield fname