Browse Source

Add ability to generate a development config

Peter Justin 8 years ago
parent
commit
a76526381d
3 changed files with 154 additions and 87 deletions
  1. 108 80
      flaskbb/cli/main.py
  2. 39 2
      flaskbb/cli/utils.py
  3. 7 5
      flaskbb/configs/config.cfg.template

+ 108 - 80
flaskbb/cli/main.py

@@ -13,19 +13,22 @@ import os
 import time
 import time
 import requests
 import requests
 import binascii
 import binascii
+from datetime import datetime
 
 
 import click
 import click
 from werkzeug.utils import import_string, ImportStringError
 from werkzeug.utils import import_string, ImportStringError
 from jinja2 import Environment, FileSystemLoader
 from jinja2 import Environment, FileSystemLoader
 from flask import current_app
 from flask import current_app
 from flask.cli import FlaskGroup, ScriptInfo, with_appcontext
 from flask.cli import FlaskGroup, ScriptInfo, with_appcontext
-from sqlalchemy_utils.functions import database_exists, create_database, drop_database
+from sqlalchemy_utils.functions import (database_exists, create_database,
+                                        drop_database)
 from flask_migrate import upgrade as upgrade_database
 from flask_migrate import upgrade as upgrade_database
 
 
 from flaskbb import create_app
 from flaskbb import create_app
 from flaskbb._compat import iteritems
 from flaskbb._compat import iteritems
 from flaskbb.extensions import db, whooshee, celery
 from flaskbb.extensions import db, whooshee, celery
