Conflicts:
	README.md
This commit is contained in:
Joshua McKenty 2013-12-09 09:55:00 -08:00
commit 12c2639281
20 changed files with 789 additions and 222 deletions

View File

@ -1,21 +0,0 @@
RefStack
========
Vendor-facing API for registration of interop-compliance endpoints and credentials for on-demand testing.
Running at http://refstack.org
See (living) documentation at https://etherpad.openstack.org/RefStackBlueprint
TODO:
=====
Metadata gathering:
- Number of nodes
- vCPUs
- Distro
- Deployment approach
- RAM
Plugins:
- Cinder
- Neutron

59
README.rst Executable file
View File

@ -0,0 +1,59 @@
RefStack
========
Vendor-facing API for registration of interop-compliance endpoints and credentials for on-demand testing.
Running at http://refstack.org
See (living) documentation at https://etherpad.openstack.org/RefStackBlueprint
Okay, I'm Sold, How Do I Run This Myself?
-----------------------------------------
This is our documentation for how we get this set up::
# Git you clonin'
git clone http://github.com/openstack-ops/refstack
cd refstack
# Setup or update the database
# NOTE: you are going to have to modify the db connection string in
# `alembic.ini` to get this working
# PROTIP: if you just want to test this out, use `-n alembic_sqlite` to
# make a local sqlite db
# $ alembic -n alembic_sqlite update head
alembic update head
# Plug this bad boy into your server infrastructure.
# We use nginx and gunicorn, you may use something else if you are smarter
# than we are.
# For the most basic setup that you can try right now, just kick off
# gunicorn:
gunicorn refstack.web:app
# To actually configure this winner, check out the config section and
# crack open refstack.cfg in vim.
# `vim refstack.cfg`
# Now browse to http://localhost:8000
Configuration
-------------
Coming soon!
TODO:
=====
Metadata gathering:
- Number of nodes
- vCPUs
- Distro
- Deployment approach
- RAM
Plugins:
- Cinder
- Neutron

View File

@ -14,6 +14,13 @@ script_location = alembic
sqlalchemy.url = driver://user:pass@localhost/dbname
[alembic_sqlite]
# path to migration scripts
script_location = alembic
sqlalchemy.url = sqlite:///db.sqlite
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

View File

