From a33e4421a88fcd1f822e67d3203945187426f36e Mon Sep 17 00:00:00 2001 From: Francesco Siddi Date: Thu, 24 Mar 2016 15:16:37 +0100 Subject: [PATCH] Introducing Project creation Authorised users can now create projects. The before and after insert projects hooks take care of stripping unwanted urls and attaching default node_type and permissions, as well as initialising a storage bucket per project. We are temporarily switching to the development version of the gcloud library, since it allows the creation of EU-based buckets. --- pillar/application/modules/projects.py | 85 ++++++++++++++++++++++++++ pillar/application/utils/gcs.py | 10 ++- pillar/manage.py | 43 ++++++++++++- requirements.txt | 6 +- 4 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 pillar/application/modules/projects.py diff --git a/pillar/application/modules/projects.py b/pillar/application/modules/projects.py new file mode 100644 index 00000000..0cdae93b --- /dev/null +++ b/pillar/application/modules/projects.py @@ -0,0 +1,85 @@ +import logging +from flask import g +from flask import abort +from eve.methods.put import put_internal +from eve.methods.post import post_internal +from application import app +from application.utils import remove_private_keys +from application.utils.gcs import GoogleCloudStorageBucket +from manage.node_types.asset import node_type_asset +from manage.node_types.group import node_type_group +from manage.node_types.page import node_type_page +from manage.node_types.comment import node_type_comment + +log = logging.getLogger(__name__) + + +def before_inserting_projects(items): + """Strip unwanted properties, that will be assigned after creation. Also, + verify permission to create a project (check quota, check role). + + :param items: List of project docs that have been inserted (normally one) + """ + for item in items: + item.pop('url', None) + + +def after_inserting_projects(items): + """After inserting a project in the collection we do some processing such as: + - apply the right permissions + - define basic node types + - optionally generate a url + - initialize storage space + + :param items: List of project docs that have been inserted (normally one) + """ + current_user = g.get('current_user', None) + users_collection = app.data.driver.db['users'] + user = users_collection.find_one({'_id': current_user['user_id']}) + + for item in items: + # Create a project specific group (with name matching the project id) + project_group = dict(name=str(item['_id'])) + group = post_internal('groups', project_group) + # If Group creation failed, stop + # TODO: undo project creation + if group[3] != 201: + abort(group[3]) + else: + group = group[0] + # Assign the current user to the group + if 'groups' in user: + user['groups'].append(group['_id']) + else: + user['groups'] = [group['_id']] + put_internal('users', remove_private_keys(user), _id=user['_id']) + # Assign the group to the project with admin rights + permissions = dict( + world=['GET'], + users=[], + groups=[ + dict(group=group['_id'], + methods=['GET', 'PUT', 'POST']) + ] + ) + # Assign permissions to the project itself, as well as to the node_types + item['permissions'] = permissions + node_type_asset['permissions'] = permissions + node_type_group['permissions'] = permissions + node_type_page['permissions'] = permissions + node_type_comment['permissions'] = permissions + # Assign the basic 'group', 'asset' and 'page' node_types + item['node_types'] = [ + node_type_group, + node_type_asset, + node_type_page, + node_type_comment] + # TODO: Depending on user role or status, assign the url attribute + # Initialize storage page (defaults to GCS) + gcs_storage = GoogleCloudStorageBucket(str(item['_id'])) + if gcs_storage.bucket.exists(): + log.debug("Created CGS bucket {0}".format(item['_id'])) + # Assign a url based on the project id + item['url'] = "p-{}".format(item['_id']) + # Commit the changes + put_internal('projects', remove_private_keys(item), _id=item['_id']) diff --git a/pillar/application/utils/gcs.py b/pillar/application/utils/gcs.py index a56711cd..697b5c92 100644 --- a/pillar/application/utils/gcs.py +++ b/pillar/application/utils/gcs.py @@ -26,9 +26,17 @@ class GoogleCloudStorageBucket(object): def __init__(self, bucket_name, subdir='_/'): gcs = Client() - self.bucket = gcs.get_bucket(bucket_name) + try: + self.bucket = gcs.get_bucket(bucket_name) + except NotFound: + self.bucket = gcs.bucket(bucket_name) + # Hardcode the bucket location to EU + self.bucket.location = 'EU' + self.bucket.create() + self.subdir = subdir + def List(self, path=None): """Display the content of a subdir in the project bucket. If the path points to a file the listing is simply empty. diff --git a/pillar/manage.py b/pillar/manage.py index 9d672c3e..fd5ff85f 100755 --- a/pillar/manage.py +++ b/pillar/manage.py @@ -9,10 +9,12 @@ from flask.ext.script import Manager # Use a sensible default when running manage.py commands. if not os.environ.get('EVE_SETTINGS'): - settings_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings.py') + settings_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'settings.py') os.environ['EVE_SETTINGS'] = settings_path from application import app +from application.utils.gcs import GoogleCloudStorageBucket from manage.node_types.asset import node_type_asset from manage.node_types.blog import node_type_blog from manage.node_types.comment import node_type_comment @@ -106,6 +108,45 @@ def setup_db(): print("Created user {0}".format(user[0]['_id'])) # TODO: Create a default project + groups_collection = app.data.driver.db['groups'] + admin_group = groups_collection.find_one({'name': 'admin'}) + + default_permissions = dict( + world=['GET'], + users=[], + groups=[ + dict(group=admin_group['_id'], + methods=['GET', 'PUT', 'POST']) + ] + ) + + node_type_blog['permissions'] = default_permissions + node_type_post['permissions'] = default_permissions + node_type_comment['permissions'] = default_permissions + + project = dict( + owners=dict(users=[], groups=[]), + description='Default Project', + name='Default Project', + node_types=[ + node_type_blog, + node_type_post, + node_type_comment + ], + status='published', + user=user[0]['_id'], + is_private=False, + permissions=default_permissions, + url='default-project', + summary='Default Project summary', + category='training' + ) + project = post_internal('projects', project) + print("Created default project {0}".format(project[0]['_id'])) + gcs_storage = GoogleCloudStorageBucket(str(project[0]['_id'])) + + if gcs_storage.bucket.exists(): + print("Created CGS instance") @manager.command diff --git a/requirements.txt b/requirements.txt index 40b2d11d..3270350a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ Eve==0.5.3 Events==0.2.1 Flask-Script==2.0.5 flup==1.0.2 -gcloud==0.11.0 +-e git+https://github.com/GoogleCloudPlatform/gcloud-python.git@dbb22a7fddcf12228bba7b191774f2c4a2adfe35#egg=gcloud google-apitools==0.4.11 httplib2==0.9.2 idna==2.0 @@ -22,3 +22,7 @@ simplejson==3.8.1 WebOb==1.5.0 wheel==0.24.0 zencoder==0.6.5 + +# development requirements +httpretty==0.8.14 +pytest==2.9.1