-from flaskbb.cli.utils import (get_version, save_user_prompt, FlaskBBCLIError,
+from flaskbb.cli.utils import (save_user_prompt, prompt_config_path,
+                               write_config, get_version, FlaskBBCLIError,
                                EmailType)
                                EmailType)
 from flaskbb.utils.populate import (create_test_data, create_welcome_forum,
 from flaskbb.utils.populate import (create_test_data, create_welcome_forum,
                                     create_default_groups,
                                     create_default_groups,
@@ -408,138 +411,163 @@ def list_urls(order_by):
 
 
 
 
 @flaskbb.command("makeconfig")
 @flaskbb.command("makeconfig")
-@click.option("--debug", "-d", default=False, is_flag=True,
+@click.option("--development", "-d", default=False, is_flag=True,
               help="Creates a development config with DEBUG set to True.")
               help="Creates a development config with DEBUG set to True.")
-@click.option("--output", "-o", default=".",
+@click.option("--output", "-o", required=False,
-              help="The path where the config file will be saved at.")
+              help="The path where the config file will be saved at. "
-@click.option("--stdout", default=False, is_flag=True,
+                   "Defaults to the flaskbb's root folder.")
-              help="This will ignore --output and print the config to stdout.")
+@click.option("--force", "-f", default=False, is_flag=True,
-def generate_config(debug, output, stdout):
+              help="Overwrite any existing config file if one exists.")
+def generate_config(development, output, force):
     """Generates a FlaskBB configuration file."""
     """Generates a FlaskBB configuration file."""
-    config_dict = {"is_debug": debug}
+    config_env = Environment(
+        loader=FileSystemLoader(os.path.join(current_app.root_path, "configs"))
+    )
+    config_template = config_env.get_template('config.cfg.template')
 
 
+    if output:
+        config_path = os.path.abspath(output)
+    else:
+        config_path = os.path.dirname(current_app.root_path)
+
+    if os.path.exists(config_path) and not os.path.isfile(config_path):
+        config_path = os.path.join(config_path, "flaskbb.cfg")
+
+    default_conf = {
+        "is_debug": True,
+        "server_name": "localhost",
+        "url_scheme": "http",
+        "database_uri": "sqlite:///" + os.path.join(
+            os.path.dirname(current_app.root_path), "flaskbb.sqlite"),
+        "redis_enabled": False,
+        "redis_uri": "redis://localhost:6379",
+        "mail_server": "localhost",
+        "mail_port": 25,
+        "mail_use_tls": False,
+        "mail_use_ssl": False,
+        "mail_username": "",
+        "mail_password": "",
+        "mail_sender_name": "FlaskBB Mailer",
+        "mail_sender_address": "noreply@yourdomain",
+        "mail_admin_address": "admin@yourdomain",
+        "secret_key": binascii.hexlify(os.urandom(24)).decode(),
+        "csrf_secret_key": binascii.hexlify(os.urandom(24)).decode(),
+        "timestamp": datetime.utcnow().strftime("%A, %d. %B %Y at %H:%M")
+    }
+
+    if not force:
+        config_path = prompt_config_path(config_path)
+
+    if force and os.path.exists(config_path):
+        click.secho("Overwriting existing config file: {}".format(config_path),
+                    fg="yellow")
+
+    if development:
+        write_config(default_conf, config_template, config_path)
+        sys.exit(0)
+
+    # SERVER_NAME
     click.secho("The name and port number of the server.\n"
     click.secho("The name and port number of the server.\n"
                 "This is needed to correctly generate URLs when no request "
                 "This is needed to correctly generate URLs when no request "
                 "context is available.", fg="cyan")
                 "context is available.", fg="cyan")
-    config_dict["server_name"] = click.prompt(
+    default_conf["server_name"] = click.prompt(
         click.style("Server Name", fg="magenta"), type=str,
         click.style("Server Name", fg="magenta"), type=str,
-        default="forums.flaskbb.org")
+        default=default_conf.get("server_name"))
 
 
+    # PREFERRED_URL_SCHEME
     click.secho("The URL Scheme is also needed in order to generate correct "
     click.secho("The URL Scheme is also needed in order to generate correct "
                 "URLs when no request context is available.\n"
                 "URLs when no request context is available.\n"
                 "Choose either 'https' or 'http'.", fg="cyan")
                 "Choose either 'https' or 'http'.", fg="cyan")
-    config_dict["url_scheme"] = click.prompt(
+    default_conf["url_scheme"] = click.prompt(
         click.style("URL Scheme", fg="magenta"),
         click.style("URL Scheme", fg="magenta"),
-        type=click.Choice(["https", "http"]), default="https")
+        type=click.Choice(["https", "http"]),
-
+        default=default_conf.get("url_scheme"))
-    config_dict["secret_key"] = binascii.hexlify(os.urandom(24)).decode()
-    config_dict["csrf_secret_key"] = binascii.hexlify(os.urandom(24)).decode()
 
 
+    # SQLALCHEMY_DATABASE_URI
     click.secho("For Postgres use:\n"
     click.secho("For Postgres use:\n"
                 "    postgresql://flaskbb@localhost:5432/flaskbb\n"
                 "    postgresql://flaskbb@localhost:5432/flaskbb\n"
                 "For more options see the SQLAlchemy docs:\n"
                 "For more options see the SQLAlchemy docs:\n"
                 "    http://docs.sqlalchemy.org/en/latest/core/engines.html",
                 "    http://docs.sqlalchemy.org/en/latest/core/engines.html",
                 fg="cyan")
                 fg="cyan")
-    config_dict["database_url"] = click.prompt(
+    default_conf["database_url"] = click.prompt(
         click.style("Database URI", fg="magenta"),
         click.style("Database URI", fg="magenta"),
-        default="sqlite:///" + os.path.join(
+        default=default_conf.get("database_uri"))
-            os.path.dirname(current_app.root_path), "flaskbb.sqlite"))
 
 
+    # REDIS_ENABLED
     click.secho("Redis will be used for things such as the task queue, "
     click.secho("Redis will be used for things such as the task queue, "
                 "caching and rate limiting.", fg="cyan")
                 "caching and rate limiting.", fg="cyan")
-    config_dict["redis_enabled"] = click.confirm(
+    default_conf["redis_enabled"] = click.confirm(
         click.style("Would you like to use redis?", fg="magenta"),
         click.style("Would you like to use redis?", fg="magenta"),
-        default=True)
+        default=True)  # default_conf.get("redis_enabled") is False
 
 
-    if config_dict.get("redis_enabled", False):
+    # REDIS_URI
-        config_dict["redis_url"] = click.prompt(
+    if default_conf.get("redis_enabled", False):
+        default_conf["redis_uri"] = click.prompt(
             click.style("Redis URI", fg="magenta"),
             click.style("Redis URI", fg="magenta"),
-            default="redis://localhost:6379")
+            default=default_conf.get("redis_uri"))
     else:
     else:
-        config_dict["redis_url"] = ""
+        default_conf["redis_uri"] = ""
 
 
+    # MAIL_SERVER
     click.secho("To use 'localhost' make sure that you have sendmail or\n"
     click.secho("To use 'localhost' make sure that you have sendmail or\n"
                 "something similar installed. Gmail is also supprted.",
                 "something similar installed. Gmail is also supprted.",
                 fg="cyan")
                 fg="cyan")
-    config_dict["mail_server"] = click.prompt(
+    default_conf["mail_server"] = click.prompt(
         click.style("Mail Server", fg="magenta"),
         click.style("Mail Server", fg="magenta"),
-        default="localhost")
+        default=default_conf.get("mail_server"))
+    # MAIL_PORT
     click.secho("The port on which the SMTP server is listening on.",
     click.secho("The port on which the SMTP server is listening on.",
                 fg="cyan")
                 fg="cyan")
-    config_dict["mail_port"] = click.prompt(
+    default_conf["mail_port"] = click.prompt(
         click.style("Mail Server SMTP Port", fg="magenta"),
         click.style("Mail Server SMTP Port", fg="magenta"),
-        default="25")
+        default=default_conf.get("mail_port"))
+    # MAIL_USE_TLS
     click.secho("If you are using a local SMTP server like sendmail this is "
     click.secho("If you are using a local SMTP server like sendmail this is "
                 "not needed. For external servers this is hopefully required.",
                 "not needed. For external servers this is hopefully required.",
                 fg="cyan")
                 fg="cyan")
-    config_dict["mail_use_tls"] = click.confirm(
+    default_conf["mail_use_tls"] = click.confirm(
         click.style("Use TLS for sending mails?", fg="magenta"),
         click.style("Use TLS for sending mails?", fg="magenta"),
-        default=False)
+        default=default_conf.get("mail_use_tls"))
+    # MAIL_USE_SSL
     click.secho("Same as above. TLS is the successor to SSL.", fg="cyan")
     click.secho("Same as above. TLS is the successor to SSL.", fg="cyan")
-    config_dict["mail_use_ssl"] = click.confirm(
+    default_conf["mail_use_ssl"] = click.confirm(
         click.style("Use SSL for sending mails?", fg="magenta"),
         click.style("Use SSL for sending mails?", fg="magenta"),
-        default=False)
+        default=default_conf.get("mail_use_ssl"))
+    # MAIL_USERNAME
     click.secho("Not needed if using a local smtp server. For gmail you have "
     click.secho("Not needed if using a local smtp server. For gmail you have "
                 "put in your email address here.", fg="cyan")
                 "put in your email address here.", fg="cyan")
-    config_dict["mail_username"] = click.prompt(
+    default_conf["mail_username"] = click.prompt(
         click.style("Mail Username", fg="magenta"),
         click.style("Mail Username", fg="magenta"),
-        default="")
+        default=default_conf.get("mail_username"))
+    # MAIL_PASSWORD
     click.secho("Not needed if using a local smtp server. For gmail you have "
     click.secho("Not needed if using a local smtp server. For gmail you have "
                 "put in your gmail password here.", fg="cyan")
                 "put in your gmail password here.", fg="cyan")
-    config_dict["mail_password"] = click.prompt(
+    default_conf["mail_password"] = click.prompt(
         click.style("Mail Password", fg="magenta"),
         click.style("Mail Password", fg="magenta"),
-        default="")
+        default=default_conf.get("mail_password"))
+    # MAIL_DEFAULT_SENDER
     click.secho("The name of the sender. You probably want to change it to "
     click.secho("The name of the sender. You probably want to change it to "
                 "something like '<your_community> Mailer'.", fg="cyan")
                 "something like '<your_community> Mailer'.", fg="cyan")
-    config_dict["mail_sender_name"] = click.prompt(
+    default_conf["mail_sender_name"] = click.prompt(
         click.style("Mail Sender Name", fg="magenta"),
         click.style("Mail Sender Name", fg="magenta"),
-        default="FlaskBB Mailer")
+        default=default_conf.get("mail_sender_name"))
     click.secho("On localhost you want to use a noreply address here. "
     click.secho("On localhost you want to use a noreply address here. "
                 "Use your email address for gmail here.", fg="cyan")
                 "Use your email address for gmail here.", fg="cyan")
-    config_dict["mail_sender_address"] = click.prompt(
+    default_conf["mail_sender_address"] = click.prompt(
         click.style("Mail Sender Address", fg="magenta"),
         click.style("Mail Sender Address", fg="magenta"),
-        default="noreply@yourdomain.org")
+        default=default_conf.get("mail_sender_address"))
+    # ADMINS
     click.secho("Logs and important system messages are sent to this address."
     click.secho("Logs and important system messages are sent to this address."
                 "Use your email address for gmail here.", fg="cyan")
                 "Use your email address for gmail here.", fg="cyan")
-    config_dict["mail_admin_address"] = click.prompt(
+    default_conf["mail_admin_address"] = click.prompt(
         click.style("Mail Admin Email", fg="magenta"),
         click.style("Mail Admin Email", fg="magenta"),
-        default="admin@youremailaddress.org")
+        default=default_conf.get("mail_admin_address"))
 
 
-    config_env = Environment(
+    write_config(default_conf, config_template, config_path)
-        loader=FileSystemLoader(os.path.join(current_app.root_path, "configs"))
-    )
-    config_template = config_env.get_template('config.cfg.template')
 
 
-    if not stdout:
+    # Finished
-        config_dict["config_path"] = os.path.join(
+    click.secho("The configuration file has been saved to:\n{cfg}"
-            os.path.dirname(current_app.root_path), "flaskbb.cfg"
+                .format(cfg=config_path), fg="blue")
-        )
+    click.secho("Usage: flaskbb --config {cfg} run\n"
-        click.secho("The path where you want to save this configuration file.",
+                "Feel free to adjust it as needed."
-                    fg="cyan")
+                .format(cfg=config_path), fg="green")
-        while True:
-            if os.path.exists(config_dict["config_path"]) and click.confirm(
-                click.style("Config {} exists. Do you want to overwride it?"
-                            .format(config_dict["config_path"]), fg="magenta")
-            ):
-                break
-
-            config_dict["config_path"] = click.prompt(
-                click.style("Save to", fg="magenta"),
-                default=config_dict["config_path"])
-
-            if not os.path.exists(config_dict["config_path"]):
-                break
-
-        with open(config_dict["config_path"], 'w') as cfg_file:
-            cfg_file.write(
-                config_template.render(**config_dict).encode("utf-8")
-            )
-
-        click.secho("The configuration file has been saved to: {cfg}"
-                    .format(cfg=config_dict["config_path"]), fg="blue")
-
-        click.secho("Use it like this: flaskbb --config {cfg} run\n"
-                    "Feel free to adjust it as needed."
-                    .format(cfg=config_dict["config_path"]), fg="green")
-    else:
-        click.echo(config_template.render(**config_dict))

+ 39 - 2
flaskbb/cli/utils.py

@@ -12,11 +12,10 @@
 import sys
 import sys
 import os
 import os
 import re
 import re
-import binascii
 
 
 import click
 import click
 
 
-from flask import current_app, __version__ as flask_version
+from flask import __version__ as flask_version
 from flask_themes2 import get_theme
 from flask_themes2 import get_theme
 
 
 from flaskbb import __version__
 from flaskbb import __version__
@@ -137,3 +136,41 @@ def get_version(ctx, param, value):
         'python_version': sys.version.split("\n")[0]
         'python_version': sys.version.split("\n")[0]
     }, color=ctx.color)
     }, color=ctx.color)
     ctx.exit()
     ctx.exit()
+
+
+def prompt_config_path(config_path):
+    """Asks for a config path. If the path exists it will ask the user
+    for a new path until a he enters a path that doesn't exist.
+
+    :param config_path: The path to the configuration.
+    """
+    click.secho("The path to save this configuration file.", fg="cyan")
+    while True:
+        if os.path.exists(config_path) and click.confirm(click.style(
+            "Config {cfg} exists. Do you want to overwrite it?"
+            .format(cfg=config_path), fg="magenta")
+        ):
+            break
+
+        config_path = click.prompt(
+            click.style("Save to", fg="magenta"),
+            default=config_path)
+
+        if not os.path.exists(config_path):
+            break
+
+    return config_path
+
+
+def write_config(config, config_template, config_path):
+    """Writes a new config file based upon the config template.
+
+    :param config: A dict containing all the key/value pairs which should be
+                   used for the new configuration file.
+    :param config_template: The config (jinja2-)template.
+    :param config_path: The place to write the new config file.
+    """
+    with open(config_path, 'w') as cfg_file:
+        cfg_file.write(
+            config_template.render(**config).encode("utf-8")
+        )

+ 7 - 5
flaskbb/configs/config.cfg.template

@@ -1,3 +1,5 @@
+# This file has been automatically generated on {{ timestamp }}.
+# Feel free to adjust it as needed.
 import os
 import os
 import datetime
 import datetime
 from flaskbb.configs.default import DefaultConfig
 from flaskbb.configs.default import DefaultConfig
@@ -29,7 +31,7 @@ PREFERRED_URL_SCHEME = "{{ url_scheme }}"
 # For PostgresSQL:
 # For PostgresSQL:
 #SQLALCHEMY_DATABASE_URI = "postgresql://flaskbb@localhost:5432/flaskbb"
 #SQLALCHEMY_DATABASE_URI = "postgresql://flaskbb@localhost:5432/flaskbb"
 # For SQLite:
 # For SQLite:
-SQLALCHEMY_DATABASE_URI = "{{ database_url }}"
+SQLALCHEMY_DATABASE_URI = "{{ database_uri }}"
 
 
 # This option will be removed as soon as Flask-SQLAlchemy removes it.
 # This option will be removed as soon as Flask-SQLAlchemy removes it.
 # At the moment it is just used to suppress the super annoying warning
 # At the moment it is just used to suppress the super annoying warning
@@ -92,14 +94,14 @@ WHOOSHEE_MIN_STRING_LEN = 3
 #   - Caching
 #   - Caching
 #   - Rate Limiting
 #   - Rate Limiting
 REDIS_ENABLED = {{ redis_enabled }}
 REDIS_ENABLED = {{ redis_enabled }}
-REDIS_URL = "{{ redis_url }}"
+REDIS_URL = "{{ redis_uri }}"
 REDIS_DATABASE = 0
 REDIS_DATABASE = 0
 
 
 
 
 # Celery
 # Celery
 # ------------------------------
 # ------------------------------
-CELERY_BROKER_URL = "{{ redis_url }}"
+CELERY_BROKER_URL = "{{ redis_uri }}"
-CELERY_RESULT_BACKEND = "{{ redis_url }}"
+CELERY_RESULT_BACKEND = "{{ redis_uri }}"
 
 
 
 
 # Rate Limiting
 # Rate Limiting
@@ -116,7 +118,7 @@ CELERY_RESULT_BACKEND = "{{ redis_url }}"
 # Using the redis storage requires the installation of the redis package,
 # Using the redis storage requires the installation of the redis package,
 # which will be installed if you enable REDIS_ENABLE while memcached
 # which will be installed if you enable REDIS_ENABLE while memcached
 # relies on the pymemcache package.
 # relies on the pymemcache package.
-RATELIMIT_STORAGE_URL = "{% if redis_enabled %}{{ redis_url }}{% else %}memory://{% endif %}"
+RATELIMIT_STORAGE_URL = "{% if redis_enabled %}{{ redis_uri }}{% else %}memory://{% endif %}"
 
 
 
 
 # Caching
 # Caching