@ -24,14 +24,10 @@ sys.path.append("./")
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
from refstack import app
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
cur_db_uri = config.get_section_option('alembic', 'sqlalchemy.url')
my_db_uri = 'sqlite:////tmp/refstack.db' # app.config.get('SQLALCHEMY_DATABASE_URI', cur_db_uri)
config.set_section_option('alembic', 'sqlalchemy.url', my_db_uri)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
@ -42,9 +38,13 @@ fileConfig(config.config_file_name)
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
# target_metadata = None
from refstack.models import *
target_metadata = Base.metadata
from refstack.models import db
target_metadata = db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
@ -92,4 +92,3 @@ if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@ -0,0 +1,31 @@
"""empty message
Revision ID: 449461dbc725
Revises: 59e15d864941
Create Date: 2013-11-26 16:57:16.062788
"""
# revision identifiers, used by Alembic.
revision = '449461dbc725'
down_revision = '59e15d864941'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table('apikey',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=60), nullable=True),
sa.Column('key', sa.String(length=200), nullable=True),
sa.Column('openid', sa.String(length=200), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('timestamp', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id'),
)
def downgrade():
op.drop_Table('apikey')

39
fabfile.py vendored
View File

@ -1,39 +0,0 @@
#!/usr/bin/env python
#
# Copyright (c) 2013 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from fabric.api import env, roles, run, cd
# Define sets of servers as roles
env.roledefs = {
'web': ['refstack.org'],
}
# Set the user to use for ssh
env.user = 'refstack'
@roles('web')
def get_version():
run('cat /etc/issue')
@roles('web')
def deploy():
with cd('/var/www/refstack'):
run('git checkout master')
run('git pull')
run('sudo pip install -r requirements.txt')
run('alembic upgrade head')
run('sudo uwsgi --reload /tmp/project-master_refstack.pid')

28
refstack/admin.py Normal file
View File

@ -0,0 +1,28 @@
import flask
from flask.ext.admin.contrib import sqla
from refstack import models
# Global admin object
from .extensions import admin
from .extensions import db
class SecureView(sqla.ModelView):
def is_accessible(self):
# let us look at the admin if we're in debug mode
if flask.current_app.debug:
return True
return flask.g.user.su is not False
def init_app(app):
admin.init_app(app)
def configure_admin(app):
admin.add_view(SecureView(models.ApiKey, db.session))
admin.add_view(SecureView(models.Cloud, db.session))
admin.add_view(SecureView(models.User, db.session))
admin.add_view(SecureView(models.Vendor, db.session))

68
refstack/api.py Normal file
View File

@ -0,0 +1,68 @@
"""Basic API code.
This is using Flask-Restless at the moment because it is super simple,
but probably should be moved to a more versatile framework like
Flask-Restful later on.
"""
import flask
from flask.ext import restless
from refstack import models
from refstack.extensions import api
def init_app(app, *args, **kw):
api.init_app(app, *args, **kw)
def access_control(**kw):
if not flask.g.user:
raise _not_authorized()
if not flask.g.user.su:
return _not_authorized()
# That's it, we're defaulting to superuser only access
# until we flesh this out further
ALL_METHODS = {'GET_SINGLE': [access_control],
'GET_MANY': [access_control],
'PUT_SINGLE': [access_control],
'PUT_MANY': [access_control],
'POST': [access_control],
'DELETE': [access_control]}
def configure_api(app):
cloud_api = api.create_api_blueprint(models.Cloud,
preprocessors=ALL_METHODS)
cloud_api.before_request(authenticate)
app.register_blueprint(cloud_api)
def _not_authorized():
return restless.ProcessingException(message='Not Authorized',
status_code=401)
def authenticate():
# If we're already authenticated, we can ignore this
if flask.g.user:
return
# Otherwise check headers
openid = flask.request.headers.get('X-AUTH-OPENID')
if openid:
# In debug mode accept anything
if flask.current_app.debug and False:
flask.g.user = models.User.query.filter_by(openid=openid).first()
return
apikey = flask.request.headers.get('X-AUTH-APIKEY')
apikey_ref = models.ApiKey.query.filter_by(key=apikey)
if apikey_ref['openid'] == openid:
flask.g.user = apikey_ref.user

239
refstack/app.py Executable file → Normal file
View File

@ -1,50 +1,209 @@
#!/usr/bin/env python
#
# Copyright (c) 2013 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# -*- coding: utf-8 -*-
"""Common Flask app config."""
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
import os
import flask
from flask import Flask, request, render_template
#from flask.ext.babel import Babel
from .config import DefaultConfig
#from .user import User, user
#from .settings import settings
#from .frontend import frontend
#from .api import api
#from .admin import admin
#from .extensions import db, mail, cache, login_manager, oid
from refstack import admin
from refstack import api
from .extensions import db
from .extensions import oid
#db_path = os.path.abspath(
# os.path.join(os.path.basename(__file__), "../"))
db_path = 'tmp'
from refstack import utils
app = flask.Flask(__name__)
INSTANCE_FOLDER_PATH = utils.INSTANCE_FOLDER_PATH
app.config['MAILGUN_KEY'] = '#@#@#@#@'
app.config['MAILGUN_DOMAIN'] = 'refstack.org'
app.config['SECRET_KEY'] = '#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@'
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get(
'DATABASE_URL', 'sqlite:///%s/refstack.db' % (db_path))
app.config['DEBUG'] = True
app.config['SECURITY_PASSWORD_HASH'] = 'sha512_crypt'
app.config['SECURITY_PASSWORD_SALT'] = app.config['SECRET_KEY']
app.config['SECURITY_POST_LOGIN_VIEW'] = 'dashboard'
app.config['SECURITY_RECOVERABLE'] = True
app.config['SECURITY_REGISTERABLE'] = True
app.config['SECURITY_EMAIL_SENDER'] = "refstack.org"
app.config['MAIL_SERVER'] = 'smtp.refstack.org'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = 'postmaster@refstack.org'
app.config['MAIL_PASSWORD'] = '1234'
# For import *
__all__ = ['create_app']
DEFAULT_BLUEPRINTS = tuple()
# frontend,
# user,
# settings,
# api,
# admin,
#)
def create_app(config=None, app_name=None, blueprints=None):
"""Create a Flask app."""
if app_name is None:
app_name = DefaultConfig.PROJECT
if blueprints is None:
blueprints = DEFAULT_BLUEPRINTS
# NOTE(termie): Flask has this new instance_path stuff that allows
# you to keep config and such in different places, but I don't really
# see how that is going to be very helpful, so we're going to stick
# to using config relative to the root unless we explicitly set such
# a path in the INSTANCE_FOLDER_PATH environment variable.
app = Flask(app_name,
instance_path=INSTANCE_FOLDER_PATH,
instance_relative_config=True)
configure_app(app, config)
configure_hook(app)
configure_blueprints(app, blueprints)
configure_extensions(app)
configure_logging(app)
configure_template_filters(app)
configure_error_handlers(app)
if app.debug:
print utils.dump_config(app)
return app
def configure_app(app, config=None):
"""Different ways of configurations."""
# http://flask.pocoo.org/docs/api/#configuration
app.config.from_object(DefaultConfig)
# If we've set the INSTANCE_FOLDER_PATH environment var, this may be
# loaded from an instance folder, otherwise relative to flask.root_path.
# http://flask.pocoo.org/docs/config/#instance-folders
app.config.from_pyfile('refstack.cfg', silent=True)
if config:
app.config.from_object(config)
# Use instance folder instead of env variables to make deployment easier.
#app.config.from_envvar('%s_APP_CONFIG' % DefaultConfig.PROJECT.upper(), silent=True)
def configure_extensions(app):
# flask-sqlalchemy
db.init_app(app)
# flask-admin
admin.init_app(app)
admin.configure_admin(app)
# flask-restless
api.init_app(app, flask_sqlalchemy_db=db)
api.configure_api(app)
## flask-mail
#mail.init_app(app)
## flask-cache
#cache.init_app(app)
## flask-babel
#babel = Babel(app)
#@babel.localeselector
#def get_locale():
# accept_languages = app.config.get('ACCEPT_LANGUAGES')
# return request.accept_languages.best_match(accept_languages)
## flask-login
#login_manager.login_view = 'frontend.login'
#login_manager.refresh_view = 'frontend.reauth'
#@login_manager.user_loader
#def load_user(id):
# return User.query.get(id)
#login_manager.setup_app(app)
# flask-openid
oid.init_app(app)
def configure_blueprints(app, blueprints):
"""Configure blueprints in views."""
for blueprint in blueprints:
app.register_blueprint(blueprint)
def configure_template_filters(app):
@app.template_filter()
def pretty_date(value):
return pretty_date(value)
@app.template_filter()
def format_date(value, format='%Y-%m-%d'):
return value.strftime(format)
def configure_logging(app):
"""Configure file(info) and email(error) logging."""
if app.debug or app.testing:
# Skip debug and test mode. Just check standard output.
return
import logging
from logging.handlers import SMTPHandler
# Set info level on logger, which might be overwritten by handlers.
# Suppress DEBUG messages.
app.logger.setLevel(logging.INFO)
info_log = os.path.join(app.config['LOG_FOLDER'], 'info.log')
info_file_handler = logging.handlers.RotatingFileHandler(info_log, maxBytes=100000, backupCount=10)
info_file_handler.setLevel(logging.INFO)
info_file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s '
'[in %(pathname)s:%(lineno)d]')
)
app.logger.addHandler(info_file_handler)
# Testing
#app.logger.info("testing info.")
#app.logger.warn("testing warn.")
#app.logger.error("testing error.")
mail_handler = SMTPHandler(app.config['MAIL_SERVER'],
app.config['MAIL_USERNAME'],
app.config['ADMINS'],
'O_ops... %s failed!' % app.config['PROJECT'],
(app.config['MAIL_USERNAME'],
app.config['MAIL_PASSWORD']))
mail_handler.setLevel(logging.ERROR)
mail_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s '
'[in %(pathname)s:%(lineno)d]')
)
app.logger.addHandler(mail_handler)
def configure_hook(app):
@app.before_request
def before_request():
pass
def configure_error_handlers(app):
@app.errorhandler(403)
def forbidden_page(error):
return render_template("errors/forbidden_page.html"), 403
@app.errorhandler(404)
def page_not_found(error):
return render_template("errors/page_not_found.html"), 404
@app.errorhandler(500)
def server_error_page(error):
return render_template("errors/server_error.html"), 500

