Skip to content

Commit

Permalink
feat(backup): Enable shimming deleted fields (#66248)
Browse files Browse the repository at this point in the history
We need to support deleted fields on Django model schemas for a number
of self-hosted released (currently 2). This means that even if we delete
a field on HEAD, we still need to make the import script aware that the
field could exit on imported JSON, and pre-process that JSON in such a
way that the field is removed in cases where the JSON was generated by
an old version that still contained the field.

There is no purpose in testing this code standalone. It passes tests in
#65923, which is the first actual use of this shimming mechanism.
  • Loading branch information
azaslavsky committed Mar 4, 2024
1 parent 04dba78 commit 495179a
Showing 1 changed file with 29 additions and 0 deletions.
29 changes: 29 additions & 0 deletions src/sentry/backup/imports.py
Expand Up @@ -47,6 +47,20 @@
"import_in_global_scope",
)

# We have to be careful when removing fields from our model schemas, since exports created using
# the old-but-still-in-the-support-window versions could have those fields set in the data they
# provide. This dict serves as a map of all fields that have been deleted on HEAD but are still
# valid in at least one of the versions we support. For example, since our current version
# support window is two minor versions back, if we delete a field at version 24.5.N, we must
# include an entry in this map for that field until that version is out of the support window
# (in this case, we can remove shim once version 24.7.0 is released).
#
# NOTE TO FUTURE EDITORS: please keep the `DELETED_FIELDS` dict, and the subsequent `if` clause,
# around even if the dict is empty, to ensure that there is a ready place to pop shims into. For
# each entry in this dict, please leave a TODO comment pointed to a github issue for removing
# the shim, noting in the comment which self-hosted release will trigger the removal.
DELETED_FIELDS: dict[str, set[str]] = {}


class ImportingError(Exception):
def __init__(self, context: RpcImportError) -> None:
Expand Down Expand Up @@ -135,6 +149,21 @@ def _import(
if decryptor is not None
else src.read().decode("utf-8")
)

if len(DELETED_FIELDS) > 0:
# Parse the content JSON and remove and fields that we have marked for deletion in the
# function.
shimmed_models = set(DELETED_FIELDS.keys())
content_as_json = json.loads(content) # type: ignore
for json_model in content_as_json:
if json_model["model"] in shimmed_models:
fields_to_remove = DELETED_FIELDS[json_model["model"]]
for field in fields_to_remove:
json_model["fields"].pop(field, None)

# Return the content to byte form, as that is what the Django deserializer expects.
content = json.dumps(content_as_json)

filters = []
if filter_by is not None:
filters.append(filter_by)
Expand Down

0 comments on commit 495179a

Please sign in to comment.