pfunk.cli

View Source
import click
import json
import os
import sys

from jinja2 import TemplateNotFound
from valley.utils import import_util
from werkzeug.serving import run_simple

from pfunk.contrib.auth.collections import Group, PermissionGroup
from pfunk.template import wsgi_template, project_template, collections_templates, key_template
from pfunk.utils.deploy import Deploy


@click.group()
def pfunk():
    pass


def load_config_file(filename):
    with open(filename, 'r') as f:
        config = json.load(f)
    return config

@pfunk.command()
@click.option('--stage_name', prompt=True, help='Application stage', default='dev')
@click.option('--email', prompt=True, help='Default From Email')
@click.option('--bucket', prompt=True, help='S3 Bucket')
@click.option('--fauna_key', prompt=True, help='Fauna Key')
@click.option('--api_type', type=click.Choice(['web', 'rest', 'none']), prompt=True, help='API Type (web, rest, none)')
@click.argument('name')
def init(name: str, api_type: str, fauna_key: str, bucket: str, email: str, stage_name: str):
    """
    Creates a PFunk project
    Args:
        name: Project name
        api_type: API Gateway type (web, rest, none)
        fauna_key: Fauna secret key
        bucket: S3 Bucket
        email: Default from Email
        stage_name: Application stage

    Returns:

    """
    if not os.path.exists(f'pfunk.json'):
        if not os.path.exists(name):
            os.mkdir(name)
        with open(f'pfunk.json', 'x') as f:
            json.dump({
                'name': name,
                'api_type': api_type,
                'stages': {stage_name: {
                    'key_module': f'{name}.{stage_name}_keys.KEYS',
                    'fauna_secret': fauna_key,
                    'bucket': bucket,
                    'default_from_email': email
                }}
            }, f, indent=4, sort_keys=True)
        open(f'{name}/__init__.py', 'x').close()
        with open(f'{name}/wsgi.py', 'x') as f:
            f.write(wsgi_template.render(PFUNK_PROJECT=f'{name}.project.project'))
        with open(f'{name}/project.py', 'x') as f:
            f.write(project_template.render())
        with open(f'{name}/collections.py', 'x') as f:
            f.write(collections_templates.render())
    else:
        click.echo('There is already a project file in this directory.')


@pfunk.command()
@click.option('--filename', default='pfunk.json', help='Fauna Key')
@click.option('--fauna_key', prompt=True, help='Fauna Key')
@click.argument('stage_name')
def add_stage(stage_name: str, fauna_key: str, filename: str):
    """
    Adds stage to the project
    Args:
        stage_name: Stage name
        fauna_key: Fauna secret key
        filename: Configuration file name (default: pfunk.json)

    Returns:

    """
    if os.path.isfile(filename):
        with open(filename, 'r') as f:
            config = json.load(f)
            config['stages'][stage_name] = {'fauna_key': fauna_key}
        with open(filename, 'w+') as f:
            f.write(json.dumps(config))
    else:
        click.echo('You have not run the init command yet.')

@pfunk.command()
@click.option('--use_reloader', default=True)
@click.option('--use_debugger', default=True)
@click.option('--config_file', default='pfunk.json')
@click.option('--wsgi', default=None)
@click.option('--port', default=3434)
@click.option('--hostname', default='localhost')
def local(hostname: str, port: int, wsgi: str, config_file: str, use_debugger: bool, use_reloader: bool):
    """
    Run local WSGI based server to test the web service.
    Args:
        hostname: Hostname to use (default: localhost)
        port: Post that will be used
        wsgi: WSGI module (dot notated path to module)
        config_file: PFunk config file (default: pfunk.json)
        use_debugger: Specifies if the server should show debugging info. (default: True)
        use_reloader: Specifies if the server should auto reload on error (default: True)

    Returns:

    """
    config = load_config_file(config_file)
    sys.path.insert(0, os.getcwd())
    wsgi_path = wsgi or f'{config.get("name")}.wsgi.app'
    app = import_util(wsgi_path)
    run_simple(hostname, port, app, use_debugger=use_debugger, use_reloader=use_reloader)


