From 109b4b6dd2ff3b66548f65b68de8d9535b426d49 Mon Sep 17 00:00:00 2001 From: Sulochan Acharya Date: Wed, 8 Mar 2017 15:18:14 +0000 Subject: [PATCH] Adds project/user bootstrap command to dbsync So far we have been using external mysql command to insert project/user to bootstrap the process. This caused much frustration since operators needed to insert records in the db. This patch adds commands to dbsync to allow creating project/users. This method also takes care of association_id. To initiate a bootstrap: craton-dbsync --config-file=craton.conf bootstrap which will create a project with a root user. Users using Dockerfile can look at docker logs after creating the container to find this info. Closes Bug: 1670561 Change-Id: I9372961ca6623d530d7844b9f38aade544d961e8 --- Dockerfile | 13 +++--- craton/api/v1/resources/utils.py | 10 +++++ craton/cmd/dbsync.py | 17 ++++++++ craton/db/sqlalchemy/migration.py | 66 ++++++++++++++++++++++++++++++- doc/source/dev/install.rst | 28 ++----------- tools/docker_run.sh | 25 ++---------- 6 files changed, 106 insertions(+), 53 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6df71ad..9241934 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,12 +11,13 @@ # under the License. ############################################################################ -# Usage: -# docker build --pull -t craton-api:latest . -# docker run -t --name craton-api -p 127.0.0.1:7780:7780 -d craton-api:latest -# python tools/generate_fake_data.py --url http://127.0.0.1:7780/v1 --user demo --project b9f10eca66ac4c279c139d01e65f96b4 --key demo -# curl http://127.0.0.1:7780/v1/regions -H "Content-Type: application/json" -H "X-Auth-Token: demo" -H "X-Auth-User: demo" -H "X-Auth-Project: b9f10eca66ac4c279c139d01e65f96b4" -############################################################################# +## Usage: +## docker build --pull -t craton-api:latest . +## docker run -t --name craton-api -p 127.0.0.1:7780:7780 -d craton-api:latest +## docker logs and copy the username, api_key, and project_id +## python tools/generate_fake_data.py --url http://127.0.0.1:7780/v1 --user bootstrap --project --key +## Use the credentials from above to try different commands using python-cratonclient. +############################################################################## # Get Ubuntu base image FROM ubuntu:16.04 diff --git a/craton/api/v1/resources/utils.py b/craton/api/v1/resources/utils.py index 1be8038..31c4f82 100644 --- a/craton/api/v1/resources/utils.py +++ b/craton/api/v1/resources/utils.py @@ -1,3 +1,5 @@ +import binascii +import os from flask import url_for from oslo_serialization import jsonutils @@ -57,3 +59,11 @@ def add_up_link(context, device): links = device.setdefault("links", []) links.append(link) + + +def gen_api_key(): + """Generates crypto strong 16 bytes api key.""" + # NOTE(sulo): this implementation is taken from secrets + # moudule available in python 3.6 + tbytes = os.urandom(16) + return binascii.hexlify(tbytes).decode('ascii') diff --git a/craton/cmd/dbsync.py b/craton/cmd/dbsync.py index a854c6a..b570400 100644 --- a/craton/cmd/dbsync.py +++ b/craton/cmd/dbsync.py @@ -23,6 +23,20 @@ class DBCommand(object): def create_schema(self): migration.create_schema() + def bootstrap_project(self): + name = 'bootstrap' + project = migration.create_bootstrap_project( + name, + db_uri=CONF.database.connection) + user = migration.create_bootstrap_user( + project.id, + name, + db_uri=CONF.database.connection) + + msg = ("\nProjectId: %s\nUsername: %s\nAPIKey: %s" + % (user.project_id, user.username, user.api_key)) + print(msg) + def add_command_parsers(subparsers): command_object = DBCommand() @@ -57,6 +71,9 @@ def add_command_parsers(subparsers): help=("Create the database schema.")) parser.set_defaults(func=command_object.create_schema) + parser = subparsers.add_parser('bootstrap') + parser.set_defaults(func=command_object.bootstrap_project) + def main(): command_opt = cfg.SubCommandOpt('command', diff --git a/craton/db/sqlalchemy/migration.py b/craton/db/sqlalchemy/migration.py index 7b436dd..4784efd 100644 --- a/craton/db/sqlalchemy/migration.py +++ b/craton/db/sqlalchemy/migration.py @@ -1,10 +1,18 @@ -import os - import alembic +import os +import uuid + from alembic import config as alembic_config import alembic.migration as alembic_migration +from sqlalchemy import create_engine +from sqlalchemy import exc +from sqlalchemy.orm import sessionmaker +import sqlalchemy.orm.exc as sa_exc from oslo_db.sqlalchemy import enginefacade +from craton.api.v1.resources import utils +from craton.db.sqlalchemy import models + def _alembic_config(): path = os.path.join(os.path.dirname(__file__), 'alembic.ini') @@ -50,3 +58,57 @@ def revision(message=None, autogenerate=False, config=None): config = config or _alembic_config() return alembic.command.revision(config, message=message, autogenerate=autogenerate) + + +def create_bootstrap_project(name, project_id=None, db_uri=None): + """Creates a new project. + :param name: Name of the new project + """ + if not project_id: + project_id = str(uuid.uuid4()) + engine = create_engine(db_uri) + Session = sessionmaker(bind=engine) + session = Session() + project = models.Project(name=name, + id=project_id) + + try: + project = session.query(models.Project).filter_by(name=name).one() + except sa_exc.NoResultFound: + session.add(project) + session.commit() + + return project + + +def create_bootstrap_user(project_id, username, db_uri=None): + """Creates a new project. + :param username: Username for new user + :param project_id: Project ID for the user + """ + engine = create_engine(db_uri) + Session = sessionmaker(bind=engine) + session = Session() + + api_key = utils.gen_api_key() + + user = models.User(project_id=project_id, + username=username, + api_key=api_key, + is_admin=True, + is_root=True) + try: + session.add(user) + session.commit() + except exc.IntegrityError as err: + if err.orig.args[0] == 1062: + # NOTE(sulo): 1062 is the normal sql duplicate error code + # also see pymysql/constants/ER.py#L65 + session.rollback() + user = session.query(models.User).filter_by(username=username) + user = user.filter_by(project_id=project_id).one() + return user + else: + raise + + return user diff --git a/doc/source/dev/install.rst b/doc/source/dev/install.rst index ec92308..ff51f7b 100755 --- a/doc/source/dev/install.rst +++ b/doc/source/dev/install.rst @@ -143,31 +143,11 @@ Run dbsync # craton-dbsync --config-file=etc/craton -api-conf.sample upgrade ------------------------ -Create Project and User ------------------------ +* Make sure to run dbsync bootstrap to create initial project and root user:: + # craton-dbsync --config-file=etc/craton-api-conf.sample bootstrap -.. note:: These goes away once the API has been setup - -* Connect to database server as root user:: - - # mysql -u root -p - -* Use the database craton:: - - # use craton; - -* Modify the projects and users as following:: - - # insert into projects (created_at, updated_at, name, id) values - (NOW(), NOW(), "osic", "717e9a216e2d44e0bc848398563bda06"); - # insert into users (created_at, updated_at, project_id, username - , api_key, is_admin) - values (NOW(), NOW(), "717e9a216e2d44e0bc848398563bda06", "demo", "demo", False); - -* Logout from the database server:: - - # exit + Note: The above command outputs user, project-id and API key to use with + python-cratonclient to interact with craton server. --------------------- Start the API Service diff --git a/tools/docker_run.sh b/tools/docker_run.sh index efef6e4..72fdaa5 100755 --- a/tools/docker_run.sh +++ b/tools/docker_run.sh @@ -19,31 +19,14 @@ mysqladmin flush-privileges ############## /craton/bin/craton-dbsync --config-file=/craton/etc/craton-api-conf.sample upgrade -PROJECT="b9f10eca66ac4c279c139d01e65f96b4" - -BOOTSTRAP_USERNAME="bootstrap" -BOOTSTRAP_TOKEN="bootstrap" - -USERNAME="demo" -TOKEN="demo" - -ADMIN_USERNAME="demo_admin" -ADMIN_TOKEN="demo_admin" - -ROOT_USERNAME="demo_root" -ROOT_TOKEN="demo_root" - -PROJECT_DISCRIMINATOR='project' #################################### # Create initial project and users # #################################### -PROJECT_VA_ID=$(mysql -u root craton -e "INSERT into variable_association (created_at, updated_at, discriminator) values (NOW(), NOW(), '$PROJECT_DISCRIMINATOR'); SELECT LAST_INSERT_ID();" | grep -Eo '[0-9]+') -mysql -u root craton -e "INSERT into projects (created_at, updated_at, name, variable_association_id, id) values (NOW(), NOW(), '$USERNAME', $PROJECT_VA_ID, '$PROJECT')" -mysql -u root craton -e "INSERT into users (created_at, updated_at, project_id, username, api_key, is_root, is_admin) values (NOW(), NOW(), '$PROJECT', '$BOOTSTRAP_USERNAME', '$BOOTSTRAP_TOKEN', True, False)" -mysql -u root craton -e "INSERT into users (created_at, updated_at, project_id, username, api_key, is_root, is_admin) values (NOW(), NOW(), '$PROJECT', '$USERNAME', '$TOKEN', False, False)" -mysql -u root craton -e "INSERT into users (created_at, updated_at, project_id, username, api_key, is_root, is_admin) values (NOW(), NOW(), '$PROJECT', '$ADMIN_USERNAME', '$ADMIN_TOKEN', False, True)" -mysql -u root craton -e "INSERT into users (created_at, updated_at, project_id, username, api_key, is_root, is_admin) values (NOW(), NOW(), '$PROJECT', '$ROOT_USERNAME', '$ROOT_TOKEN', True, True)" +# NOTE(sulo): One initial bootstrap project with root user will be created by the +# bootstrap process. Users can docker logs -f to view their api-key +# to use with the client. +/craton/bin/craton-dbsync --config-file=etc/craton-api-conf.sample bootstrap ######################### # Start the API service #