Created
November 6, 2011 09:24
-
-
Save tell-k/1342640 to your computer and use it in GitHub Desktop.
Setup Flaskr on Heroku with Amazon RDS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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