Migrating a Schema in SpiceDB
A Schema in SpiceDB is the file that represents the structural definitions of which relationships are allowed in SpiceDB and how permissions are computed. It can be updated or migrated via the WriteSchema API.
SpiceDB processes all calls to the WriteSchema in a safe manner: it is not possible to break the type safety of a schema. This means that, for example, it is not possible to accidentally remove a relation that is still referenced by one or more relationships. However, this does mean that some operations require us to do some steps beforehand.
Safe Migrations
Adding a new relation
Adding a new relation to a definition is always allowed, as it cannot change any existing
types or computation:
definition resource {
relation existing: user
relation newrelation: user
permission view = existing
}Changing a permission
Changing how a permission is computed is always allowed, so long as the expression references other defined permissions or relations:
definition resource {
relation viewer: user
relation editor: user
permission view = viewer + editor
}Adding a new subject type to a relation
Adding a new allowed subject type to a relation is always allowed:
definition resource {
relation viewer: user | group#member
permission view = viewer
}Deleting a permission
Removing a permission is always allowed, so long as it is not referenced by another permission or relation.
While this cannot break the schema, it can break API callers if they are making checks or other API requests against the permission. It is up to your own CI system to verify that removed permissions are no longer referenced externally.
Contingent Migrations
For type safety reasons, any removal of a relation with data, or a relation or permission
referenced by another relation or permission is disallowed.
Removing a relation
A relation can only be removed if all of the relationships referencing it
have been deleted and it is not referenced by any other relation or permission
in the schema.
Process for removing a relation
Given this example schema and we wish to remove relation editor:
definition resource {
relation viewer: user
relation editor: user
permission view = viewer + editor
}To remove relation editor:
-
Change the schema to no longer reference the relation and call WriteSchema with the changes:
definition resource { relation viewer: user relation editor: user permission view = viewer } -
Issue a DeleteRelationships call to delete all relationships for the
editorrelation -
Update the schema to remove the relation entirely and call WriteSchema with the changes:
definition resource { relation viewer: user permission view = viewer }
Removing an allowed subject type
Similar to removing a relation itself, removing an allowed subject type can only be performed once all relationships with that subject type on the relation have been deleted.
Process for removing an allowed subject type
Given this example schema and we wish to remove supporting group#member on viewer:
definition resource {
relation viewer: user | group#member
permission view = viewer
}-
Issue a DeleteRelationships call to delete all relationships for the
viewerrelation with subject typegroup#member -
Update the schema to remove the allowed subject type call WriteSchema with the changes:
definition resource {
relation viewer: user
permission view = viewer
}Migrating data from one relation to another
Given the constraints described above, migrating relationships from one relation to another
requires a few steps.
Let’s take a sample schema and walk through migrating data from relation viewer to a new relation new_viewer:
definition resource {
relation viewer: user
permission view = viewer
}-
Add the new relation:
We start by adding the new relation and adding it to the
viewpermission:definition resource { relation viewer: user relation new_viewer: user2 permission view = viewer + new_viewer } -
Update the application so that it writes relationships to both
relation viewerandrelation new_viewer. This ensures that once we run the backfill (the next step), both relations are fully specified. -
Backfill the relationships:
We next backfill the relationships by having our application write the relationships for the
new_viewerrelation. Make sure to copy all relevant relationships in this step. -
Drop
viewerfrom the permission:Once the relationships for
new_viewerhave been fully written and the permission has been verified, (typically by issuing a CheckPermission request directly tonew_viewer), theviewerrelation can be dropped from theviewpermission:definition resource { relation viewer: user relation new_viewer: user2 permission view = new_viewer } -
Update the application:
We next update our application to no longer write relationships to the
viewerrelation, as it is no longer used. -
Delete the relation
viewer:Finally, follow the instructions above for deleting a
relationto delete the now-unused relation.