Skip to content
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

feat(CLI command): Apache Superset "Factory Reset" CLI command #27207 #27221

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions superset/cli/reset.py
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 18 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L18

Added line #L18 was not covered by tests

import click
from flask.cli import with_appcontext
from werkzeug.security import check_password_hash

Check warning on line 22 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L20-L22

Added lines #L20 - L22 were not covered by tests

from superset.cli.lib import feature_flags

Check warning on line 24 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L24

Added line #L24 was not covered by tests

if feature_flags.get("ENABLE_FACTORY_RESET_COMMAND"):

Check warning on line 26 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L26

Added line #L26 was not covered by tests

@click.command()
@with_appcontext
@click.option("--username", prompt="Admin Username", help="Admin Username")
@click.option(

Check warning on line 31 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L28-L31

Added lines #L28 - L31 were not covered by tests
"--silent",
is_flag=True,
prompt=(
"Are you sure you want to reset Superset? "
"This action cannot be undone. Continue?"
),
help="Confirmation flag",
)
@click.option(

Check warning on line 40 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L40

Added line #L40 was not covered by tests
"--exclude-users",
default=None,
help="Comma separated list of users to exclude from reset",
)
@click.option(

Check warning on line 45 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L45

Added line #L45 was not covered by tests
"--exclude-roles",
default=None,
help="Comma separated list of roles to exclude from reset",
)
def factory_reset(

Check warning on line 50 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L50

Added line #L50 was not covered by tests
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

Check warning on line 57 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L56-L57

Added lines #L56 - L57 were not covered by tests

# 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)

Check warning on line 67 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L60-L67

Added lines #L60 - L67 were not covered by tests

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)

Check warning on line 74 in superset/cli/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/cli/reset.py#L69-L74

Added lines #L69 - L74 were not covered by tests
94 changes: 94 additions & 0 deletions superset/commands/security/reset.py
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 19 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L18-L19

Added lines #L18 - L19 were not covered by tests

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

Check warning on line 27 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L21-L27

Added lines #L21 - L27 were not covered by tests

logger = logging.getLogger(__name__)

Check warning on line 29 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L29

Added line #L29 was not covered by tests


class ResetSupersetCommand(BaseCommand):
def __init__(

Check warning on line 33 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L32-L33

Added lines #L32 - L33 were not covered by tests
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(","))

Check warning on line 47 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L40-L47

Added lines #L40 - L47 were not covered by tests

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

Check warning on line 53 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L49-L53

Added lines #L49 - L53 were not covered by tests

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()

Check warning on line 66 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L55-L66

Added lines #L55 - L66 were not covered by tests

logger.debug("Ignoring Users: %s", self._users_to_exclude)
users_to_delete = (

Check warning on line 69 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L68-L69

Added lines #L68 - L69 were not covered by tests
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)

Check warning on line 76 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L74-L76

Added lines #L74 - L76 were not covered by tests

logger.debug("Ignoring Roles: %s", self._roles_to_exclude)
roles_to_delete = (

Check warning on line 79 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L78-L79

Added lines #L78 - L79 were not covered by tests
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)

Check warning on line 85 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L84-L85

Added lines #L84 - L85 were not covered by tests

# Insert new record into Log table
log = Log(

Check warning on line 88 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L88

Added line #L88 was not covered by tests
action="Factory Reset", json="{}", user_id=self._user.id, user=self._user
)
db.session.add(log)

Check warning on line 91 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L91

Added line #L91 was not covered by tests

db.session.commit() # pylint: disable=consider-using-transaction
logger.debug("Resetting Superset Completed")

Check warning on line 94 in superset/commands/security/reset.py

View check run for this annotation

Codecov / codecov/patch

superset/commands/security/reset.py#L93-L94

Added lines #L93 - L94 were not covered by tests
2 changes: 2 additions & 0 deletions superset/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,8 @@ class D3TimeFormat(TypedDict, total=False):
"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,
}

# ------------------------------
Expand Down
Loading