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 requests
 import binascii
+from datetime import datetime
 
 import click
 from werkzeug.utils import import_string, ImportStringError
 from jinja2 import Environment, FileSystemLoader
 from flask import current_app
 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 flaskbb import create_app
 from flaskbb._compat import iteritems
 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)
 from flaskbb.utils.populate import (create_test_data, create_welcome_forum,
                                     create_default_groups,
@@ -408,138 +411,163 @@ def list_urls(order_by):
 
 
 @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.")
-@click.option("--output", "-o", default=".",
-              help="The path where the config file will be saved at.")
-@click.option("--stdout", default=False, is_flag=True,
-              help="This will ignore --output and print the config to stdout.")
-def generate_config(debug, output, stdout):
+@click.option("--output", "-o", required=False,
+              help="The path where the config file will be saved at. "
+                   "Defaults to the flaskbb's root folder.")
+@click.option("--force", "-f", default=False, is_flag=True,
+              help="Overwrite any existing config file if one exists.")
+def generate_config(development, output, force):
     """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"
                 "This is needed to correctly generate URLs when no request "
                 "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,
-        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 "
                 "URLs when no request context is available.\n"
                 "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"),
-        type=click.Choice(["https", "http"]), default="https")
-
-    config_dict["secret_key"] = binascii.hexlify(os.urandom(24)).decode()
-    config_dict["csrf_secret_key"] = binascii.hexlify(os.urandom(24)).decode()
+        type=click.Choice(["https", "http"]),
+        default=default_conf.get("url_scheme"))
 
+    # SQLALCHEMY_DATABASE_URI
     click.secho("For Postgres use:\n"
                 "    postgresql://flaskbb@localhost:5432/flaskbb\n"
                 "For more options see the SQLAlchemy docs:\n"
                 "    http://docs.sqlalchemy.org/en/latest/core/engines.html",
                 fg="cyan")
-    config_dict["database_url"] = click.prompt(
+    default_conf["database_url"] = click.prompt(
         click.style("Database URI", fg="magenta"),
-        default="sqlite:///" + os.path.join(
-            os.path.dirname(current_app.root_path), "flaskbb.sqlite"))
+        default=default_conf.get("database_uri"))
 
+    # REDIS_ENABLED
     click.secho("Redis will be used for things such as the task queue, "
                 "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"),
-        default=True)
+        default=True)  # default_conf.get("redis_enabled") is False
 
-    if config_dict.get("redis_enabled", False):
-        config_dict["redis_url"] = click.prompt(
+    # REDIS_URI
+    if default_conf.get("redis_enabled", False):
+        default_conf["redis_uri"] = click.prompt(
             click.style("Redis URI", fg="magenta"),
-            default="redis://localhost:6379")
+            default=default_conf.get("redis_uri"))
     else:
-        config_dict["redis_url"] = ""
+        default_conf["redis_uri"] = ""
 
+    # MAIL_SERVER
     click.secho("To use 'localhost' make sure that you have sendmail or\n"
                 "something similar installed. Gmail is also supprted.",
                 fg="cyan")
-    config_dict["mail_server"] = click.prompt(
+    default_conf["mail_server"] = click.prompt(
         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.",
                 fg="cyan")
-    config_dict["mail_port"] = click.prompt(
+    default_conf["mail_port"] = click.prompt(
         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 "
                 "not needed. For external servers this is hopefully required.",
                 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"),
-        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")
-    config_dict["mail_use_ssl"] = click.confirm(
+    default_conf["mail_use_ssl"] = click.confirm(
         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 "
                 "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"),
-        default="")
+        default=default_conf.get("mail_username"))
+    # MAIL_PASSWORD
     click.secho("Not needed if using a local smtp server. For gmail you have "
                 "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"),
-        default="")
+        default=default_conf.get("mail_password"))
+    # MAIL_DEFAULT_SENDER
     click.secho("The name of the sender. You probably want to change it to "
                 "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"),
-        default="FlaskBB Mailer")
+        default=default_conf.get("mail_sender_name"))
     click.secho("On localhost you want to use a noreply address here. "
                 "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"),
-        default="noreply@yourdomain.org")
+        default=default_conf.get("mail_sender_address"))
+    # ADMINS
     click.secho("Logs and important system messages are sent to this address."
                 "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"),
-        default="admin@youremailaddress.org")
+        default=default_conf.get("mail_admin_address"))
 
-    config_env = Environment(
-        loader=FileSystemLoader(os.path.join(current_app.root_path, "configs"))
-    )
-    config_template = config_env.get_template('config.cfg.template')
+    write_config(default_conf, config_template, config_path)
 
-    if not stdout:
-        config_dict["config_path"] = os.path.join(
-            os.path.dirname(current_app.root_path), "flaskbb.cfg"
-        )
-        click.secho("The path where you want to save this configuration file.",
-                    fg="cyan")
-        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))
+    # Finished
+    click.secho("The configuration file has been saved to:\n{cfg}"
+                .format(cfg=config_path), fg="blue")
+    click.secho("Usage: flaskbb --config {cfg} run\n"
+                "Feel free to adjust it as needed."
+                .format(cfg=config_path), fg="green")

+ 39 - 2
flaskbb/cli/utils.py

@@ -12,11 +12,10 @@
 import sys
 import os
 import re
-import binascii
 
 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 flaskbb import __version__
@@ -137,3 +136,41 @@ def get_version(ctx, param, value):
         'python_version': sys.version.split("\n")[0]
     }, color=ctx.color)
     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 datetime
 from flaskbb.configs.default import DefaultConfig
@@ -29,7 +31,7 @@ PREFERRED_URL_SCHEME = "{{ url_scheme }}"
 # For PostgresSQL:
 #SQLALCHEMY_DATABASE_URI = "postgresql://flaskbb@localhost:5432/flaskbb"
 # For SQLite:
-SQLALCHEMY_DATABASE_URI = "{{ database_url }}"
+SQLALCHEMY_DATABASE_URI = "{{ database_uri }}"
 
 # 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
@@ -92,14 +94,14 @@ WHOOSHEE_MIN_STRING_LEN = 3
 #   - Caching
 #   - Rate Limiting
 REDIS_ENABLED = {{ redis_enabled }}
-REDIS_URL = "{{ redis_url }}"
+REDIS_URL = "{{ redis_uri }}"
 REDIS_DATABASE = 0
 
 
 # Celery
 # ------------------------------
-CELERY_BROKER_URL = "{{ redis_url }}"
-CELERY_RESULT_BACKEND = "{{ redis_url }}"
+CELERY_BROKER_URL = "{{ redis_uri }}"
+CELERY_RESULT_BACKEND = "{{ redis_uri }}"
 
 
 # Rate Limiting
@@ -116,7 +118,7 @@ CELERY_RESULT_BACKEND = "{{ redis_url }}"
 # Using the redis storage requires the installation of the redis package,
 # which will be installed if you enable REDIS_ENABLE while memcached
 # 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