diff --git a/.gitignore b/.gitignore index f3e9dd5..0152c92 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ *.bak *.orig *~ +.project +.pydevproject diff --git a/.openshift/README.md b/.openshift/README.md new file mode 100644 index 0000000..6265e00 --- /dev/null +++ b/.openshift/README.md @@ -0,0 +1,25 @@ +# Django Template for OpenShift + +## Template App Information +Product: Django +Version: 1.4 +Source: https://github.com/django/django.git +Commit: 2591fb8d4c0246f68b79554976c012039df75359 + +## Maintenance +This folder contains a diff file that includes the changes made to the +stock Django app in order to make it OpenShift-Template-ready. If +you are a maintainer tasked with updating the Django template, you +may be able to use this patch file on the updated Django code to +automatically reapply these changes. + +Here are the steps involved: + +1. Under the 'wsgi' directory, apply any patches required to update the 'openshift' Django app. +2. From the template root directory, run 'git apply --check .openshift/template.patch' to test for patching problems. +3. Next run 'git am --signoff < .openshift/template.patch' to apply the patch to the template. + +If this process succeeds, then the changes have been automatically +applied. Otherwise it may be necessary to manually apply the +changes. If the base package has changed enough, you may need to +re-audit the base code and generate a new patch file. diff --git a/.openshift/action_hooks/build b/.openshift/action_hooks/build index 1176dd4..9aec2e8 100755 --- a/.openshift/action_hooks/build +++ b/.openshift/action_hooks/build @@ -1,19 +1,7 @@ #!/bin/bash -# This is a simple build script, place your post-deploy but pre-start commands -# in this script. This script gets executed directly, so it could be python, -# php, ruby, etc. +# This is a simple build script and will be executed on your CI system if +# available. Otherwise it will execute while your application is stopped +# before the deploy step. This script gets executed directly, so it +# could be python, php, ruby, etc. # Activate VirtualEnv in order to use the correct libraries -source $OPENSHIFT_APP_DIR/virtenv/bin/activate - -if [ ! -f $OPENSHIFT_DATA_DIR/sqlite3.db ] -then - echo "Copying $OPENSHIFT_REPO_DIR/wsgi/openshift/sqlite3.db to $OPENSHIFT_DATA_DIR" - cp $OPENSHIFT_REPO_DIR/wsgi/openshift/sqlite3.db $OPENSHIFT_DATA_DIR/ -else - echo "Executing 'python $OPENSHIFT_REPO_DIR/wsgi/openshift/manage.py syncdb --noinput'" - python $OPENSHIFT_REPO_DIR/wsgi/openshift/manage.py syncdb --noinput -fi - -echo "Executing 'python $OPENSHIFT_REPO_DIR/wsgi/openshift/manage.py collectstatic --noinput'" -python $OPENSHIFT_REPO_DIR/wsgi/openshift/manage.py collectstatic --noinput diff --git a/.openshift/action_hooks/deploy b/.openshift/action_hooks/deploy new file mode 100755 index 0000000..cbf4fb1 --- /dev/null +++ b/.openshift/action_hooks/deploy @@ -0,0 +1,20 @@ +#!/bin/bash +# This deploy hook gets executed after dependencies are resolved and the +# build hook has been run but before the application has been started back +# up again. This script gets executed directly, so it could be python, php, +# ruby, etc. + +source $OPENSHIFT_HOMEDIR/python/virtenv/bin/activate + +if [ ! -f $OPENSHIFT_DATA_DIR/sqlite3.db ] +then + echo "Copying $OPENSHIFT_REPO_DIR/wsgi/openshift/sqlite3.db to $OPENSHIFT_DATA_DIR" + cp "$OPENSHIFT_REPO_DIR"wsgi/openshift/sqlite3.db $OPENSHIFT_DATA_DIR + python "$OPENSHIFT_REPO_DIR".openshift/action_hooks/secure_db.py | tee ${OPENSHIFT_DATA_DIR}/CREDENTIALS +else + echo "Executing 'python $OPENSHIFT_REPO_DIR/wsgi/openshift/manage.py syncdb --noinput'" + python "$OPENSHIFT_REPO_DIR"wsgi/openshift/manage.py syncdb --noinput +fi + +echo "Executing 'python $OPENSHIFT_REPO_DIR/wsgi/openshift/manage.py collectstatic --noinput'" +python "$OPENSHIFT_REPO_DIR"wsgi/openshift/manage.py collectstatic --noinput diff --git a/.openshift/action_hooks/post_deploy b/.openshift/action_hooks/post_deploy new file mode 100755 index 0000000..a57d1f5 --- /dev/null +++ b/.openshift/action_hooks/post_deploy @@ -0,0 +1,4 @@ +#!/bin/bash +# This is a simple post deploy hook executed after your application +# is deployed and started. This script gets executed directly, so +# it could be python, php, ruby, etc. \ No newline at end of file diff --git a/.openshift/action_hooks/pre_build b/.openshift/action_hooks/pre_build new file mode 100755 index 0000000..10cd544 --- /dev/null +++ b/.openshift/action_hooks/pre_build @@ -0,0 +1,5 @@ +#!/bin/bash +# This is a simple script and will be executed on your CI system if +# available. Otherwise it will execute while your application is stopped +# before the build step. This script gets executed directly, so it +# could be python, php, ruby, etc. \ No newline at end of file diff --git a/.openshift/action_hooks/secure_db.py b/.openshift/action_hooks/secure_db.py new file mode 100755 index 0000000..5d36b94 --- /dev/null +++ b/.openshift/action_hooks/secure_db.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +import hashlib, imp, os, sqlite3 + +# Load the openshift helper library +lib_path = os.environ['OPENSHIFT_REPO_DIR'] + 'wsgi/openshift/' +modinfo = imp.find_module('openshiftlibs', [lib_path]) +openshiftlibs = imp.load_module('openshiftlibs', modinfo[0], modinfo[1], modinfo[2]) + +# Open the database +conn = sqlite3.connect(os.environ['OPENSHIFT_DATA_DIR'] + '/sqlite3.db') +c = conn.cursor() + +# Grab the default security info +c.execute('SELECT password FROM AUTH_USER WHERE id = 1') +pw_info = c.fetchone()[0] + +# The password is stored as [hashtype]$[salt]$[hashed] +pw_fields = pw_info.split("$") +hashtype = pw_fields[0] +old_salt = pw_fields[1] +old_pass = pw_fields[2] + +# Randomly generate a new password and a new salt +# The PASSWORD value below just sets the length (12) +# for the real new password. +old_keys = { 'SALT': old_salt, 'PASS': '123456789ABC' } +use_keys = openshiftlibs.openshift_secure(old_keys) + +# Encrypt the new password +new_salt = use_keys['SALT'] +new_pass = use_keys['PASS'] +new_hashed = hashlib.sha1(new_salt + new_pass).hexdigest() +new_pw_info = "$".join([hashtype,new_salt,new_hashed]) + +# Update the database +c.execute('UPDATE AUTH_USER SET password = ? WHERE id = 1', [new_pw_info]) +conn.commit() +c.close() +conn.close() + +# Print the new password info +print "Django application credentials:\n\tuser: admin\n\t" + new_pass diff --git a/.openshift/template.patch b/.openshift/template.patch new file mode 100644 index 0000000..1f22aca --- /dev/null +++ b/.openshift/template.patch @@ -0,0 +1,133 @@ +From 2b49faba38b8ceb8abe639b5f7ec022a59f47ce0 Mon Sep 17 00:00:00 2001 +From: "N. Harrison Ripps" +Date: Mon, 23 Jul 2012 11:05:13 -0400 +Subject: [PATCH] Added changes to templatize the quick start. + +--- + wsgi/openshift/openshiftlibs.py | 81 +++++++++++++++++++++++++++++++++++++++ + wsgi/openshift/settings.py | 14 ++++++- + 2 files changed, 93 insertions(+), 2 deletions(-) + create mode 100644 wsgi/openshift/openshiftlibs.py + +diff --git a/wsgi/openshift/openshiftlibs.py b/wsgi/openshift/openshiftlibs.py +new file mode 100644 +index 0000000..a11e0e5 +--- /dev/null ++++ b/wsgi/openshift/openshiftlibs.py +@@ -0,0 +1,81 @@ ++#!/usr/bin/env python ++import hashlib, inspect, os, random, sys ++ ++# Gets the secret token provided by OpenShift ++# or generates one (this is slightly less secure, but good enough for now) ++def get_openshift_secret_token(): ++ token = os.getenv('OPENSHIFT_SECRET_TOKEN') ++ name = os.getenv('OPENSHIFT_APP_NAME') ++ uuid = os.getenv('OPENSHIFT_APP_UUID') ++ if token is not None: ++ return token ++ elif (name is not None and uuid is not None): ++ return hashlib.sha256(name + '-' + uuid).hexdigest() ++ return None ++ ++# Loop through all provided variables and generate secure versions ++# If not running on OpenShift, returns defaults and logs an error message ++# ++# This function calls secure_function and passes an array of: ++# { ++# 'hash': generated sha hash, ++# 'variable': name of variable, ++# 'original': original value ++# } ++def openshift_secure(default_keys, secure_function = 'make_secure_key'): ++ # Attempts to get secret token ++ my_token = get_openshift_secret_token() ++ ++ # Only generate random values if on OpenShift ++ my_list = default_keys ++ ++ if my_token is not None: ++ # Loop over each default_key and set the new value ++ for key, value in default_keys.iteritems(): ++ # Create hash out of token and this key's name ++ sha = hashlib.sha256(my_token + '-' + key).hexdigest() ++ # Pass a dictionary so we can add stuff without breaking existing calls ++ vals = { 'hash': sha, 'variable': key, 'original': value } ++ # Call user specified function or just return hash ++ my_list[key] = sha ++ if secure_function is not None: ++ # Pick through the global and local scopes to find the function. ++ possibles = globals().copy() ++ possibles.update(locals()) ++ supplied_function = possibles.get(secure_function) ++ if not supplied_function: ++ raise Exception("Cannot find supplied security function") ++ else: ++ my_list[key] = supplied_function(vals) ++ else: ++ calling_file = inspect.stack()[1][1] ++ if os.getenv('OPENSHIFT_REPO_DIR'): ++ base = os.getenv('OPENSHIFT_REPO_DIR') ++ calling_file.replace(base,'') ++ sys.stderr.write("OPENSHIFT WARNING: Using default values for secure variables, please manually modify in " + calling_file + "\n") ++ ++ return my_list ++ ++ ++# This function transforms default keys into per-deployment random keys; ++def make_secure_key(key_info): ++ hashcode = key_info['hash'] ++ key = key_info['variable'] ++ original = key_info['original'] ++ ++ chars = '0123456789abcdef' ++ ++ # Use the hash to seed the RNG ++ random.seed(int("0x" + hashcode[:8], 0)) ++ ++ # Create a random string the same length as the default ++ rand_key = '' ++ for _ in range(len(original)): ++ rand_pos = random.randint(0,len(chars)) ++ rand_key += chars[rand_pos:(rand_pos+1)] ++ ++ # Reset the RNG ++ random.seed() ++ ++ # Set the value ++ return rand_key +diff --git a/wsgi/openshift/settings.py b/wsgi/openshift/settings.py +index 842669e..2f44079 100644 +--- a/wsgi/openshift/settings.py ++++ b/wsgi/openshift/settings.py +@@ -1,6 +1,6 @@ + # -*- coding: utf-8 -*- + # Django settings for openshift project. +-import os ++import imp, os + + # a setting to determine whether we are running on OpenShift + ON_OPENSHIFT = False +@@ -104,8 +104,18 @@ STATICFILES_FINDERS = ( + #'django.contrib.staticfiles.finders.DefaultStorageFinder', + ) + ++# Make a dictionary of default keys ++default_keys = { 'SECRET_KEY': 'vm4rl5*ymb@2&d_(gc$gb-^twq9w(u69hi--%$5xrh!xk(t%hw' } ++ ++# Replace default keys with dynamic values if we are in OpenShift ++use_keys = default_keys ++if ON_OPENSHIFT: ++ imp.find_module('openshiftlibs') ++ import openshiftlibs ++ use_keys = openshiftlibs.openshift_secure(default_keys) ++ + # Make this unique, and don't share it with anybody. +-SECRET_KEY = 'vm4rl5*ymb@2&d_(gc$gb-^twq9w(u69hi--%$5xrh!xk(t%hw' ++SECRET_KEY = use_keys['SECRET_KEY'] + + # List of callables that know how to import templates from various sources. + TEMPLATE_LOADERS = ( +-- +1.7.5.4 + diff --git a/README b/README index 75f37d5..d07b890 100644 --- a/README +++ b/README @@ -8,28 +8,31 @@ libs/ - Additional libraries data/ - For not-externally exposed wsgi code setup.py - Standard setup.py, specify deps here ../data - For persistent data (also env var: OPENSHIFT_DATA_DIR) -.openshift/action_hooks/build - Script that gets run every push, just prior to - starting your app +.openshift/action_hooks/pre_build - Script that gets run every git push before the build +.openshift/action_hooks/build - Script that gets run every git push as part of the build process (on the CI system if available) +.openshift/action_hooks/deploy - Script that gets run every git push after build but before the app is restarted +.openshift/action_hooks/post_deploy - Script that gets run every git push after the app is restarted Environment Variables ===================== -OpenShift Express provides several environment variables to reference for ease +OpenShift provides several environment variables to reference for ease of use. The following list are some common variables but far from exhaustive: os.environ['OPENSHIFT_APP_NAME'] - Application name - os.environ['OPENSHIFT_APP_DIR'] - Application dir os.environ['OPENSHIFT_DATA_DIR'] - For persistent storage (between pushes) os.environ['OPENSHIFT_TMP_DIR'] - Temp storage (unmodified files deleted after 10 days) -When embedding a database using rhc-ctl-app, you can reference environment +When embedding a database using 'rhc cartridge add', you can reference environment variables for username, host and password: - os.environ['OPENSHIFT_DB_HOST'] - DB host - os.environ['OPENSHIFT_DB_PORT'] - DB Port - os.environ['OPENSHIFT_DB_USERNAME'] - DB Username - os.environ['OPENSHIFT_DB_PASSWORD'] - DB Password +If you embed MySQL, then: + + os.environ['OPENSHIFT_MYSQL_DB_HOST'] - DB host + os.environ['OPENSHIFT_MYSQL_DB_PORT'] - DB Port + os.environ['OPENSHIFT_MYSQL_DB_USERNAME'] - DB Username + os.environ['OPENSHIFT_MYSQL_DB_PASSWORD'] - DB Password To get a full list of environment variables, simply add a line in your .openshift/action_hooks/build script that says "export" and push. diff --git a/README.md b/README.md index 613c593..78af6bc 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,77 @@ -Django on OpenShift Express -============================ +Django on OpenShift +=================== -This git repository helps you get up and running quickly w/ a Django installation -on OpenShift Express. The Django project name used in this repo is 'openshift' -but you can feel free to change it. Right now the backend is sqlite3 and the -database runtime is @ $OPENSHIFT_DATA_DIR/sqlite3.db. +This git repository helps you get up and running quickly w/ a Django +installation on OpenShift. The Django project name used in this repo +is 'openshift' but you can feel free to change it. Right now the +backend is sqlite3 and the database runtime is found in +`$OPENSHIFT_DATA_DIR/sqlite3.db`. -When you push this application up for the first time, the sqlite database is -copied from wsgi/openshift/sqlite3.db. This is the stock database that is created -when 'python manage.py syncdb' is run with only the admin app installed. +Before you push this app for the first time, you will need to change +the [Django admin password](#admin-user-name-and-password). +Then, when you first push this +application to the cloud instance, the sqlite database is copied from +`wsgi/openshift/sqlite3.db` with your newly changed login +credentials. Other than the password change, this is the stock +database that is created when `python manage.py syncdb` is run with +only the admin app installed. -You can delete the database from your git repo after the first push (you probably -should for security). On subsequent pushes, a 'python manage.py syncdb' is -executed to make sure that any models you added are created in the DB. If you -do anything that requires an alter table, you could add the alter statements -in GIT_ROOT/.openshift/action_hooks/alter and then use -GIT_ROOT/.openshift/action_hooks/build to execute that script (make susre to -back up your database w/ rhc-snapshot first :) ) +On subsequent pushes, a `python manage.py syncdb` is executed to make +sure that any models you added are created in the DB. If you do +anything that requires an alter table, you could add the alter +statements in `GIT_ROOT/.openshift/action_hooks/alter.sql` and then use +`GIT_ROOT/.openshift/action_hooks/deploy` to execute that script (make +sure to back up your database w/ `rhc app snapshot save` first :) ) +You can also turn on the DEBUG mode for Django application using the +`rhc env set DEBUG=True --app APP_NAME`. If you do this, you'll get +nicely formatted error pages in browser for HTTP 500 errors. + +Do not forget to turn this environment variable off and fully restart +the application when you finish: + +``` +$ rhc env unset DEBUG +$ rhc app stop && rhc app start +``` Running on OpenShift ----------------------------- +-------------------- Create an account at http://openshift.redhat.com/ -Create a wsgi-3.2 application +Install the RHC client tools if you have not already done so: + + sudo gem install rhc + +Create a python-2.6 application - rhc-create-app -a django -t wsgi-3.2 + rhc app create -a django -t python-2.6 -Add this upstream seambooking repo +Add this upstream repo cd django git remote add upstream -m master git://github.com/openshift/django-example.git git pull -s recursive -X theirs upstream master - + Then push the repo upstream git push -That's it, you can now checkout your application at (default admin account is admin/admin): +Here, the [admin user name and password will be displayed](#admin-user-name-and-password), so pay +special attention. + +That's it. You can now checkout your application at: - http://django-$yourlogin.rhcloud.com + http://django-$yournamespace.rhcloud.com + +Admin user name and password +---------------------------- +As the `git push` output scrolls by, keep an eye out for a +line of output that starts with `Django application credentials: `. This line +contains the generated admin password that you will need to begin +administering your Django app. This is the only time the password +will be displayed, so be sure to save it somewhere. You might want +to pipe the output of the git push to a text file so you can grep for +the password later. diff --git a/setup.py b/setup.py index ae205f3..33accc8 100755 --- a/setup.py +++ b/setup.py @@ -9,5 +9,5 @@ author='Your Name', author_email='example@example.com', url='http://www.python.org/sigs/distutils-sig/', - install_requires=['Django>=1.3'], + install_requires=['Django<=1.4'], ) diff --git a/wsgi/openshift/openshiftlibs.py b/wsgi/openshift/openshiftlibs.py new file mode 100644 index 0000000..a75be18 --- /dev/null +++ b/wsgi/openshift/openshiftlibs.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +import hashlib, inspect, os, random, sys + +# Gets the secret token provided by OpenShift +# or generates one (this is slightly less secure, but good enough for now) +def get_openshift_secret_token(): + token = os.getenv('OPENSHIFT_SECRET_TOKEN') + name = os.getenv('OPENSHIFT_APP_NAME') + uuid = os.getenv('OPENSHIFT_APP_UUID') + if token is not None: + return token + elif (name is not None and uuid is not None): + return hashlib.sha256(name + '-' + uuid).hexdigest() + return None + +# Loop through all provided variables and generate secure versions +# If not running on OpenShift, returns defaults and logs an error message +# +# This function calls secure_function and passes an array of: +# { +# 'hash': generated sha hash, +# 'variable': name of variable, +# 'original': original value +# } +def openshift_secure(default_keys, secure_function = 'make_secure_key'): + # Attempts to get secret token + my_token = get_openshift_secret_token() + + # Only generate random values if on OpenShift + my_list = default_keys + + if my_token is not None: + # Loop over each default_key and set the new value + for key, value in default_keys.iteritems(): + # Create hash out of token and this key's name + sha = hashlib.sha256(my_token + '-' + key).hexdigest() + # Pass a dictionary so we can add stuff without breaking existing calls + vals = { 'hash': sha, 'variable': key, 'original': value } + # Call user specified function or just return hash + my_list[key] = sha + if secure_function is not None: + # Pick through the global and local scopes to find the function. + possibles = globals().copy() + possibles.update(locals()) + supplied_function = possibles.get(secure_function) + if not supplied_function: + raise Exception("Cannot find supplied security function") + else: + my_list[key] = supplied_function(vals) + else: + calling_file = inspect.stack()[1][1] + if os.getenv('OPENSHIFT_REPO_DIR'): + base = os.getenv('OPENSHIFT_REPO_DIR') + calling_file.replace(base,'') + sys.stderr.write("OPENSHIFT WARNING: Using default values for secure variables, please manually modify in " + calling_file + "\n") + + return my_list + + +# This function transforms default keys into per-deployment random keys; +def make_secure_key(key_info): + hashcode = key_info['hash'] + key = key_info['variable'] + original = key_info['original'] + + # These are the legal password characters + # as per the Django source code + # (django/contrib/auth/models.py) + chars = 'abcdefghjkmnpqrstuvwxyz' + chars += 'ABCDEFGHJKLMNPQRSTUVWXYZ' + chars += '23456789' + + # Use the hash to seed the RNG + random.seed(int("0x" + hashcode[:8], 0)) + + # Create a random string the same length as the default + rand_key = '' + for _ in range(len(original)): + rand_pos = random.randint(0,len(chars)) + rand_key += chars[rand_pos:(rand_pos+1)] + + # Reset the RNG + random.seed() + + # Set the value + return rand_key diff --git a/wsgi/openshift/settings.py b/wsgi/openshift/settings.py index 989ecca..d4afdc8 100644 --- a/wsgi/openshift/settings.py +++ b/wsgi/openshift/settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Django settings for openshift project. -import os +import imp, os # a setting to determine whether we are running on OpenShift ON_OPENSHIFT = False @@ -8,8 +8,12 @@ ON_OPENSHIFT = True PROJECT_DIR = os.path.dirname(os.path.realpath(__file__)) - -DEBUG = True +if ON_OPENSHIFT: + DEBUG = bool(os.environ.get('DEBUG', False)) + if DEBUG: + print("WARNING: The DEBUG environment is set to True.") +else: + DEBUG = True TEMPLATE_DEBUG = DEBUG ADMINS = ( @@ -18,8 +22,8 @@ MANAGERS = ADMINS if ON_OPENSHIFT: - # os.environ['OPENSHIFT_DB_*'] variables can be used with databases created - # with rhc-ctl-app (see /README in this git repo) + # os.environ['OPENSHIFT_MYSQL_DB_*'] variables can be used with databases created + # with rhc cartridge add (see /README in this git repo) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. @@ -67,7 +71,7 @@ # Absolute filesystem path to the directory that will hold user-uploaded files. # Example: "/home/media/media.lawrence.com/media/" -MEDIA_ROOT = '' +MEDIA_ROOT = os.environ.get('OPENSHIFT_DATA_DIR', '') # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash. @@ -104,8 +108,18 @@ #'django.contrib.staticfiles.finders.DefaultStorageFinder', ) +# Make a dictionary of default keys +default_keys = { 'SECRET_KEY': 'vm4rl5*ymb@2&d_(gc$gb-^twq9w(u69hi--%$5xrh!xk(t%hw' } + +# Replace default keys with dynamic values if we are in OpenShift +use_keys = default_keys +if ON_OPENSHIFT: + imp.find_module('openshiftlibs') + import openshiftlibs + use_keys = openshiftlibs.openshift_secure(default_keys) + # Make this unique, and don't share it with anybody. -SECRET_KEY = 'vm4rl5*ymb@2&d_(gc$gb-^twq9w(u69hi--%$5xrh!xk(t%hw' +SECRET_KEY = use_keys['SECRET_KEY'] # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = (