Skip to content

Commit

Permalink
Add doc on schema updates and migrations
Browse files Browse the repository at this point in the history
  • Loading branch information
josephschorr committed Aug 26, 2024
1 parent 5f9c25f commit 00b34ef
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 1 deletion.
3 changes: 2 additions & 1 deletion pages/spicedb/modeling/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"representing-users": "Representing Users",
"validation-testing-debugging": "Validation, Testing, Debugging",
"recursion-and-max-depth": "Recursion & Max Depth",
"protecting-a-list-endpoint": "Protecting a List Endpoint"
"protecting-a-list-endpoint": "Protecting a List Endpoint",
"migrating-schema": "Updating and Migrating Schema"
}
202 changes: 202 additions & 0 deletions pages/spicedb/modeling/migrating-schema.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { Callout, Tabs } from 'nextra/components';

# Updating Migrating Schema in SpiceDB

[Schema] in SpiceDB represents the structural definitions of which relationships are allowed
in SpiceDB and how permissions are computed.

[Schema]: developing-a-schema

SpiceDB processes all calls to the [WriteSchema] in a **safe** manner: it is not possible to
break the type safety of a schema.

As a result, certain operations are disallowed (as described below), but there is no risk in
accidentally breaking an internal computation.

[WriteSchema]: https://buf.build/authzed/api/docs/main:authzed.api.v1#authzed.api.v1.SchemaService.WriteSchema

## Allowed Operations

### Adding a new relation

Adding a new `relation` to a `definition` is always allowed, as it cannot change any existing
types or computation:

```zed {3}
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:

```zed {4} /+ editor/
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:

```zed {2} /group#member/
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.

<Callout type="warning">
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.
</Callout>

## Contingent Operations

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`:

```zed {3} /relation editor: user/
definition resource {
relation viewer: user
relation editor: user
permission view = viewer + editor
}
```

To remove `relation editor`:

1. Change the schema to no longer reference the relation and call [WriteSchema] with the changes:

```zed {4}
definition resource {
relation viewer: user
relation editor: user
permission view = viewer
}
```

1. Issue a [DeleteRelationships] call to delete _all_ relationships for the `editor` relation

[DeleteRelationships]: https://buf.build/authzed/api/docs/main:authzed.api.v1#authzed.api.v1.PermissionsService.DeleteRelationships

1. Update the schema to remove the relation entirely and call [WriteSchema] with the changes:

```zed
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`:

```zed {2} /group#member/
definition resource {
relation viewer: user | group#member
permission view = viewer
}
```

1. Issue a [DeleteRelationships] call to delete _all_ relationships for the `viewer` relation with subject type `group#member`

1. Update the schema to remove the allowed subject type call [WriteSchema] with the changes:

```zed
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`:

```zed
definition resource {
relation viewer: user
permission view = viewer
}
```

1. Add the new relation:

We start by adding the new relation and adding it to the `view` permission:

```zed {3,4} /relation new_viewer: user2/ /+ new_viewer/
definition resource {
relation viewer: user
relation new_viewer: user2
permission view = viewer + new_viewer
}
```

1. Update the application:

We next update our application so that it writes relationships to _both_ `relation viewer` and `relation new_viewer`.
This ensures that once we run the backfill (the next step), both relations are fully specified.

1. Backfill the relationships:

We next backfill the relationships by having our application write the relationships
for the `new_viewer` relation.
Make sure to copy _all_ relevant relationships in this step.

1. Drop `viewer` from the permission:

Once the relationships for `new_viewer` have been fully written and the permission has been verified,
(typically by issuing a CheckPermission request _directly_ to `new_viewer`), the `viewer` relation
can be dropped from the `view` permission:

```zed {4}
definition resource {
relation viewer: user
relation new_viewer: user2
permission view = new_viewer
}
```

1. Update the application:

We next update our application to no longer write relationships to the `viewer` relation,
as it is no longer used.

1. Delete the relation `viewer`:

Finally, follow the instructions above for deleting a `relation` to delete the now-unused relation.

0 comments on commit 00b34ef

Please sign in to comment.