Skip to content

Instantly share code, notes, and snippets.

@tell-k
Created November 6, 2011 09:24
Show Gist options
  • Save tell-k/1342640 to your computer and use it in GitHub Desktop.
Save tell-k/1342640 to your computer and use it in GitHub Desktop.
Setup Flaskr on Heroku with Amazon RDS
#!/bin/sh
########################################
# Setup Flaskr on Heroku with Amazon RDS
#
# filename: setup_flaskr_on_heroku.sh
#
# dependencies:
# - heroku account
# - bash
# - git
# - virtualenv
# - pip
# - rds-command-line-tools
#
# refs:
# - http://flask.pocoo.org/docs/tutorial/
# - http://d.hatena.ne.jp/Voluntas/20110920/1316529816
# - http://d.hatena.ne.jp/deeeki/20110606/rds_modify_db_parameter_group
# - http://d.hatena.ne.jp/blaue_fuchs/20111005/1317802532
# - http://tjstein.com/2011/09/running-wordpress-on-heroku-and-amazon-rds/
# - http://devcenter.heroku.com/articles/python
# - http://teps4545.blogspot.com/2010/01/amazon-rds.html
#
# author: tell-k
# url: http://d.hatena.ne.jp/tell-k/
########################################
if [ $1 ]; then
APP_NAME=$1
else
echo 'set application name! ex) sh setup_flaskr_on_heroku.sh [app_name] [username] [password]'
exit
fi
if [ $2 ]; then
USERNAME=$2
else
echo 'set user name! ex) sh setup_flaskr_on_heroku.sh [app_name] [username] [password]'
exit
fi
if [ $3 ]; then
PASSWORD=$3
else
echo 'set password! ex) sh setup_flaskr_on_heroku.sh [app_name] [username] [password]'
exit
fi
DB_USERNAME=$USERNAME
DB_PASSWORD=$PASSWORD
echo "create Amazon RDS instance...."
echo "create new db paramter group"
rds-create-db-parameter-group ${APP_NAME}-dbparam \
--db-parameter-group-family MySQL5.5 \
--description "dbparameter for flaskr+heroku" \
--region us-east-1
rds-modify-db-parameter-group ${APP_NAME}-dbparam -p "name=character_set_client, value=utf8, method=immediate"
rds-modify-db-parameter-group ${APP_NAME}-dbparam -p "name=character_set_connection, value=utf8, method=immediate"
rds-modify-db-parameter-group ${APP_NAME}-dbparam -p "name=character_set_database, value=utf8, method=immediate"
rds-modify-db-parameter-group ${APP_NAME}-dbparam -p "name=character_set_results, value=utf8, method=immediate"
rds-modify-db-parameter-group ${APP_NAME}-dbparam -p "name=character_set_server, value=utf8, method=immediate"
rds-modify-db-parameter-group ${APP_NAME}-dbparam -p "name=collation_server,value=utf8_general_ci, method=immediate"
echo "create new db instance"
rds-create-db-instance --db-instance-identifier ${APP_NAME}-instance\
--allocated-storage 5 \
--db-instance-class db.m1.small \
--engine MySQL \
--engine-version 5.5 \
--master-username $DB_USERNAME \
--master-user-password $DB_PASSWORD \
--db-name ${APP_NAME}db\
--region us-east-1\
--multi-az true\
--db-parameter-group-name ${APP_NAME}-dbparam\
--headers
echo "edit default security group."
GLOBAL_IP=`wget -q -O - ipcheck.ieserver.net`
rds-authorize-db-security-group-ingress default --cidr-ip $GLOBAL_IP/32 --region us-east-1
echo "edit default security group. add heroku ec2 security group."
rds-authorize-db-security-group-ingress default \
--ec2-security-group-name default \
--ec2-security-group-owner-id 098166147350 \
--region us-east-1
echo "create heroku application ..."
mkdir $APP_NAME
cd $APP_NAME
echo "create requirments.txt ..."
cat > requirements.txt <<'EOF'
Flask
Flask-SQLAlchemy
Flask-Script
MySQL-Python
gunicorn
EOF
echo "create Procfile ..."
cat > Procfile <<'EOF'
web: gunicorn apps.flaskr:app -b "0.0.0.0:$PORT"
EOF
echo "create .gitignore ..."
cat > .gitignore <<'EOF'
env
*.pyc
*.swp
EOF
echo "create Flaskr Application ..."
SECRET_KEY=`od -vAn -tx1 -N16 </dev/urandom |tr -d '[:space:]' |sed -e 'a\'`
mkdir apps
echo "create apps/__init__py ..."
touch apps/__init__.py
echo "create apps/flaskr.py ..."
cat > apps/flaskr.py <<'EOF'
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# all the imports
import os
from contextlib import closing
from flask import Flask, request, session, g, redirect, url_for, \
abort, render_template, flash
from flaskext.sqlalchemy import SQLAlchemy
# configuration
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
DEBUG = True
SECRET_KEY = 'flaskr_secret_key'
USERNAME = 'flaskr_user_name'
PASSWORD = 'flaskr_password'
app = Flask(__name__)
app.config.from_object(__name__)
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
db = SQLAlchemy(app)
class Entry(db.Model):
__tablename__ = "entries"
id = db.Column(db.Integer, db.Sequence('%s_id_seq' % __tablename__), primary_key=True)
title = db.Column(db.String(256), nullable=False)
text = db.Column(db.String(256), nullable=False)
@app.before_request
def before_request():
g.db = db
@app.teardown_request
def teardown_request(exception):
if hasattr(g, 'db') and g.db:
g.db.session.close()
@app.route('/')
def show_entries():
entries = Entry.query.all()
return render_template('show_entries.html', entries=entries)
@app.route('/add', methods=['POST'])
def add_entry():
if not session.get('logged_in'):
abort(401)
db.session.add(Entry(title=request.form['title'], text=request.form['text']))
db.session.commit()
flash('New entry was successfully posted')
return redirect(url_for('show_entries'))
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != app.config['USERNAME']:
error = 'Invalid username'
elif request.form['password'] != app.config['PASSWORD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in')
return redirect(url_for('show_entries'))
return render_template('login.html', error=error)
@app.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('show_entries'))
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(host='0.0.0.0', port=port)
EOF
sed -i -e s/flaskr_secret_key/$SECRET_KEY/ apps/flaskr.py
sed -i -e s/flaskr_user_name/$USERNAME/ apps/flaskr.py
sed -i -e s/flaskr_password/$PASSWORD/ apps/flaskr.py
rm -fr apps/flaskr.py-e
echo "create apps/manage.py ..."
cat > apps/manage.py <<'EOF'
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flaskext.script import Manager
from flaskr import app, db
manager = Manager(app)
@manager.command
def syncdb():
db.engine.execute('alter database default character set utf8;')
db.create_all()
if __name__ == "__main__":
manager.run()
EOF
echo "create static files ..."
echo "create apps/static/style.css ..."
mkdir apps/static
cat > apps/static/style.css <<'EOF'
body { font-family: sans-serif; background: #eee; }
a, h1, h2 { color: #377BA8; }
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
h1 { border-bottom: 2px solid #eee; }
h2 { font-size: 1.2em; }
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
padding: 0.8em; background: white; }
.entries { list-style: none; margin: 0; padding: 0; }
.entries li { margin: 0.8em 1.2em; }
.entries li h2 { margin-left: -1em; }
.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry dl { font-weight: bold; }
.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;
margin-bottom: 1em; background: #fafafa; }
.flash { background: #CEE5F5; padding: 0.5em;
border: 1px solid #AACBE2; }
.error { background: #F0D6D6; padding: 0.5em; }
EOF
echo "create templates ..."
mkdir apps/templates
echo "create apps/templates/layout.html ..."
cat > apps/templates/layout.html <<'EOF'
<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class=page>
<h1>Flaskr</h1>
<div class=metanav>
{% if not session.logged_in %}
<a href="{{ url_for('login') }}">log in</a>
{% else %}
<a href="{{ url_for('logout') }}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}
<div class=flash>{{ message }}</div>
{% endfor %}
{% block body %}{% endblock %}
</div>
EOF
echo "create apps/templates/login.html ..."
cat > apps/templates/login.html <<'EOF'
{% extends "layout.html" %}
{% block body %}
<h2>Login</h2>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
<form action="{{ url_for('login') }}" method=post>
<dl>
<dt>Username:
<dd><input type=text name=username>
<dt>Password:
<dd><input type=password name=password>
<dd><input type=submit value=Login>
</dl>
</form>
{% endblock %}
EOF
echo "create apps/templates/show_entries.html ..."
cat > apps/templates/show_entries.html <<'EOF'
{% extends "layout.html" %}
{% block body %}
{% if session.logged_in %}
<form action="{{ url_for('add_entry') }}" method=post class=add-entry>
<dl>
<dt>Title:
<dd><input type=text size=30 name=title>
<dt>Text:
<dd><textarea name=text rows=5 cols=40></textarea>
<dd><input type=submit value=Share>
</dl>
</form>
{% endif %}
<ul class=entries>
{% for entry in entries %}
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
{% else %}
<li><em>Unbelievable. No entries here so far</em>
{% endfor %}
</ul>
{% endblock %}
EOF
echo "create virtualenv..."
virtualenv --no-site-package --distribute env
source env/bin/activate
pip install -r requirements.txt
pip freeze > requirements.txt
echo "create git repository and heroku application..."
git init
git add .
git commit -m 'first commit'
heroku create --stack cedar
echo "push heroku application..."
git push heroku master
heroku scale web=1
echo 'Wating for create RDS instance. about 10 minutes.'
while true; do
DISCRIBE=`rds-describe-db-instances ${APP_NAME}-instance --region us-east-1`
RDS_HOST_REGEX="${APP_NAME}-instance\..*\.rds\.amazonaws\.com"
RDS_HOST=`echo $DISCRIBE | sed -e "s/^.*\(${RDS_HOST_REGEX}\).*$/\1/"`
if expr "$RDS_HOST" : ".*rds.amazonaws.com" >/dev/null; then
echo 'complete. creating RDS instance'
break;
else
echo '.'
fi
sleep 10
done
echo "heroku addons amazon_rds..."
heroku addons:add amazon_rds url=mysql://${DB_USERNAME}:${DB_PASSWORD}@${RDS_HOST}/${APP_NAME}db
echo "flaskr syncdb..."
heroku run bin/python apps/manage.py syncdb
echo "open application..."
heroku apps:open
echo "Complete!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment