def get_organization_list():
    if not os.path.exists(organization_path):
        return []

    return [organization
            for organization in os.listdir(organization_path)
            if organization[0] != '.'] def baseboxes():
    """
    Retrieve a list of base boxes from the remote S3 bucket, sorted by infra

    :return: dict[str,str]
    """
    if not basebox_bucket():
        return {}

    # retrieve our bucket
    bucket = S3(basebox_bucket())

    boxes = {}
    current_infra = None
    for obj in bucket.list_bucket():
        # ignore objects that are not boxes
        if not obj['Key'].endswith('.box'):
            continue

        infra, box = obj['Key'].split('/')

        if current_infra != infra:
            current_infra = infra
            boxes[current_infra] = []

        boxes[current_infra].append(box)

    return boxes def sync(box, direction):
    if not box.project.rsync_enabled():
        return None

    if direction == 'down':
        click.secho('Syncing files down from the box...',
                    fg='cyan', bold=True)
        if box.rsync_down():
            click.secho('Sync down done!', fg='green', bold=True)
        else:
            click.secho('Sync down failed!', fg='red', bold=True)
            return False
    elif direction == 'up':
        click.secho('Syncing files up to the box...',
                    fg='cyan', bold=True)
        if box.rsync_up():
            click.secho('Sync up done!', fg='green', bold=True)
        else:
            click.secho('Sync up failed!', fg='red', bold=True)
            return False
    else:
        click.secho('error: invalid direction %s' % direction,
                    fg='red', bold=True)
        return False

    return True class Command(click.Command):
    # This is inherited by the Context to switch between pure POSIX and
    # argparse-like argument parsing. Setting it to false will push any
    # unknown flag to the argument located where the flag was declared
    allow_interspersed_args = config.get('config', 'posix', default=False)

    def __init__(self, *args, **kwargs):
        cs = dict(help_option_names=['-h', '--help'])
        super(Command, self).__init__(context_settings=cs,
                                      *args, **kwargs) def fatal(text, code=1, **kwargs):
    error(text, **kwargs)
    sys.exit(code) def warning(text, **kwargs):
    click.secho(text, fg='yellow', err=True, **kwargs) def error(text, **kwargs):
    click.secho(text, fg='red', err=True, **kwargs) def get_input(prompt):
    from prompt_toolkit.contrib.shortcuts import get_input as pt_get_input

    return pt_get_input(prompt) def start_box(box, provision_with=None):
    # if the vm is suspended, just resume it
    res = 0

    provision = provision_with or []

    if 'shell' not in provision and box.status() != 'not created':
        provision.append('shell')

    extra_args = []
    if provision:
        extra_args = ['--provision-with', ','.join(provision)]

    manual_provision = False
    if box.status() == 'saved':
        manual_provision = True
        click.echo('Resuming box %s' % box.name())

    if not box.is_running():
        try:
            extra_args_copy = extra_args[:]
            if '--provision-with' in extra_args_copy:
                extra_args_copy.insert(0, '--provision')
            res = box.up(*extra_args_copy)
        except (ExposeTimeout, ExposeConnectionError):
            warning('warning: expose is not available at the moment')
    else:
        hist = box.history()
        if not hist or (
            'failed_at' in hist[-1] and
                hist[-1]['failed_at']
        ) or hist[-1]['stats'][box.name()]['unreachable'] > 0:
            # run provisioning if last one failed
            res = box.vagrant('provision')

        box.expose()  # just in case

    if manual_provision:
        box.vagrant('provision', *extra_args)

    if res == 0 or res is True:
        timestamp(render_cli('provision-success', box=box))
    else:
        timestamp(render_cli('provision-failure'))

    # refresh cache
    list_vms(clear_cache_only=True)

    return res def move_shell_to(path):
    """
    If the process is called through the function set in scripts/env.sh,
    will move the user to the given path on successful exit

    :param path: str
    """
    if 'AERIS_CD_TMP_FILE' in os.environ:
        with open(os.environ['AERIS_CD_TMP_FILE'], 'w') as f:
            f.write(path) class AerisCompletableList(Completer):
    """
    Used to implement an autocompleter that allows selecting
    and un-selecting items, access selected items in self.selected
    """

    def __init__(self, terms, meta_dict=None, allow_selected=False):
        self.terms = list(terms)
        self.meta_dict = meta_dict or {}
        self.selected = []
        self.terms.sort()
        self.allow_selected = allow_selected
        # keep a pristine copy
        self.items = self.terms[:]

    def select(self, term):
        if term not in self.selected:
            self.selected.append(term)
            self.terms.remove(term)

    def unselect(self, term):
        if term in self.selected:
            self.terms.append(term)
            self.selected.remove(term)
            self.terms.sort()

    def get_completions(self, document, complete_event):
        word_before_cursor = document.get_word_before_cursor(True)
        word_len = len(word_before_cursor)

        search_list = self.allow_selected and self.items or self.terms

        if word_before_cursor.startswith('-'):
            word_before_cursor = word_before_cursor[1:]
            word_len -= 1
            search_list = self.selected

        for a in search_list:
            if a.startswith(word_before_cursor):
                display_meta = self.meta_dict.get(a, '')
                yield Completion(a, -word_len,
                                 display_meta=display_meta) class AerisPrompt(CommandLineInterface):
    """
    Generate an auto-complete prompt using prompt_toolkit
    It is written in pure python and does not depend on readline

    Usage:

    prompt = AerisPrompt('prompt> ')
    input = prompt.get_input()
    """

    def __init__(self, message, completer=None):
        self._completer = completer

        # Create history instance.
        history = History()

        # Load all key bindings.
        manager = KeyBindingManager()

        # Our prompt layout
        layout = create_default_layout(
            message=message,
            reserve_space_for_menu=(completer is not None)
        )

        # Create CLI
        CommandLineInterface.__init__(
            self,
            layout=layout,
            buffer=Buffer(
                history=history,
                completer=completer,
                ),
            key_bindings_registry=manager.registry
        )

    def get_input(self):
        document = self.read_input(on_abort=AbortAction.RETURN_NONE,
                                   on_exit=AbortAction.RETURN_NONE)

        if document:
            return document.text

    def select(self, term):
        self._completer.select(term)

    def unselect(self, term):
        self._completer.unselect(term) def module_path():
    def __init__(self):
    def load(self):
    def has(self, section, option):
    def get(self, section, option, **kwargs):
    def set(self, section, option, value):
    def unset(self, section, option):
    def save(self):
    def dump(self):
    def complete(self):