77
refstack/config.py Normal file
View File

@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
import os
from utils import make_dir, INSTANCE_FOLDER_PATH, PROJECT_ROOT
class BaseConfig(object):
PROJECT = "refstack"
# The app root path, also can use flask.root_path.
PROJECT_ROOT = PROJECT_ROOT
DEBUG = False
TESTING = False
ADMINS = ['youremail@yourdomain.com']
# http://flask.pocoo.org/docs/quickstart/#sessions
SECRET_KEY = 'secret key'
LOG_FOLDER = os.path.join(INSTANCE_FOLDER_PATH, 'logs')
make_dir(LOG_FOLDER)
# Fild upload, should override in production.
# Limited the maximum allowed payload to 16 megabytes.
# http://flask.pocoo.org/docs/patterns/fileuploads/#improving-uploads
MAX_CONTENT_LENGTH = 16 * 1024 * 1024
UPLOAD_FOLDER = os.path.join(INSTANCE_FOLDER_PATH, 'uploads')
make_dir(UPLOAD_FOLDER)
class DefaultConfig(BaseConfig):
DEBUG = True
# Flask-Sqlalchemy: http://packages.python.org/Flask-SQLAlchemy/config.html
SQLALCHEMY_ECHO = True
# SQLITE for prototyping.
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + INSTANCE_FOLDER_PATH + '/db.sqlite'
# MYSQL for production.
#SQLALCHEMY_DATABASE_URI = 'mysql://username:password@server/db?charset=utf8'
# Flask-babel: http://pythonhosted.org/Flask-Babel/
ACCEPT_LANGUAGES = ['zh']
BABEL_DEFAULT_LOCALE = 'en'
# Flask-cache: http://pythonhosted.org/Flask-Cache/
CACHE_TYPE = 'simple'
CACHE_DEFAULT_TIMEOUT = 60
# Flask-mail: http://pythonhosted.org/flask-mail/
# https://bitbucket.org/danjac/flask-mail/issue/3/problem-with-gmails-smtp-server
MAIL_DEBUG = DEBUG
MAIL_SERVER = 'smtp.gmail.com'
MAIL_PORT = 587
MAIL_USE_TLS = True
MAIL_USE_SSL = False
# Should put MAIL_USERNAME and MAIL_PASSWORD in production under instance folder.
MAIL_USERNAME = 'yourmail@gmail.com'
MAIL_PASSWORD = 'yourpass'
MAIL_DEFAULT_SENDER = MAIL_USERNAME
# Flask-openid: http://pythonhosted.org/Flask-OpenID/
OPENID_FS_STORE_PATH = os.path.join(INSTANCE_FOLDER_PATH, 'openid')
make_dir(OPENID_FS_STORE_PATH)
class TestConfig(BaseConfig):
TESTING = True
WTF_CSRF_ENABLED = False
SQLALCHEMY_ECHO = False
SQLALCHEMY_DATABASE_URI = 'sqlite://'

