-
Notifications
You must be signed in to change notification settings - Fork 65
Init command and enhanced init-db #369
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
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 |
|---|---|---|
| @@ -1,95 +1,211 @@ | ||
| import click | ||
| from flask import Flask | ||
| from sqlalchemy import or_, func | ||
| import random | ||
| import string | ||
| from datetime import datetime, timezone | ||
|
|
||
|
|
||
| from .config import Configuration | ||
| def _echo_title(title): | ||
| click.echo("") | ||
| click.echo(f"# {title}") | ||
| click.echo() | ||
|
|
||
|
|
||
| def _echo_error(msg): | ||
| click.secho("Error: ", fg="red", nl=False, bold=True) | ||
| click.secho(msg, fg="bright_red") | ||
|
|
||
|
|
||
| def add_commands(app: Flask): | ||
| from .app import db | ||
| from mergin.auth.models import UserProfile | ||
|
|
||
| @app.cli.group() | ||
| def server(): | ||
| """Server management commands.""" | ||
| pass | ||
| def _check_celery(): | ||
| from celery import current_app | ||
|
|
||
| @server.command() | ||
| @click.option("--email", required=True) | ||
| def send_check_email(email: str): # pylint: disable=W0612 | ||
| ping_celery = current_app.control.inspect().ping() | ||
| if not ping_celery: | ||
| _echo_error( | ||
| "Celery process not running properly. Configure celery worker and celery beat. This breaks also email sending from the system.", | ||
| ) | ||
| return | ||
| click.secho("Celery is running properly", fg="green") | ||
| return True | ||
|
|
||
| def _send_statistics(): | ||
| from .stats.tasks import send_statistics, save_statistics | ||
|
|
||
| _echo_title("Sending statistics.") | ||
| # save rows to MerginStatistics table | ||
| save_statistics.delay() | ||
|
|
||
| if not app.config.get("COLLECT_STATISTICS"): | ||
| click.secho( | ||
| "Statistics sending is disabled.", | ||
| ) | ||
| return | ||
|
|
||
| if not _check_celery(): | ||
| return | ||
| send_statistics.delay() | ||
| click.secho("Statistics sent.", fg="green") | ||
|
|
||
| def _send_email(email: str): | ||
| """Send check email to specified email address.""" | ||
| from .celery import send_email_async | ||
|
|
||
| _echo_title(f"Sending check email to specified email address {email}.") | ||
| if app.config["MAIL_SUPPRESS_SEND"]: | ||
| click.echo( | ||
| click.style( | ||
| "Sending emails is disabled. Please set MAIL_SUPPRESS_SEND=False to enable sending emails.", | ||
| fg="red", | ||
| ) | ||
| _echo_error( | ||
| "Sending emails is disabled. Please set MAIL_SUPPRESS_SEND=False to enable sending emails." | ||
| ) | ||
| return | ||
| if not app.config["MAIL_DEFAULT_SENDER"]: | ||
| click.echo( | ||
| click.style( | ||
| "No default sender set. Please set MAIL_DEFAULT_SENDER environment variable", | ||
| fg="red", | ||
| ) | ||
| default_sender = app.config.get("MAIL_DEFAULT_SENDER") | ||
| if not default_sender: | ||
| _echo_error( | ||
| "No default sender set. Please set MAIL_DEFAULT_SENDER environment variable", | ||
| ) | ||
| return | ||
| email_data = { | ||
| "subject": "Mergin Maps server check", | ||
| "html": "Awesome, your email configuration of Mergin Maps server is working.", | ||
| "recipients": [email], | ||
| "sender": app.config["MAIL_DEFAULT_SENDER"], | ||
| "sender": default_sender, | ||
| } | ||
| click.echo( | ||
| f"Sending email to specified email address {email}. Check your inbox." | ||
| ) | ||
| try: | ||
| is_celery_running = _check_celery() | ||
| if not is_celery_running: | ||
| return | ||
| send_email_async.delay(**email_data) | ||
| except Exception as e: | ||
| click.echo( | ||
| click.style( | ||
| f"Error sending email: {e}", | ||
| fg="red", | ||
| ) | ||
| _echo_error( | ||
| f"Error sending email: {e}", | ||
| ) | ||
|
|
||
| @server.command() | ||
| def check(): # pylint: disable=W0612 | ||
| """Check server configuration. Define email to send testing email.""" | ||
| from celery import current_app | ||
| def _check_server(): # pylint: disable=W0612 | ||
| """Check server configuration.""" | ||
|
|
||
| click.echo(f"Mergin Maps server version: {app.config['VERSION']}") | ||
| _echo_title("Server health check") | ||
| edition_map = { | ||
| "ce": "Community Edition", | ||
| "ee": "Enterprise Edition", | ||
| } | ||
| edition = edition_map.get(app.config["SERVER_TYPE"]) | ||
| if edition: | ||
| click.echo(f"Mergin Maps edition: {edition}") | ||
| click.echo(f"Mergin Maps version: {app.config['VERSION']}") | ||
harminius marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| base_url = app.config["MERGIN_BASE_URL"] | ||
| if not base_url: | ||
| click.echo( | ||
| click.style( | ||
| "No base URL set. Please set MERGIN_BASE_URL environment variable", | ||
| fg="red", | ||
| ), | ||
| _echo_error( | ||
| "No base URL set. Please set MERGIN_BASE_URL environment variable", | ||
|
Collaborator
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. shall we also check contact_email variable? I'd maybe use it for admin/test email
Collaborator
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. we can, but it's not required variable - see docs https://merginmaps.com/docs/server/administer/environment/ |
||
| ) | ||
| else: | ||
| click.secho(f"Base URL of server is {base_url}", fg="green") | ||
|
|
||
| contact_email = app.config["CONTACT_EMAIL"] | ||
| if not contact_email: | ||
| _echo_error( | ||
| "No contact email set. Please set CONTACT_EMAIL environment variable", | ||
| ) | ||
| else: | ||
| click.echo(f"Base URL of server is {base_url}") | ||
| click.secho(f"Base URL of server is {base_url}", fg="green") | ||
|
|
||
| tables = db.engine.table_names() | ||
| if not tables: | ||
| click.echo( | ||
| click.style( | ||
| "Database not initialized. Run flask init-db command", fg="red" | ||
| ) | ||
| ) | ||
| _echo_error("Database not initialized. Run flask init-db command") | ||
| else: | ||
| click.echo("Database initialized properly") | ||
| click.secho("Database initialized properly", fg="green") | ||
|
|
||
| ping_celery = current_app.control.inspect().ping() | ||
| if not ping_celery: | ||
| click.echo( | ||
| click.style( | ||
| "Celery not running. Configure celery worker and celery beat", | ||
| fg="red", | ||
| ) | ||
| _check_celery() | ||
|
|
||
| def _init_db(): | ||
| """Create database tables.""" | ||
| from .stats.models import MerginInfo | ||
|
|
||
| _echo_title("Database initialization") | ||
| with click.progressbar( | ||
| label="Creating database", length=4, show_eta=False | ||
| ) as progress_bar: | ||
MarcelGeo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| progress_bar.update(0) | ||
| db.drop_all(bind=None) | ||
| progress_bar.update(1) | ||
| db.create_all(bind=None) | ||
| progress_bar.update(2) | ||
| db.session.commit() | ||
| progress_bar.update(3) | ||
| info = MerginInfo.query.first() | ||
| if not info and app.config.get("COLLECT_STATISTICS"): | ||
| # create new info with random service id | ||
| service_id = app.config.get("SERVICE_ID", None) | ||
| info = MerginInfo(service_id) | ||
| db.session.add(info) | ||
| db.session.commit() | ||
| progress_bar.update(4) | ||
|
|
||
| click.secho("Tables created.", fg="green") | ||
|
|
||
| @app.cli.command() | ||
| def init_db(): | ||
| """Re-create database tables.""" | ||
| _init_db() | ||
|
|
||
| @app.cli.command() | ||
| @click.option("--email", "-e", required=True, envvar="CONTACT_EMAIL") | ||
| @click.option( | ||
| "--recreate", | ||
| "-r", | ||
| help="Recreate database and admin user.", | ||
| is_flag=True, | ||
| required=False, | ||
| ) | ||
| def init(email: str, recreate: bool): | ||
| """Initialize database if does not exist or -r is provided. Perform check of server configuration. Send statistics, respecting your setup.""" | ||
|
|
||
| from .auth.models import User | ||
|
|
||
| tables = db.engine.table_names() | ||
| if recreate and tables: | ||
| click.confirm( | ||
| "Are you sure you want to recreate database and admin user? This will remove all data!", | ||
| default=False, | ||
| abort=True, | ||
| ) | ||
| else: | ||
| click.echo("Celery running properly") | ||
|
|
||
| if not tables or recreate: | ||
| _init_db() | ||
|
Collaborator
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 recreate flag might be dangerous, shall we ask for user confirmation?
Collaborator
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. added |
||
|
|
||
| _echo_title("Creating admin user. Copy generated password.") | ||
| username = User.generate_username(email) | ||
| password_chars = string.ascii_letters + string.digits | ||
| password = "".join(random.choice(password_chars) for i in range(12)) | ||
| user = User(username=username, passwd=password, email=email, is_admin=True) | ||
| user.profile = UserProfile() | ||
| user.active = True | ||
| db.session.add(user) | ||
| db.session.commit() | ||
| click.secho( | ||
| "Admin user created. Please save generated password.", fg="green" | ||
| ) | ||
| click.secho(f"Email: {email}") | ||
| click.secho(f"Username: {username}") | ||
| click.secho(f"Password: {password}") | ||
| _check_server() | ||
| _send_email(email) | ||
| _send_statistics() | ||
|
|
||
| @app.cli.group() | ||
| def server(): | ||
| """Server management commands.""" | ||
| pass | ||
|
|
||
| @server.command() | ||
| @click.option("--email", required=True) | ||
| def send_check_email(email: str): # pylint: disable=W0612 | ||
| """Send check email to specified email address.""" | ||
| _send_email(email) | ||
|
|
||
| @server.command() | ||
| def check(): | ||
| """Check server configuration.""" | ||
| _check_server() | ||
Uh oh!
There was an error while loading. Please reload this page.