cookiecutter-django/hooks/post_gen_project.py
Nikita Shupeyko 275c13292c
Integrate Flower with Docker Compose setup (#1655)
* Integrate Flower with Docker Compose setup locally

* Remove alien worker celeryd option

* Move Flower COPY section below the worker's

* Remove set -o pipefail command from Flower start script

* Flower client authentication

* Override flower service image name

* Move flower service to the end of local.yml

* Install flower==0.9.2 in all environments

* Introduce production flower service

* Fix local flower start script

* Document Flower integration

* Prettify *.django envs

Rationale: consistency.

* Reference local environment Flower docs from the production's

* 'two more services' -> 'three more services'
2018-06-27 19:33:21 +03:00

333 lines
9.6 KiB
Python

"""
NOTE:
the below code is to be maintained Python 2.x-compatible
as the whole Cookiecutter Django project initialization
can potentially be run in Python 2.x environment
(at least so we presume in `pre_gen_project.py`).
TODO: ? restrict Cookiecutter Django project initialization to Python 3.x environments only
"""
from __future__ import print_function
import os
import random
import shutil
import string
try:
# Inspired by
# https://github.com/django/django/blob/master/django/utils/crypto.py
random = random.SystemRandom()
using_sysrandom = True
except NotImplementedError:
using_sysrandom = False
TERMINATOR = "\x1b[0m"
WARNING = "\x1b[1;33m [WARNING]: "
INFO = "\x1b[1;33m [INFO]: "
HINT = "\x1b[3;33m"
SUCCESS = "\x1b[1;32m [SUCCESS]: "
DEBUG_VALUE = "debug"
def remove_open_source_files():
file_names = ["CONTRIBUTORS.txt"]
for file_name in file_names:
os.remove(file_name)
def remove_gplv3_files():
file_names = ["COPYING"]
for file_name in file_names:
os.remove(file_name)
def remove_pycharm_files():
idea_dir_path = ".idea"
if os.path.exists(idea_dir_path):
shutil.rmtree(idea_dir_path)
docs_dir_path = os.path.join("docs", "pycharm")
if os.path.exists(docs_dir_path):
shutil.rmtree(docs_dir_path)
def remove_docker_files():
shutil.rmtree("compose")
file_names = ["local.yml", "production.yml", ".dockerignore"]
for file_name in file_names:
os.remove(file_name)
def remove_heroku_files():
file_names = ["Procfile", "runtime.txt", "requirements.txt"]
for file_name in file_names:
os.remove(file_name)
def remove_gulp_files():
file_names = ["gulpfile.js"]
for file_name in file_names:
os.remove(file_name)
def remove_packagejson_file():
file_names = ["package.json"]
for file_name in file_names:
os.remove(file_name)
def remove_celery_app():
shutil.rmtree(os.path.join("{{ cookiecutter.project_slug }}", "taskapp"))
def remove_dottravisyml_file():
os.remove(".travis.yml")
def append_to_project_gitignore(path):
gitignore_file_path = ".gitignore"
with open(gitignore_file_path, "a") as gitignore_file:
gitignore_file.write(path)
gitignore_file.write(os.linesep)
def generate_random_string(
length, using_digits=False, using_ascii_letters=False, using_punctuation=False
):
"""
Example:
opting out for 50 symbol-long, [a-z][A-Z][0-9] string
would yield log_2((26+26+50)^50) ~= 334 bit strength.
"""
if not using_sysrandom:
return None
symbols = []
if using_digits:
symbols += string.digits
if using_ascii_letters:
symbols += string.ascii_letters
if using_punctuation:
symbols += string.punctuation.replace('"', "").replace("'", "").replace(
"\\", ""
)
return "".join([random.choice(symbols) for _ in range(length)])
def set_flag(file_path, flag, value=None, formatted=None, *args, **kwargs):
if value is None:
random_string = generate_random_string(*args, **kwargs)
if random_string is None:
print(
"We couldn't find a secure pseudo-random number generator on your system. "
"Please, make sure to manually {} later.".format(flag)
)
random_string = flag
if formatted is not None:
random_string = formatted.format(random_string)
value = random_string
with open(file_path, "r+") as f:
file_contents = f.read().replace(flag, value)
f.seek(0)
f.write(file_contents)
f.truncate()
return value
def set_django_secret_key(file_path):
django_secret_key = set_flag(
file_path,
"!!!SET DJANGO_SECRET_KEY!!!",
length=64,
using_digits=True,
using_ascii_letters=True,
)
return django_secret_key
def set_django_admin_url(file_path):
django_admin_url = set_flag(
file_path,
"!!!SET DJANGO_ADMIN_URL!!!",
formatted="{}/",
length=32,
using_digits=True,
using_ascii_letters=True,
)
return django_admin_url
def generate_random_user():
return generate_random_string(length=32, using_ascii_letters=True)
def generate_postgres_user(debug=False):
return DEBUG_VALUE if debug else generate_random_user()
def set_postgres_user(file_path, value):
postgres_user = set_flag(
file_path,
"!!!SET POSTGRES_USER!!!",
value=value,
)
return postgres_user
def set_postgres_password(file_path, value=None):
postgres_password = set_flag(
file_path,
"!!!SET POSTGRES_PASSWORD!!!",
value=value,
length=64,
using_digits=True,
using_ascii_letters=True,
)
return postgres_password
def set_celery_flower_user(file_path, value):
celery_flower_user = set_flag(
file_path,
"!!!SET CELERY_FLOWER_USER!!!",
value=value,
)
return celery_flower_user
def set_celery_flower_password(file_path, value=None):
celery_flower_password = set_flag(
file_path,
"!!!SET CELERY_FLOWER_PASSWORD!!!",
value=value,
length=64,
using_digits=True,
using_ascii_letters=True,
)
return celery_flower_password
def append_to_gitignore_file(s):
with open(".gitignore", "a") as gitignore_file:
gitignore_file.write(s)
gitignore_file.write(os.linesep)
def set_flags_in_envs(
postgres_user,
celery_flower_user,
debug=False,
):
local_django_envs_path = os.path.join(".envs", ".local", ".django")
production_django_envs_path = os.path.join(".envs", ".production", ".django")
local_postgres_envs_path = os.path.join(".envs", ".local", ".postgres")
production_postgres_envs_path = os.path.join(".envs", ".production", ".postgres")
set_django_secret_key(production_django_envs_path)
set_django_admin_url(production_django_envs_path)
set_postgres_user(local_postgres_envs_path, value=postgres_user)
set_postgres_password(local_postgres_envs_path, value=DEBUG_VALUE if debug else None)
set_postgres_user(production_postgres_envs_path, value=postgres_user)
set_postgres_password(production_postgres_envs_path, value=DEBUG_VALUE if debug else None)
set_celery_flower_user(local_django_envs_path, value=celery_flower_user)
set_celery_flower_password(local_django_envs_path, value=DEBUG_VALUE if debug else None)
set_celery_flower_user(production_django_envs_path, value=celery_flower_user)
set_celery_flower_password(production_django_envs_path, value=DEBUG_VALUE if debug else None)
def set_flags_in_settings_files():
set_django_secret_key(os.path.join("config", "settings", "local.py"))
set_django_secret_key(os.path.join("config", "settings", "test.py"))
def remove_envs_and_associated_files():
shutil.rmtree(".envs")
os.remove("merge_production_dotenvs_in_dotenv.py")
def remove_celery_compose_dirs():
shutil.rmtree(os.path.join("compose", "local", "django", "celery"))
shutil.rmtree(os.path.join("compose", "production", "django", "celery"))
def main():
debug = "{{ cookiecutter.debug }}".lower() == "y"
set_flags_in_envs(
DEBUG_VALUE if debug else generate_random_user(),
DEBUG_VALUE if debug else generate_random_user(),
debug=debug,
)
set_flags_in_settings_files()
if "{{ cookiecutter.open_source_license }}" == "Not open source":
remove_open_source_files()
if "{{ cookiecutter.open_source_license}}" != "GPLv3":
remove_gplv3_files()
if "{{ cookiecutter.use_pycharm }}".lower() == "n":
remove_pycharm_files()
if "{{ cookiecutter.use_docker }}".lower() == "n":
remove_docker_files()
if "{{ cookiecutter.use_heroku }}".lower() == "n":
remove_heroku_files()
if (
"{{ cookiecutter.use_docker }}".lower() == "n"
and "{{ cookiecutter.use_heroku }}".lower() == "n"
):
if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y":
print(
INFO + ".env(s) are only utilized when Docker Compose and/or "
"Heroku support is enabled so keeping them does not "
"make sense given your current setup." + TERMINATOR
)
remove_envs_and_associated_files()
else:
append_to_gitignore_file(".env")
append_to_gitignore_file(".envs/*")
if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y":
append_to_gitignore_file("!.envs/.local/")
if "{{ cookiecutter.js_task_runner}}".lower() == "none":
remove_gulp_files()
remove_packagejson_file()
if (
"{{ cookiecutter.js_task_runner }}".lower() != "none"
and "{{ cookiecutter.use_docker }}".lower() == "y"
):
print(
WARNING
+ "Docker and {} JS task runner ".format(
"{{ cookiecutter.js_task_runner }}".lower().capitalize()
)
+ "working together not supported yet. "
"You can continue using the generated project like you "
"normally would, however you would need to add a JS "
"task runner service to your Docker Compose configuration "
"manually." + TERMINATOR
)
if "{{ cookiecutter.use_celery }}".lower() == "n":
remove_celery_app()
if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_celery_compose_dirs()
if "{{ cookiecutter.use_travisci }}".lower() == "n":
remove_dottravisyml_file()
print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR)
if __name__ == "__main__":
main()