def inventory_path():
def config_dir():
def data_dir():
def config_ini_path():
def projects_path():
def basebox_bucket():
def default_organization():
def expose_username():
def expose_url():
def has_github_integration():
def verbosity(val=None):
class Config(object): class Project(object):
    """
    Represents an AerisCloud project

    :param folder: str The folder where the project is stored
    """

    def __init__(self, folder):
        self._folder = folder
        self._config_file = os.path.join(self._folder, '.aeriscloud.yml')
        self._initialized = False
        self._config = None
        self._boxes = BoxList()
        self._logger = get_logger(self.folder(True), logger)

        if os.path.isfile(self._config_file):
            self._initialized = True

    def _load_config(self):
        if not os.path.isfile(self._config_file):
            self._config = {}
            return

        import yaml

        with open(self._config_file) as fd:
            self._config = yaml.load(fd) or {}

    def _load_infra(self):
        if self._boxes:
            return
        # We store boxes in a list as python dicts are not ordered
        if 'boxes' in self.config():
            self._boxes = BoxList([Box(self, box)
                                  for box in self.config()['boxes']])

    def initialized(self):
        return self._initialized

    def name(self):
        """
        Return the name of the project
        :return: str
        """
        if not self._config or 'project_name' not in self.config():
            return self.folder(True)
        return self.config()['project_name']

    def set_name(self, name):
        self.config()['project_name'] = name
        self._logger = get_logger(self.name(), logger)

    def get_production_url(self):
        if 'production_url' not in self.config():
            return None
        return self.config()['production_url']

    def id(self):
        if 'id' not in self.config():
            return None
        return self.config()['id']

    def set_id(self, project_id):
        self.config()['id'] = project_id

    def organization(self):
        if 'organization' not in self.config():
            return None
        return self.config()['organization']

    def set_organization(self, organization):
        self.config()['organization'] = organization

    def services(self):
        if 'services' not in self.config():
            return []
        return self.config()['services']

    def set_services(self, services):
        self.config()['services'] = services

    def add_box(self, basebox, basebox_url=None):
        if 'boxes' not in self.config():
            self.config()['boxes'] = []

        if 'basebox' not in basebox:
            raise ValueError('Invalid basebox dict provided')

        basebox.setdefault('name', slugify(basebox['basebox']))
        basebox.setdefault('cpu', BASEBOX_DEFAULT_CPU)
        basebox.setdefault('ram', BASEBOX_DEFAULT_RAM)

        if basebox in self.config()['boxes']:
            return

        if basebox_url:
            self.config()['basebox_url'] = basebox_url

        self.config()['boxes'].append(basebox)
        self._load_infra()

    def folder(self, base=False):
        if base:
            return os.path.basename(self._folder)
        return self._folder

    def config(self):
        if self._config is None:
            self._load_config()
            self._load_infra()
        return self._config

    def config_file(self):
        return self._config_file

    def boxes(self):
        """
        Return the list of boxes available for this project
        :return: BoxList[Box]
        """
        self._load_infra()
        return self._boxes

    def rsync_enabled(self):
        return 'use_rsync' in self.config() and self.config()['use_rsync']

    def box(self, name=''):
        """
        Retrieve a box by name
        :param name: str
        :return: Box|None
        """
        self._load_infra()
        if not self._boxes:
            return None
        if not name:
            return self.boxes()[0]
        for box in self._boxes:
            if box.name() == name:
                return box
        return None

    def save(self):
        self._logger.info('writing .aeriscloud.yml')

        env = jinja_env()
        env.filters['yaml'] = _yaml_filter
        config = env.get_template('aeriscloud.yml.j2') \
            .render(config=_ProjectConfig(self.config()))

        with open(self._config_file, 'w') as fd:
            fd.write(config)
            self._initialized = True

    def vagrant(self, *args, **kwargs):
        self._logger.info('running: vagrant %s', ' '.join(args))
        return run(self, *args, **kwargs)

    def vagrant_dir(self):
        return os.path.join(self.folder(), '.vagrant')

    def vagrant_working_dir(self):
        return aeriscloud_path

    def endpoints(self):
        if 'browse' in self.config():
            return self.config()['browse']
        else:
            return {}

    def __repr__(self):
        return '<Project %s [%s]>' % (self.name(), self._folder) def services(organization, env='production'):
    service_list = {}

    if not organization:
        return service_list

    service_file = os.path.join(organization_path,
                                organization,
                                'env_%s.yml' % env)

    with open(service_file) as f:
        service = None
        for line in f.readlines():
            if service:
                service_name = line[line.find(':') + 1:].strip()
                service_list[service_name] = service
                service = None
            elif line.startswith('# service:'):
                service = {
                    'description': line[line.find(':') + 1:].strip(),
                    'default': False
                }
            elif line.startswith('# default service:'):
                service = {
                    'description': line[line.find(':') + 1:].strip(),
                    'default': True
                }

    return service_list
