This is an automated email from the ASF dual-hosted git repository.

craigrueda pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 6b73b69b41 feat(CLI command): Apache Superset "Factory Reset" CLI 
command #27207 (#27221)
6b73b69b41 is described below

commit 6b73b69b415ec6b6fcbac80a358f4e31c4ed91b9
Author: mknadh <[email protected]>
AuthorDate: Wed Jul 3 21:50:05 2024 +0530

    feat(CLI command): Apache Superset "Factory Reset" CLI command #27207 
(#27221)
---
 superset/cli/reset.py               | 74 +++++++++++++++++++++++++++++
 superset/commands/security/reset.py | 94 +++++++++++++++++++++++++++++++++++++
 superset/config.py                  |  2 +
 3 files changed, 170 insertions(+)

diff --git a/superset/cli/reset.py b/superset/cli/reset.py
new file mode 100644
index 0000000000..fd5e726075
--- /dev/null
+++ b/superset/cli/reset.py
@@ -0,0 +1,74 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import sys
+
+import click
+from flask.cli import with_appcontext
+from werkzeug.security import check_password_hash
+
+from superset.cli.lib import feature_flags
+
+if feature_flags.get("ENABLE_FACTORY_RESET_COMMAND"):
+
+    @click.command()
+    @with_appcontext
+    @click.option("--username", prompt="Admin Username", help="Admin Username")
+    @click.option(
+        "--silent",
+        is_flag=True,
+        prompt=(
+            "Are you sure you want to reset Superset? "
+            "This action cannot be undone. Continue?"
+        ),
+        help="Confirmation flag",
+    )
+    @click.option(
+        "--exclude-users",
+        default=None,
+        help="Comma separated list of users to exclude from reset",
+    )
+    @click.option(
+        "--exclude-roles",
+        default=None,
+        help="Comma separated list of roles to exclude from reset",
+    )
+    def factory_reset(
+        username: str, silent: bool, exclude_users: str, exclude_roles: str
+    ) -> None:
+        """Factory Reset Apache Superset"""
+
+        # pylint: disable=import-outside-toplevel
+        from superset import security_manager
+        from superset.commands.security.reset import ResetSupersetCommand
+
+        # Validate the user
+        password = click.prompt("Admin Password", hide_input=True)
+        user = security_manager.find_user(username)
+        if not user or not check_password_hash(user.password, password):
+            click.secho("Invalid credentials", fg="red")
+            sys.exit(1)
+        if not any(role.name == "Admin" for role in user.roles):
+            click.secho("Permission Denied", fg="red")
+            sys.exit(1)
+
+        try:
+            ResetSupersetCommand(silent, user, exclude_users, 
exclude_roles).run()
+            click.secho("Factory reset complete", fg="green")
+        except Exception as ex:  # pylint: disable=broad-except
+            click.secho(f"Factory reset failed: {ex}", fg="red")
+            sys.exit(1)
diff --git a/superset/commands/security/reset.py 
b/superset/commands/security/reset.py
new file mode 100644
index 0000000000..5c93bb4646
--- /dev/null
+++ b/superset/commands/security/reset.py
@@ -0,0 +1,94 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+from typing import Any, Optional
+
+from superset import db, security_manager
+from superset.commands.base import BaseCommand
+from superset.connectors.sqla.models import SqlaTable
+from superset.key_value.models import KeyValueEntry
+from superset.models.core import Database, FavStar, Log
+from superset.models.dashboard import Dashboard
+from superset.models.slice import Slice
+
+logger = logging.getLogger(__name__)
+
+
+class ResetSupersetCommand(BaseCommand):
+    def __init__(
+        self,
+        confirm: bool,
+        user: Any,
+        exclude_users: Optional[str] = None,
+        exclude_roles: Optional[str] = None,
+    ) -> None:
+        self._user = user
+        self._confirm = confirm
+        self._users_to_exclude = ["admin"]
+        if exclude_users:
+            self._users_to_exclude.extend(exclude_users.split(","))
+        self._roles_to_exclude = ["Admin", "Public", "Gamma", "Alpha", 
"sql_lab"]
+        if exclude_roles:
+            self._roles_to_exclude.extend(exclude_roles.split(","))
+
+    def validate(self) -> None:
+        if not self._confirm:
+            raise Exception("Reset aborted.")  # pylint: 
disable=broad-exception-raised
+        if not self._user or not self._user.is_active:
+            raise Exception("User not found.")  # pylint: 
disable=broad-exception-raised
+
+    def run(self) -> None:
+        self.validate()
+        logger.debug("Resetting Superset Started")
+        db.session.query(SqlaTable).delete()
+        databases = db.session.query(Database)
+        for database in databases:
+            db.session.delete(database)
+        db.session.query(Dashboard).delete()
+        db.session.query(Slice).delete()
+        db.session.query(KeyValueEntry).delete()
+        db.session.query(Log).delete()
+        db.session.query(FavStar).delete()
+
+        logger.debug("Ignoring Users: %s", self._users_to_exclude)
+        users_to_delete = (
+            db.session.query(security_manager.user_model)
+            
.filter(security_manager.user_model.username.not_in(self._users_to_exclude))
+            .all()
+        )
+        for user in users_to_delete:
+            if not any(role.name == "Admin" for role in user.roles):
+                db.session.delete(user)
+
+        logger.debug("Ignoring Roles: %s", self._roles_to_exclude)
+        roles_to_delete = (
+            db.session.query(security_manager.role_model)
+            
.filter(security_manager.role_model.name.not_in(self._roles_to_exclude))
+            .all()
+        )
+        for role in roles_to_delete:
+            db.session.delete(role)
+
+        # Insert new record into Log table
+        log = Log(
+            action="Factory Reset", json="{}", user_id=self._user.id, 
user=self._user
+        )
+        db.session.add(log)
+
+        db.session.commit()  # pylint: disable=consider-using-transaction
+        logger.debug("Resetting Superset Completed")
diff --git a/superset/config.py b/superset/config.py
index e4dc202537..fa31fd069a 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -539,6 +539,8 @@ DEFAULT_FEATURE_FLAGS: dict[str, bool] = {
     "CHART_PLUGINS_EXPERIMENTAL": False,
     # Regardless of database configuration settings, force SQLLAB to run async 
using Celery
     "SQLLAB_FORCE_RUN_ASYNC": False,
+    # Set to True to to enable factory resent CLI command
+    "ENABLE_FACTORY_RESET_COMMAND": False,
 }
 
 # ------------------------------

Reply via email to