@pfunk.command()
@click.option('--config_path', help='Configuration file path', default='pfunk.json')
@click.option('--project_path', help='Project module path')
@click.argument('stage_name')
def publish(stage_name: str, project_path: str, config_path: str):
    """
    Publish GraphQL schema to Fauna
    Args:
        stage_name: Stage to publish to
        project_path: Project module path
        config_path: Path to the project config file. (default: pfunk.json)

    Returns:

    """
    config = load_config_file(config_path)
    sys.path.insert(0, os.getcwd())
    if not project_path:
        project_path = f'{config.get("name")}.project.project'
    project = import_util(project_path)
    secret = config['stages'][stage_name]['fauna_secret']
    os.environ['FAUNA_SECRET'] = secret
    project.publish()


@pfunk.command()
@click.option('--config_path', help='Configuration file path', default='pfunk.json')
@click.argument('stage_name')
def seed_keys(stage_name: str, config_path: str):
    """
    Seed encryption keys
    Args:
        stage_name: Stage that the keys should be associated with
        config_path: Configuration path

    Returns:

    """
    config = load_config_file(config_path)
    Key = import_util('pfunk.contrib.auth.collections.Key')
    keys = Key.create_keys()
    name = config.get('name')
    keys_path = f'{name}/{stage_name}_keys.py'
    with open(keys_path, 'w+') as f:
        f.write(key_template.render(keys=keys))
    return keys_path

@pfunk.command()
@click.option('--config_path', help='Configuration file path', default='pfunk.json')
@click.option('--project_path', help='Project module path')
@click.option('--username', prompt=True, help='Username')
@click.option('--password', prompt=True, hide_input=True, confirmation_prompt=True, help='Password')
@click.option('--email', prompt=True, help='Email')
@click.option('--first_name', prompt=True, help='First Name')
@click.option('--last_name', prompt=True, help='Last Name')
@click.option('--group_slug', prompt=True, help='User Group Slug', default=None)
@click.argument('stage_name')
def create_admin_user(stage_name: str, group_slug: str, last_name: str, first_name: str, email: str, password: str, username: str,
                      project_path: str, config_path: str):
    """
    Create an admin user in the project's Fauna user collection.
    Args:
        stage_name: Stage name
        group_slug: slug of the group that the user should be assigned to
        last_name: Last name of user
        first_name: First name of user
        email: Email address of user
        password: Password for the user
        username: Username for the user
        project_path: Project path
        config_path: Config path

    Returns:

    """
    config = load_config_file(config_path)
    secret = config['stages'][stage_name]['fauna_secret']
    User = import_util('pfunk.contrib.auth.collections.User')
    os.environ['FAUNA_SECRET'] = secret

    user = User.create(
        username=username,
        _credentials=password,
        first_name=first_name,
        last_name=last_name,
        email=email,
        account_status='ACTIVE'
    )

    if group_slug:
        group = Group.get_by('unique_Group_slug', group_slug)
        if not project_path:
            project_path = f'{config.get("name")}.project.project'
        sys.path.insert(0, os.getcwd())
        project = import_util(project_path)
        perm_list = []
        for i in project.collections:
            perm_list.append(PermissionGroup(collection=i, permissions=['create', 'write', 'read', 'delete']))
        user.add_permissions(group, perm_list)

@pfunk.command()
@click.option('--config_path', help='Configuration file path')
@click.argument('stage_name')
def deploy(stage_name: str, config_path: str):
    """
    Publish the GraphQL schema and API to Lambda and API Gateway (if applicable)
    Args:
        stage_name: Stage name
        config_path: Config path

    Returns:

    """
    try:
        d = Deploy(config_path=config_path)
    except FileNotFoundError:
        cp = config_path or 'pfunk.json'
        print(f'Deploy Failed: Config path ({cp}) not found ')
        return
    d.deploy(stage_name)

if __name__ == '__main__':
    pfunk()
#   def load_config_file(filename):
View Source
def load_config_file(filename):
    with open(filename, 'r') as f:
        config = json.load(f)
    return config