-
Notifications
You must be signed in to change notification settings - Fork 43
Erase organization command in server CLI #12172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,98 @@ | ||||||||||||||||||||||||||
| .. Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| .. _doc_hosting_erase_organization: | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Erase an organization | ||||||||||||||||||||||||||
| ===================== | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Where & how the data are stored | ||||||||||||||||||||||||||
| ------------------------------- | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| For any given organization, data are split as follow: | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| - A PostgreSQL database containing the certificates (e.g. user/devices/workspaces) and | ||||||||||||||||||||||||||
| encrypted workspaces metadata (e.g. content of folder, list of blocks for each file version). | ||||||||||||||||||||||||||
| - A blockstore (e.g. S3) containing the blocks (i.e. encrypted pieces of data that compose the files). | ||||||||||||||||||||||||||
| - On top of that, each Parsec client having access to the organization has an encrypted local database | ||||||||||||||||||||||||||
| containing a copy of the certificates, metadata for the workspaces it has access to, and a subset | ||||||||||||||||||||||||||
| of the blocks (depending on local cache configuration). | ||||||||||||||||||||||||||
|
Comment on lines
+13
to
+18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Erasing data | ||||||||||||||||||||||||||
| ------------ | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| When no longer in use (or for legal reason) an organization can be erased from the Parsec server. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| In practice this means: | ||||||||||||||||||||||||||
|
Comment on lines
+23
to
+25
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 1. Removing everything (certificates & metadata) related to the organization in the PostgreSQL database. | ||||||||||||||||||||||||||
| 2. Removing the blocks from the blockstore. | ||||||||||||||||||||||||||
| 3. Removing the remaining data from the clients. | ||||||||||||||||||||||||||
|
Comment on lines
+27
to
+29
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd use "delete" (or erase) since "remove" may indicate persistence.
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Step 1: remove from PostgreSQL | ||||||||||||||||||||||||||
| ------------------------------ | ||||||||||||||||||||||||||
|
Comment on lines
+31
to
+32
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd make these steps (1, 2, 3) subsections of "Erasing data"
Suggested change
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Erasing an organization from Parsec is both an uncommon and (obviously !) a destructive operation. | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
| As such it is not available from the Administration API but instead must be triggered from the server CLI directly. | ||||||||||||||||||||||||||
|
FirelightFlagboy marked this conversation as resolved.
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| .. code-block:: bash | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # On Parsec server | ||||||||||||||||||||||||||
| parsec erase_organization --organization <OrgName> --db <database_url> | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| .. warning:: | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| This operation cannot be undone. Make sure you have a backup of any data you may need before | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: maybe add the command to make a backup in the warning message ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, the administrator is responsible for the backup strategy, so we cannot provide a one-size-fits-all command here (typically "backup any data" may refer to do a PostgreSQL snapshot and storing it on cold storage, or it may refer to simply have a user start a Parsec client and copy/paste the files still needed)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is already a section describing how to make a backup, we should link to it. See my next comment/suggestion. |
||||||||||||||||||||||||||
| proceeding. | ||||||||||||||||||||||||||
|
Comment on lines
+44
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| .. note:: | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| After erasing an organization, it is possible to create a new organization with the same name | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the expected error on the client side if they haven't deleted their local data and a new organization with the same name is created ? If their parsec client would just look offline that may be confusing if the same entity re creates an organization and expect to enroll the same people.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The client will get an error complaining the organization is not found on the server (even if a new organization with the same name is created, since they don't have the same root verify key) I've added a word about this in the note 👍
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Otherwise one may think that it is OK to create an org with same name just after step 1 (postgresql), before step 2 (blockstore cleanup). IMO, regardless of Parsec internals, the erasing procedure should be treated almost as a" transaction", step 1 to 3 should be performed as a whole before attempting to create a new organization with same name. |
||||||||||||||||||||||||||
| since no trace of the previous one remains. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Even if they share the same name, the erased and the new organization are strictly unrelated | ||||||||||||||||||||||||||
| since they have a different root verify key (i.e. root key used to verify all certificates | ||||||||||||||||||||||||||
| in the organization). | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Typically this means a Parsec client trying to access the erased organization will complain | ||||||||||||||||||||||||||
| it doesn't exist on the server even if a new organization with the same name exist. | ||||||||||||||||||||||||||
|
Comment on lines
+52
to
+57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Step 2: Blockstore cleanup | ||||||||||||||||||||||||||
| -------------------------- | ||||||||||||||||||||||||||
|
Comment on lines
+59
to
+60
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Once step 1 done, the blocks' decryption keys has been lost. In other words, everything | ||||||||||||||||||||||||||
| stored in the blockstore and related to the organization is irrecoverable. | ||||||||||||||||||||||||||
| Hence removing those data from the blockstore should be seen as an optional step to reclaim | ||||||||||||||||||||||||||
|
Comment on lines
+63
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
| needlessly occupied space. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| This can be done by manually removing from the blockstore the top level directory named after the organization. | ||||||||||||||||||||||||||
| For example, if the organization was named ``CoolOrg``, remove the ``CoolOrg/`` prefix from the bucket. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| .. note:: | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Blockstores have their own backup strategy. Typically AWS S3 allows for a bucket to | ||||||||||||||||||||||||||
| have an history so that a data removal can be cancelled. | ||||||||||||||||||||||||||
| You should pay attention to this to ensure the blocks have actually been removed. | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we expect them to be removed from the history too ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The goal here is to permanently erase all data from a given organization, so we expect the administrator to remove the history. |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Step 3: Clients cleanup | ||||||||||||||||||||||||||
| ----------------------- | ||||||||||||||||||||||||||
|
Comment on lines
+76
to
+77
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Once the organization erased from the server, the Parsec client will display an error | ||||||||||||||||||||||||||
| about the organization not being found on the server. | ||||||||||||||||||||||||||
| However the client can still work in offline mode (if the server is not reachable, the | ||||||||||||||||||||||||||
| client cannot know the organization has been erased from the server!), | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| For this reason, an end-user is still able to use his Parsec client to work on the | ||||||||||||||||||||||||||
| organization using the local cache (e.g. creating a new file in a workspace or | ||||||||||||||||||||||||||
| reading an existing file that is in cache). | ||||||||||||||||||||||||||
|
Comment on lines
+84
to
+86
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| To prevent the users from accessing the local cache, the local configuration and data should be manually removed: | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. by the user themselves ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can also be done by an administrator that has remote access to the machine. Here we don't specify how things should be done but what should be done.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rephrase it like this:
Suggested change
IMO we should add a note or warning to explain how to identify which device is linked to the organization (device id is displayed in the "My Devices" when logged-in... maybe we should consider displaying it in the Home page as tooltip or detail button ?)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, in other sections of the docs we suggest using the CLI for that, e.g.:
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| - Linux: | ||||||||||||||||||||||||||
| - config: ``$XDG_DATA_HOME or $HOME/.local/share/parsec3/<device_id>`` (e.g. ``/home/alice/.local/share/parsec3/e68b7131394749a4bbd279bd087e6ae6``) | ||||||||||||||||||||||||||
| - data: ``$XDG_CONFIG_HOME or $HOME/.config/parsec3/libparsec/devices/<device_id>`` (e.g. ``/home/alice/.config/parsec3/libparsec/devices/e68b7131394749a4bbd279bd087e6ae6``) | ||||||||||||||||||||||||||
|
Comment on lines
+91
to
+92
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
| - macOS: | ||||||||||||||||||||||||||
| - config: ``$HOME/Library/Application Support/parsec3/<device_id>`` (e.g. ``/Users/Alice/Library/Application Support/parsec3/e68b7131394749a4bbd279bd087e6ae6``) | ||||||||||||||||||||||||||
| - data: ``$HOME/Library/Application Support/parsec3/libparsec/devices/<device_id>`` (e.g. ``/Users/Alice/Library/Application Support/parsec3/libparsec/devices/e68b7131394749a4bbd279bd087e6ae6``) | ||||||||||||||||||||||||||
| - Windows: | ||||||||||||||||||||||||||
| - config: ``{FOLDERID_RoamingAppData}\parsec3\<device_id>`` (e.g. ``C:\Users\Alice\AppData\Roaming\parsec3\e68b7131394749a4bbd279bd087e6ae6``) | ||||||||||||||||||||||||||
| - data: ``{FOLDERID_RoamingAppData}\parsec3\libparsec\devices\<device_id>`` (e.g. ``C:\Users\Alice\AppData\Roaming/parsec3/libparsec/devices/e68b7131394749a4bbd279bd087e6ae6``) | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,3 +13,4 @@ Server Administration | |
| stats_organization | ||
| freeze_users | ||
| shared_recovery | ||
| erase_organization | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| # Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS | ||
| from __future__ import annotations | ||
|
|
||
| import asyncio | ||
| from typing import Any | ||
|
|
||
| import click | ||
|
|
||
| from parsec._parsec import ( | ||
| OrganizationID, | ||
| ) | ||
| from parsec.cli.options import ( | ||
| db_server_options, | ||
| debug_config_options, | ||
| logging_config_options, | ||
| ) | ||
| from parsec.cli.testbed import if_testbed_available | ||
| from parsec.cli.utils import cli_exception_handler, spinner, start_backend | ||
| from parsec.components.organization import OrganizationEraseBadOutcome | ||
| from parsec.config import ( | ||
| BaseDatabaseConfig, | ||
| DisabledBlockStoreConfig, | ||
| LogLevel, | ||
| MockedBlockStoreConfig, | ||
| ) | ||
|
|
||
|
|
||
| class DevOption(click.Option): | ||
| def handle_parse_result( | ||
| self, ctx: click.Context, opts: Any, args: list[str] | ||
| ) -> tuple[Any, list[str]]: | ||
| value, args = super().handle_parse_result(ctx, opts, args) | ||
| if value: | ||
| for key, value in ( | ||
| ("debug", True), | ||
| ("db", "MOCKED"), | ||
| ("with_testbed", "coolorg"), | ||
| ("organization", "CoolorgOrgTemplate"), | ||
| ): | ||
| if key not in opts: | ||
| opts[key] = value | ||
|
|
||
| return value, args | ||
|
|
||
|
|
||
| @click.command(short_help="Erase an organization from the database") | ||
| @click.option("--organization", type=OrganizationID, help="Organization ID", required=True) | ||
| @click.option("--yes", is_flag=True, help="Don't ask for confirmation before proceeding") | ||
| @db_server_options | ||
| # Add --log-level/--log-format/--log-file | ||
| @logging_config_options(default_log_level="INFO") | ||
| # Add --debug & --version | ||
| @debug_config_options | ||
| @if_testbed_available( | ||
| click.option("--with-testbed", help="Start by populating with a testbed template") | ||
| ) | ||
| @if_testbed_available( | ||
| click.option( | ||
| "--dev", | ||
| cls=DevOption, | ||
| is_flag=True, | ||
| is_eager=True, | ||
| help=( | ||
| "Equivalent to `--debug --db=MOCKED --with-testbed=coolorg --organization CoolorgOrgTemplate`" | ||
| ), | ||
| ) | ||
| ) | ||
| def erase_organization( | ||
|
FirelightFlagboy marked this conversation as resolved.
|
||
| organization: OrganizationID, | ||
| db: BaseDatabaseConfig, | ||
| db_max_connections: int, | ||
| db_min_connections: int, | ||
| log_level: LogLevel, | ||
| log_format: str, | ||
| log_file: str | None, | ||
| yes: bool, | ||
| debug: bool, | ||
| with_testbed: str | None = None, | ||
| dev: bool = False, | ||
| ) -> None: | ||
| with cli_exception_handler(debug): | ||
| asyncio.run( | ||
| _erase_organization( | ||
| yes=yes, | ||
| db_config=db, | ||
| debug=debug, | ||
| with_testbed=with_testbed, | ||
| organization_id=organization, | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| async def _erase_organization( | ||
| db_config: BaseDatabaseConfig, | ||
| yes: bool, | ||
| debug: bool, | ||
| with_testbed: str | None, | ||
| organization_id: OrganizationID, | ||
| ) -> None: | ||
| # Can use a dummy blockstore config since we are not going to query it | ||
| if with_testbed is None: | ||
| blockstore_config = DisabledBlockStoreConfig() | ||
| else: | ||
| # Testbed template might need to create some blocks | ||
| blockstore_config = MockedBlockStoreConfig() | ||
|
|
||
| display_org = click.style(organization_id.str, fg="yellow") | ||
| click.echo( | ||
| f"You are about to entirely erase the {display_org} organization from the database, this action cannot be undone." | ||
| ) | ||
|
|
||
| display_bucket_path = click.style(f"{organization_id.str}/", fg="yellow") | ||
| click.echo("Notes:") | ||
| click.echo( | ||
| "- No trace of the organization will remain, so it will be possible to re-create another organization with the same name." | ||
| ) | ||
| click.echo( | ||
| f"- The organization's blocks won't be erased from the blockstore, you should manually remove the {display_bucket_path} top level directory from it." | ||
| ) | ||
| click.echo("") | ||
|
|
||
| if not yes: | ||
| confirmation = click.prompt("To confirm, type the name of the organization") | ||
| if confirmation != organization_id.str: | ||
| raise RuntimeError("Organization name does not match, aborting") | ||
|
|
||
| async with start_backend( | ||
| db_config=db_config, | ||
| blockstore_config=blockstore_config, | ||
| debug=debug, | ||
| populate_with_template=with_testbed, | ||
| ) as backend: | ||
| async with spinner("Removing from database..."): | ||
| outcome = await backend.organization.erase(id=organization_id) | ||
| match outcome: | ||
| case None: | ||
| pass | ||
| case OrganizationEraseBadOutcome.ORGANIZATION_NOT_FOUND: | ||
| raise RuntimeError("Organization doesn't exist") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.