import click
import json
import os
import re
import requests
import sys
from sh import git, ErrorReturnCode
from aeriscloud.ansible import get_organization_list
from aeriscloud.basebox import baseboxes
from aeriscloud.cli.aeris.sync import sync
from aeriscloud.cli.helpers import Command, fatal, warning, \
    error, get_input, start_box, move_shell_to
from aeriscloud.cli.prompt import AerisCompletableList, AerisPrompt
from aeriscloud.config import aeriscloud_path, config, basebox_bucket, \
    projects_path
from aeriscloud.project import Project
from aeriscloud.services import services as list_services
]
default_basebox = 'chef/centos-7.0'


def _title(title):
    click.secho('\n%s\n%s' % (title, '-' * len(title)), fg='cyan')


def _get_git_root():
    """
    Retrieve the git directory, or prompt to create one if not found
    """
    git_root = None

    try:
        git_root = str(git('rev-parse', '--show-toplevel')).strip()
    except ErrorReturnCode as e:
        if e.exit_code != 128:
            fatal(e.message, e.exit_code)

    if not git_root:
        warning('You must be in a git repository directory to '
                'initialize a new project.')

        if not click.confirm('Do you want to create a new git '
                             'repository here?', default=True):
            fatal('Please run %s' % style('git init', bold=True))

        try:
            vgit('init')
            git_root = os.getcwd()
        except ErrorReturnCode as e:
            fatal('An error occurred when trying to initialize a '
                  'new repo.', e.exit_code)

    if git_root == aeriscloud_path:
        fatal('You cannot init AerisCloud from the AerisCloud directory!')

    return git_root


def _ask_general_info(git_root):
    _title('General')

    # Take the basename of the git root as default name for the application
    default_name = os.path.basename(git_root) \
        .lower()

    app_name = None
    app_id = None

    click.secho('\nSelect your %s (only lowercase letters, numbers and '
                'dash are accepted).' % style('application name', bold=True))
    while True:
        app_name = get_input('[default: %s] > ' % default_name)
        if not app_name:
            app_name = default_name
        if re.match("^[a-z0-9-]+$", app_name):
            break
        else:
            click.echo('Only lowercase letters and numbers are accepted.')

    organization_list = AerisCompletableList(get_organization_list())

    click.secho('\nWhich organization contains the playbook you want to '
                'use to provision the boxes of this project?')
    organization = AerisPrompt('> ', completer=organization_list).get_input()

    if not organization:
        organization = config.get('config', 'default_organization',
                                  default=None)

    click.echo('\nProvide your %s.\n'
               'If you don\'t have one, ask your system operators!' %
               style('application ID', bold=True))
    while True:
        app_id = get_input('> ')
        if len(app_id) > 0:
            try:
                app_id = int(app_id)
                if app_id > 0:
                    break
                warning('Please enter a valid ID.')
            except ValueError:
                warning('Please enter a valid ID.')

    return app_name, organization, app_id


def _show_service_list(service_list, enabled_services):
    """
    Show the service list, coloring enabled services, return the list of non
    default services

    TODO: find a better command name, as it doesn't just show services
    """
    if not enabled_services:
        enabled_services = []

    service_complete = {}
    for service, service_info in sorted(service_list.iteritems()):
        if not service_info['default']:
            service_complete[service] = service_info['description']
            if service in enabled_services:
                click.secho('* %s - %s' % (
                    style(service, bold=True, fg='green'),
                    service_info['description']
                ))
            else:
                click.secho('* %s - %s' % (
                    style(service, bold=True),
                    service_info['description']
                ))
    return service_complete


def _ask_services(organization, enabled_services=None):
    """
    Ask the user which services to enable/disable, returns a list of services
    """

    service_list = list_services(organization)