17
refstack/decorators.py Normal file
View File

@ -0,0 +1,17 @@
#-*- coding: utf-8 -*-
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
from functools import wraps
from flask import abort
from flask.ext.login import current_user
def admin_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_admin():
abort(403)
return f(*args, **kwargs)
return decorated_function

View File

@ -0,0 +1,47 @@
#
# Copyright (c) 2013 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
###############################################################
#
#
# THIS FILE IS NOT USED
# (leaveing it around briefly for reference)
#
#
###############################################################
db_path = '/tmp'
class Default(object):
MAILGUN_KEY = '#@#@#@#@'
MAILGUN_DOMAIN = 'refstack.org'
SECRET_KEY = '#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@'
SQLALCHEMY_DATABASE_URI = os.environ.get(
'DATABASE_URL', 'sqlite:///%s/refstack.db' % (db_path))
DEBUG = True
SECURITY_PASSWORD_HASH = 'sha512_crypt'
SECURITY_PASSWORD_SALT = SECRET_KEY
SECURITY_POST_LOGIN_VIEW = 'dashboard'
SECURITY_RECOVERABLE = True
SECURITY_REGISTERABLE = True
SECURITY_EMAIL_SENDER = "refstack.org"
MAIL_SERVER = 'smtp.refstack.org'
MAIL_PORT = 465
MAIL_USE_SSL = True
MAIL_USERNAME = 'postmaster@refstack.org'
MAIL_PASSWORD = '1234'

