From 2e3482e40bf21e19f532c72f6ab3f3c6940d867f Mon Sep 17 00:00:00 2001 From: Scott Strong Date: Mon, 13 Jul 2026 10:16:06 -0700 Subject: [PATCH] move ExternalActorReplica to pending deletion --- migrations_lockfile.txt | 2 +- src/sentry/db/router.py | 1 + ...031_remove_externalactorreplica_pending.py | 58 +++++++++++++++++++ src/sentry/hybridcloud/models/__init__.py | 2 - .../models/externalactorreplica.py | 36 ------------ 5 files changed, 60 insertions(+), 39 deletions(-) create mode 100644 src/sentry/hybridcloud/migrations/0031_remove_externalactorreplica_pending.py delete mode 100644 src/sentry/hybridcloud/models/externalactorreplica.py diff --git a/migrations_lockfile.txt b/migrations_lockfile.txt index ac0120880283..4cee859b2c63 100644 --- a/migrations_lockfile.txt +++ b/migrations_lockfile.txt @@ -13,7 +13,7 @@ feedback: 0007_cleanup_failed_safe_deletes flags: 0001_squashed_0004_add_flag_audit_log_provider_column -hybridcloud: 0001_squashed_0030_remove_orgslugreservationreplica_delete +hybridcloud: 0031_remove_externalactorreplica_pending insights: 0001_squashed_0002_backfill_team_starred diff --git a/src/sentry/db/router.py b/src/sentry/db/router.py index 1345381091eb..579cdd98cb95 100644 --- a/src/sentry/db/router.py +++ b/src/sentry/db/router.py @@ -69,6 +69,7 @@ class SiloRouter: "authidentity_duplicate": SiloMode.CONTROL, "authprovider_duplicate": SiloMode.CONTROL, "feedback_feedback": SiloMode.CELL, + "hybridcloud_externalactorreplica": SiloMode.CONTROL, "hybridcloud_organizationslugreservationreplica": SiloMode.CELL, "prevent_ai_configuration": SiloMode.CELL, "releases_commit": SiloMode.CELL, diff --git a/src/sentry/hybridcloud/migrations/0031_remove_externalactorreplica_pending.py b/src/sentry/hybridcloud/migrations/0031_remove_externalactorreplica_pending.py new file mode 100644 index 000000000000..0937f55f8e3d --- /dev/null +++ b/src/sentry/hybridcloud/migrations/0031_remove_externalactorreplica_pending.py @@ -0,0 +1,58 @@ +# Generated by Django 5.2.14 on 2026-07-13 17:02 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations + +import sentry.db.models.fields.foreignkey +from sentry.new_migrations.migrations import CheckedMigration +from sentry.new_migrations.monkey.models import SafeDeleteModel +from sentry.new_migrations.monkey.state import DeletionAction + + +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", "0001_squashed_0030_remove_orgslugreservationreplica_delete"), + ] + + operations = [ + # Drop the DB-level FK constraints before removing the model from state; + # SafeDeleteModel(MOVE_TO_PENDING) refuses to proceed while any remain. + migrations.AlterField( + model_name="externalactorreplica", + name="integration", + field=sentry.db.models.fields.foreignkey.FlexibleForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.CASCADE, + to="sentry.integration", + ), + ), + migrations.AlterField( + model_name="externalactorreplica", + name="user", + field=sentry.db.models.fields.foreignkey.FlexibleForeignKey( + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + SafeDeleteModel( + name="ExternalActorReplica", + deletion_action=DeletionAction.MOVE_TO_PENDING, + ), + ] diff --git a/src/sentry/hybridcloud/models/__init__.py b/src/sentry/hybridcloud/models/__init__.py index a90394adeb1e..a6118b669166 100644 --- a/src/sentry/hybridcloud/models/__init__.py +++ b/src/sentry/hybridcloud/models/__init__.py @@ -4,13 +4,11 @@ "OrgAuthTokenReplica", "CacheVersionBase", "CellCacheVersion", - "ExternalActorReplica", "WebhookPayload", ] from .apikeyreplica import ApiKeyReplica # noqa from .apitokenreplica import ApiTokenReplica # noqa from .cacheversion import CacheVersionBase, CellCacheVersion # noqa -from .externalactorreplica import ExternalActorReplica # noqa from .orgauthtokenreplica import OrgAuthTokenReplica # noqa from .webhookpayload import WebhookPayload # noqa diff --git a/src/sentry/hybridcloud/models/externalactorreplica.py b/src/sentry/hybridcloud/models/externalactorreplica.py deleted file mode 100644 index 2cabe42156ec..000000000000 --- a/src/sentry/hybridcloud/models/externalactorreplica.py +++ /dev/null @@ -1,36 +0,0 @@ -from django.db import models - -from sentry.backup.scopes import RelocationScope -from sentry.db.models import ( - BoundedPositiveIntegerField, - FlexibleForeignKey, - Model, - control_silo_model, -) -from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignKey - - -@control_silo_model -class ExternalActorReplica(Model): - __relocation_scope__ = RelocationScope.Excluded - - externalactor_id = BoundedPositiveIntegerField() - team_id = HybridCloudForeignKey("sentry.Team", null=True, db_index=True, on_delete="CASCADE") - user = FlexibleForeignKey("sentry.User", null=True, db_index=True, on_delete=models.CASCADE) - organization_id = HybridCloudForeignKey("sentry.Organization", on_delete="CASCADE") - integration = FlexibleForeignKey("sentry.Integration", on_delete=models.CASCADE) - - provider = BoundedPositiveIntegerField() - - # The display name i.e. username, team name, channel name. - external_name = models.TextField() - # The unique identifier i.e user ID, channel ID. - external_id = models.TextField(null=True) - - class Meta: - app_label = "hybridcloud" - db_table = "hybridcloud_externalactorreplica" - unique_together = ( - ("organization_id", "provider", "external_name", "team_id"), - ("organization_id", "provider", "external_name", "user_id"), - )