25
refstack/extensions.py Normal file
View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
from flask.ext.admin import Admin
admin = Admin()
from flask.ext.restless import APIManager
api = APIManager()
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy()
from flask.ext.mail import Mail
mail = Mail()
# TODO(termie): not used yet
#from flask.ext.cache import Cache
#cache = Cache()
from flask.ext.login import LoginManager
login_manager = LoginManager()
from flask.ext.openid import OpenID
oid = OpenID()

View File

@ -13,44 +13,25 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""striaght up sqlalchemy declarative_base model structure.
*I created this because i was having a problem getting
the cli to use the models that were generated for the flask
webapp. The plan is to use this for both. But I have not
started my serious efforts on the web interface. dl 10.2013
*For now in dev I have this database in /tmp we can talk
about someplace else for it by default.
"""
from datetime import datetime
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker,relationship, backref
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Binary, Boolean
engine = create_engine('sqlite:////tmp/refstack.db', convert_unicode=True)
db = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db.query_property()
from .extensions import db
class User(Base):
class User(db.Model):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
vendor_id = Column(Integer, ForeignKey('vendor.id'))
vendor = relationship('Vendor',
backref=backref('clouds',
id = db.Column(db.Integer, primary_key=True)
vendor_id = db.Column(db.Integer, db.ForeignKey('vendor.id'))
vendor = db.relationship('Vendor',
backref=db.backref('clouds',
lazy='dynamic'))
name = Column(String(60))
email = Column(String(200), unique=True)
email_verified = Column(Boolean)
openid = Column(String(200), unique=True)
authorized = Column(Boolean, default=False)
su = Column(Boolean, default=False)
name = db.Column(db.String(60))
email = db.Column(db.String(200), unique=True)
email_verified = db.Column(db.Boolean)
openid = db.Column(db.String(200), unique=True)
authorized = db.Column(db.Boolean, default=False)
su = db.Column(db.Boolean, default=False)
def __init__(self, name, email, openid):
self.name = name
@ -61,63 +42,76 @@ class User(Base):
return self.name
class ApiKey(db.Model):
__tablename__ = 'apikey'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60))
key = db.Column(db.String(200))
openid = db.Column(db.String(200))
timestamp = db.Column(db.DateTime, default=datetime.now)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('User',
backref=db.backref('apikeys', lazy='dynamic'))
"""
Note: The vendor list will be pre-populated from the sponsoring company database.
TODO: better define the vendor object and its relationship with user
it needs the ability to facilitate a login.
"""
class Vendor(Base):
class Vendor(db.Model):
__tablename__ = 'vendor'
id = Column(Integer, primary_key=True)
vendor_name = Column(String(80), unique=True)
contact_email = Column(String(120), unique=True)
contact_name = Column(String(120), unique=False)
id = db.Column(db.Integer, primary_key=True)
vendor_name = db.Column(db.String(80), unique=True)
contact_email = db.Column(db.String(120), unique=True)
contact_name = db.Column(db.String(120), unique=False)
def __str__(self):
return self.vendor_name
class Cloud(Base):
class Cloud(db.Model):
"""*need to take the time to descibe this stuff in detail.
"""
__tablename__ = 'cloud'
id = Column(Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=True)
label = Column(String(60), unique=False)
endpoint = Column(String(120), unique=True)
test_user = Column(String(80), unique=False)
test_key = Column(String(80), unique=False)
admin_endpoint = Column(String(120), unique=False)
admin_user = Column(String(80), unique=False)
admin_key = Column(String(80), unique=False)
label = db.Column(db.String(60), unique=False)
endpoint = db.Column(db.String(120), unique=True)
test_user = db.Column(db.String(80), unique=False)
test_key = db.Column(db.String(80), unique=False)
admin_endpoint = db.Column(db.String(120), unique=False)
admin_user = db.Column(db.String(80), unique=False)
admin_key = db.Column(db.String(80), unique=False)
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship('User',
backref=backref('clouds',lazy='dynamic'))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('User',
backref=db.backref('clouds',lazy='dynamic'))
class Test(Base):
class Test(db.Model):
__tablename__ = 'test'
id = Column(Integer, primary_key=True)
cloud_id = Column(Integer, ForeignKey('cloud.id'))
cloud = relationship('Cloud',
backref=backref('tests',lazy='dynamic'))
config = Column(String(4096))
id = db.Column(db.Integer, primary_key=True)
cloud_id = db.Column(db.Integer, db.ForeignKey('cloud.id'))
cloud = db.relationship('Cloud',
backref=db.backref('tests',lazy='dynamic'))
config = db.Column(db.String(4096))
def __init__(self, cloud_id):
self.cloud_id = cloud_id
class TestStatus(Base):
class TestStatus(db.Model):
__tablename__ = 'test_status'
id = Column(Integer, primary_key=True)
test_id = Column(Integer, ForeignKey('test.id'))
test = relationship('Test',
backref=backref('status',lazy='dynamic'))
message = Column(String(1024))
finished = Column(Boolean, default=False)
timestamp = Column(DateTime, default=datetime.now)
id = db.Column(db.Integer, primary_key=True)
test_id = db.Column(db.Integer, db.ForeignKey('test.id'))
test = db.relationship('Test',
backref=db.backref('status',lazy='dynamic'))
message = db.Column(db.String(1024))
finished = db.Column(db.Boolean, default=False)
timestamp = db.Column(db.DateTime, default=datetime.now)
def __init__(self,test_id, message, finished=False):
@ -126,14 +120,14 @@ class TestStatus(Base):
self.finished = finished
class TestResults(Base):
class TestResults(db.Model):
__tablename__ = 'test_results'
id = Column(Integer, primary_key=True)
test_id = Column(Integer, ForeignKey('test.id'))
test = relationship('Test',
backref=backref('results',lazy='dynamic'))
timestamp = Column(DateTime, default=datetime.now)
subunit = Column(String(8192))
blob = Column(Binary)
id = db.Column(db.Integer, primary_key=True)
test_id = db.Column(db.Integer, db.ForeignKey('test.id'))
test = db.relationship('Test',
backref=db.backref('results',lazy='dynamic'))
timestamp = db.Column(db.DateTime, default=datetime.now)
subunit = db.Column(db.String(8192))
blob = db.Column(db.Binary)

View File

@ -1,3 +0,0 @@
CREATE TABLE IF NOT EXISTS VENDORS
(vendor_id integer primary key asc autoincrement,
vendor_name TEXT NOT NULL);

View File

@ -5,19 +5,19 @@
<script src="{{ url_for('static', filename='jquery-1.10.1.min.js') }}"></script>
<script src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"></script>
<!-- refstack specific js-->
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" />
<link rel="stylesheet" type="text/css" href="/static/toast.css">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='refstack.css') }}">
<!-- compiled and minified bootstrap JavaScript -->
{% block head_css %}{% endblock %}
</head>
<body>
<body>
<div class="container">
<div class="header">
<div class="header">
<a href="/" class="logo">
<span class="glyphicon glyphicon-check"></span>
Refstack
@ -32,7 +32,7 @@
<li class="dropdown">
<span class="glyphicon glyphicon-cog"></span>
<a data-toggle="dropdown" href="#">
Admin
<span class="caret"></span>
</a>
@ -46,7 +46,7 @@
<li >
<a href="/admin/userview/">Users</a>
</li>
</ul>
</li>
<li>
@ -96,4 +96,4 @@
{% block tail_js %}{% endblock %}
<script src="{{ url_for('static', filename='refstack.js') }}"></script>
</body>
</html>
</html>

116
refstack/utils.py Normal file
View File

@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
# This file based on MIT licensed code at: https://github.com/imwilsonxu/fbone
"""
Utils has nothing to do with models and views.
"""
from datetime import datetime
import logging
import os
import pprint
import random
import string
# Instance folder path, if set, otherwise project root.
PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
INSTANCE_FOLDER_PATH = os.environ.get('INSTANCE_FOLDER_PATH', PROJECT_ROOT)
ALLOWED_AVATAR_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
# Form validation
USERNAME_LEN_MIN = 4
USERNAME_LEN_MAX = 25
REALNAME_LEN_MIN = 4
REALNAME_LEN_MAX = 25
PASSWORD_LEN_MIN = 6
PASSWORD_LEN_MAX = 16
AGE_MIN = 1
AGE_MAX = 300
DEPOSIT_MIN = 0.00
DEPOSIT_MAX = 9999999999.99
# Sex type.
MALE = 1
FEMALE = 2
OTHER = 9
SEX_TYPE = {
MALE: u'Male',
FEMALE: u'Female',
OTHER: u'Other',
}
# Model
STRING_LEN = 64
def get_current_time():
return datetime.utcnow()
def pretty_date(dt, default=None):
"""
Returns string representing "time since" e.g.
3 days ago, 5 hours ago etc.
Ref: https://bitbucket.org/danjac/newsmeme/src/a281babb9ca3/newsmeme/
"""
if default is None:
default = 'just now'
now = datetime.utcnow()
diff = now - dt
periods = (
(diff.days / 365, 'year', 'years'),
(diff.days / 30, 'month', 'months'),
(diff.days / 7, 'week', 'weeks'),
(diff.days, 'day', 'days'),
(diff.seconds / 3600, 'hour', 'hours'),
(diff.seconds / 60, 'minute', 'minutes'),
(diff.seconds, 'second', 'seconds'),
)
for period, singular, plural in periods:
if not period:
continue
if period == 1:
return u'%d %s ago' % (period, singular)
else:
return u'%d %s ago' % (period, plural)
return default
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_AVATAR_EXTENSIONS
def id_generator(size=10, chars=string.ascii_letters + string.digits):
#return base64.urlsafe_b64encode(os.urandom(size))
return ''.join(random.choice(chars) for x in range(size))
def make_dir(dir_path):
try:
if not os.path.exists(dir_path):
os.mkdir(dir_path)
except Exception, e:
raise e
### Begin Non-Fbone stuff
def dump_config(app):
"""Useful to dump app config for debug purposes."""
return pprint.pformat(dict(app.config.iteritems()))

View File

@ -13,6 +13,10 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import logging
import flask
from flask import Flask, abort, flash, request, redirect, url_for, \
render_template, g, session
from flask_openid import OpenID
@ -24,32 +28,33 @@ from wtforms import Form, BooleanField, TextField, \
PasswordField, validators
from flask_mail import Mail
from refstack.app import app
from refstack.models import *
from refstack import app as base_app
from refstack.extensions import db
from refstack.extensions import oid
from refstack import api
from refstack.models import ApiKey
from refstack.models import Cloud
from refstack.models import Test
from refstack.models import TestResults
from refstack.models import TestStatus
from refstack.models import User
from refstack.models import Vendor
# TODO(termie): transition all the routes below to blueprints
# TODO(termie): use extensions setup from the create_app() call
app = base_app.create_app()
mail = Mail(app)
# setup flask-openid
oid = OpenID(app)
admin = Admin(app, base_template='admin/master.html')
class SecureView(ModelView):
def is_accessible(self):
return g.user.su is not False
admin.add_view(SecureView(Vendor, db))
admin.add_view(SecureView(Cloud, db))
admin.add_view(SecureView(User, db))
@app.before_request
def before_request():
"""Runs before the request itself."""
g.user = None
flask.g.user = None
if 'openid' in session:
g.user = User.query.filter_by(openid=session['openid']).first()
flask.g.user = User.query.filter_by(openid=session['openid']).first()
@app.route('/', methods=['POST', 'GET'])
@ -112,8 +117,8 @@ def create_profile():
flash(u'Error: you have to enter a valid email address')
else:
flash(u'Profile successfully created')
db.add(User(name, email, session['openid']))
db.commit()
db.session.add(User(name, email, session['openid']))
db.session.commit()
return redirect(oid.get_next_url())
return render_template(
'create_profile.html', next_url=oid.get_next_url())
@ -129,8 +134,8 @@ def delete_cloud(cloud_id):
elif not c.user_id == g.user.id:
flash(u"This isn't your cloud!")
else:
db.delete(c)
db.commit()
db.session.delete(c)
db.session.commit()
return redirect('/')
@ -170,7 +175,7 @@ def edit_cloud(cloud_id):
c.admin_user = request.form['admin_user']
c.admin_key = request.form['admin_key']
db.commit()
db.session.commit()
flash(u'Cloud Saved!')
return redirect('/')
@ -220,8 +225,8 @@ def create_cloud():
c.admin_user = request.form['admin_user']
c.admin_key = request.form['admin_key']
db.add(c)
db.commit()
db.session.add(c)
db.session.commit()
return redirect('/')
return render_template('create_cloud.html', next_url='/')
@ -235,8 +240,8 @@ def edit_profile():
form = dict(name=g.user.name, email=g.user.email)
if request.method == 'POST':
if 'delete' in request.form:
db.delete(g.user)
db.commit()
db.session.delete(g.user)
db.session.commit()
session['openid'] = None
flash(u'Profile deleted')
return redirect(url_for('index'))
@ -250,7 +255,7 @@ def edit_profile():
flash(u'Profile successfully created')
g.user.name = form['name']
g.user.email = form['email']
db.commit()
db.session.commit()
return redirect(url_for('edit_profile'))
return render_template('edit_profile.html', form=form)
@ -270,6 +275,3 @@ def logout():
session.pop('openid', None)
flash(u'You have been signed out')
return redirect(oid.get_next_url())

View File

@ -1,13 +1,14 @@
Flask==0.9
Flask-Admin==1.0.6
Flask==0.10.1
Flask-Admin==1.0.7
Flask-Login==0.1.3
Flask-Mail==0.8.2
Flask-OpenID==1.1.1
Flask-Principal==0.3.5
Flask-SQLAlchemy==0.16
Flask-SQLAlchemy==1.0
Flask-Security==1.6.3
Flask-WTF==0.8.3
SQLAlchemy==0.8.1
Flask-Restless=0.12.0
SQLAlchemy==0.8.3
WTForms==1.0.4
Werkzeug==0.8.3
alembic==0.5.0