text: # coding: utf-8
  
  # Copyright Luna Technology 2016
  # Matthieu Riviere <mriviere@luna-technology.com>
  
  import argparse
  from copy import deepcopy
  import datetime
  import json
  import os
  import pwd
  import re
  import shlex
  import shutil
  import subprocess
  import time
  import traceback
  
  import yaml
  
  
  # Prevents things to fail on linux because WindowsError isn't defined there.
  # Trick stolen from shutil.
  try:
      WindowsError
  except NameError:
      WindowsError = None
  
  
  PYPI_MIRROR_HOST = '10.63.90.56'
  PYPI_URL = 'http://10.63.90.56:31000/luna/prod/+simple/'
  
  PYPI_SUBMIT_URL = 'http://10.63.90.56:30000/pypi/'
  HOME = os.path.expanduser('~')
  
  
  ###
  # Helpers
  ###
  def _pip_exec(pip_args, get_output=False):
      pip_cmd = ['/usr/bin/env', 'pip'] + pip_args
  
      return _exec(pip_cmd, get_output=get_output)
  
  
  def _exec(command, get_output=False, silent_errors=False):
      print("+ %s" % command)
  
      try:
          # We set universal_newlines so that we get a str (and not a byte string) in return
          s = datetime.datetime.utcnow()
          r = subprocess.check_output(command, universal_newlines=True)
          elapsed_time = (datetime.datetime.utcnow() - s).total_seconds()
      except subprocess.CalledProcessError as e:
          if not silent_errors:
              print('>> Command execution failed. Output:')
              print(e.output)
          raise
      else:
          print("-> %.2fs" % elapsed_time)
          if get_output:
              return r
          else:
              return None
  
  
  def _argparse_function(subparsers, call_function, command_name, description, arguments=None):
      f_parser = subparsers.add_parser(
          command_name,
          help=description,
          description=description,
      )
  
      if arguments is not None:
          for argument in arguments:
              f_parser.add_argument(**argument)
  
      f_parser.set_defaults(func=call_function)
  
      return f_parser
  
  
  def start_postgresql():
      print("Starting postgresql...")
      _exec([
          'sudo',
          '/usr/sbin/service',
          'postgresql',
          'start',
      ])
  
  
  def wait_for_postgresql():
      print("Waiting for postgresql server to be ready...")
  
      i = 0
      ready = False
      while i < 60:
          try:
              _exec([
                  'sudo',
                  '/usr/bin/pg_isready',
              ], silent_errors=True)
              ready = True
              break
          except subprocess.CalledProcessError:
              i += 1
              time.sleep(5)
  
      if not ready:
          raise RuntimeError('Postgresql did not start in time. Aborting...')
  
  
  def start_redis():
      print("Starting redis...")
      _exec([
          'sudo',
          '/usr/bin/service',
          'redis-server',
          'start',
      ])
  
  
  def setup_pypi_mirror():
      with open(os.path.join(HOME, '.pydistutils.cfg'), 'w') as fh:
          fh.write("""[easy_install]
  index_url = """ + PYPI_URL + """
  """)
  
      try:
          os.makedirs(os.path.join(HOME, '.pip'))
      except Exception:
          pass
  
      with open(os.path.join(HOME, '.pip', 'pip.conf'), 'w') as fh:
          fh.write("""[global]
  index-url = """ + PYPI_URL + """
  trusted-host = """ + PYPI_MIRROR_HOST + """
  """)
  
      with open(os.path.join(HOME, '.pypirc'), 'w') as fh:
          fh.write("""[distutils]
  index-servers =
      luna_internal
      devpi_luna
  
  [luna_internal]
  username:foo
  password:bar
  repository:""" + PYPI_SUBMIT_URL + """
  
  [devpi_luna]
  username:luna
  password:bHdTzu1gPYcxbtEoq3MiwdKeR
  repository:http://10.63.90.56:31000/luna/prod/
  """)
  
  
  def setup_venv(pyenv_path):
      try:
          shutil.rmtree(pyenv_path)
      except Exception:
          pass
  
      _exec([
          '/usr/bin/env',
          'virtualenv',
          '--no-site-packages',
          pyenv_path
      ])
  
  
  def setup_venv_py3(pyenv_path):
      try:
          shutil.rmtree(pyenv_path)
      except Exception:
          pass
  
      _exec([
          '/usr/bin/env',
          'pyvenv-3.4',
          pyenv_path
      ])
  
  
  def _expand_package_specs(package_specs):
      # Expand requirements file, if any, and check for ignored packages
      ignored_packages=['pkg-resources']
      try:
          idx = package_specs.index('-r')
          requirements_file = package_specs[idx+1]
          package_specs = package_specs[:idx] + package_specs[idx+2:]
          with open(requirements_file, 'r') as fh:
              requirements_data = fh.read()
          requirements_lines = requirements_data.splitlines()
          for requirement_line in requirements_lines:
              requirement_line = requirement_line.strip()  # Remove whitespace
              if requirement_line.startswith('#'):
                  continue
              if len(requirement_line) == 0:
                  continue
              requirement_line_parts = requirement_line.split('==')
              if requirement_line_parts[0] in ignored_packages:
                  continue
              else:
                  package_specs.append(requirement_line)
      except ValueError:
          pass
  
      return package_specs
  
  
  def build_wheel_cache(wheels_path, package_specs):
      package_specs = _expand_package_specs(package_specs)
  
      _pip_exec(['install', 'wheel'])
      _pip_exec(['wheel', '--wheel-dir=%s' % wheels_path, '--find-links=%s' % wheels_path] + package_specs)
      _pip_exec(['install', '--use-wheel', '--no-index', '--find-links=%s' % wheels_path] + package_specs)
  
  
  def install_from_wheel_cache(wheels_path, package_specs):
      package_specs = _expand_package_specs(package_specs)
      _pip_exec(['install', 'wheel'])
      _pip_exec(['install', '--use-wheel', '--no-index', '--find-links=%s' % wheels_path] + package_specs)
  
  
  def upgrade_pip():
      _pip_exec(['install', '--upgrade', 'pip'])
  
  
  def create_build_info(version=None):
      data = {}
  
      # Git revision
      try:
          git_revision = _exec([
              '/usr/bin/env',
              'git',
              'rev-parse',
              '--verify',
              '-q',
              'HEAD'
          ], get_output=True, silent_errors=True).strip()
      except:
          traceback.print_exc()
          git_revision = None
      data['git_revision'] = git_revision
  
      # Build number
      data['build_number'] = os.environ.get('BUILD_NUMBER')
  
      # Build version
      data['version'] = version
  
      # Pypi packages
      try:
          output = _pip_exec(['freeze'], get_output=True)
          deps = {}
          for line in output.splitlines():
              pkg, version = line.split('==')
              deps[pkg] = version
          data['pypi_deps'] = deps
      except:
          traceback.print_exc()
  
      # Dump the data to disk
      with open('build_info.txt', 'w') as fh:
          fh.write(json.dumps(data, indent=4))
  
  
  def freeze_requirements():
      output = _pip_exec(['freeze'], get_output=True)
      return output
  
  
  def _create_dir(path):
      """Ensure a directory exists. Creates it otherwise.
      This catches all sorts of "Directory already exist" exceptions,
      and lets the others pass through unchanged."""
      try:
          os.makedirs(path)
      except IOError as e:
          if e.errno == 17:
              # Already exists. Ok for us.
              pass
          else:
              raise e
      except WindowsError as e:
          if e.winerror == 183:
              # Already exists
              pass
          elif e.winerror == 5:
              # Access denied.
              # We get this error when we do os.makedirs("C:\\") (it does *not* happen for "C:")
              # No idea why, but fixing it anyway.
              # We get the error whether the drive exists or not, so we need to test that ourselves
              drive, subpath = os.path.splitdrive(path)
              if subpath == "\\" and os.path.exists(drive):
                  pass
              else:
                  raise e
          else:
              raise e
      except OSError as e:
          if e.errno == 17:
              # Already exists. Ok for us.
              pass
          else:
              raise e
  
  
  def _gen_archive(output_name, exclude_paths, version=None):
      tar_cmd = [
          '/usr/bin/env',
          'tar',
      ]
  
      _create_dir('.build')
  
      for path in exclude_paths + ['.build']:
          tar_cmd.append("--exclude=./%s" % path)
  
      if version is not None:
          output_filename = '%s.%s.tar.xz' % (output_name, version)
      else:
          output_filename = '%s.tar.xz' % output_name
  
      tar_cmd += ['-Jcf', '.build/%s' % output_filename, '.']
  
      _exec(tar_cmd)
  
  
  def _extract_django_module_from_manage_py():
      with open('manage.py', 'r') as fh:
          data = fh.read()
  
      m = re.search(r'os.environ.setdefault\("DJANGO_SETTINGS_MODULE", "(?P<module>\w+).settings"\)', data)
  
      if m is not None:
          return m.groupdict()['module']
      else:
          return None
  
  
  def _make_supervisor_environment_string(env):
      return ','.join([
          "%s=\"%s\"" % (k, env[k]) for k in env.keys()
      ])
  
  
  def load_config(source_dir=None):
      if source_dir is None:
          source_dir = os.getcwd()
      candidate_paths = ['maho.yml', 'deploy/maho.yml']
      full_candidate_paths = [os.path.join(source_dir, p) for p in candidate_paths]
  
      config_path = None
      for candidate_config_path in full_candidate_paths:
          if os.path.exists(candidate_config_path):
              config_path = candidate_config_path
              break
  
      if config_path is None:
          raise RuntimeError('Could not find maho.yml!')
  
      with open(config_path, 'r') as fh:
          config = yaml.load(fh.read())
  
      default_conf = {
          'project': 'UNNAMED_PROJECT',
          'env': {},
          'python_version': 'python3',
          'modules': [],
          'test_commands': [],
  
          'uwsgi_apps': [],
          'daemon_commands': []
      }
      default_conf.update(config)
  
      return default_conf
  
  
  def load_deployment_config():
      candidate_paths = ['deploy.yml']
  
      config_path = None
      for candidate_config_path in candidate_paths:
          if os.path.exists(candidate_config_path):
              config_path = candidate_config_path
              break
  
      if config_path is None:
          raise RuntimeError('Could not find deploy.yml!')
  
      with open(config_path, 'r') as fh:
          config = yaml.load(fh.read())
  
      default_conf = {
          'deployment_name': 'UNNAMED_DEPLOYMENT',
          'listen_port': None,
          'env': {},
          'additional_files': {}
      }
      default_conf.update(config)
  
      return default_conf
  
  
  def cache_dependencies(args):
      config = load_config()
  
      if os.path.exists('requirements.txt'):
          pyenv_path = '.pyenv'
          wheels_dir = 'py_wheels'
  
          setup_pypi_mirror()
          if config['python_version'] == 'python3':
              setup_venv_py3(pyenv_path)
          else:
              setup_venv(pyenv_path)
          os.environ['PATH'] = os.path.join(pyenv_path, 'bin') + os.pathsep + os.environ['PATH']
          upgrade_pip()
          build_wheel_cache(wheels_dir, ['-r', 'requirements.txt'])
  
  
  ###
  # The main build command
  ###
  def build(args):
      #
      # Things that we can't guess:
      # project name
      # requires postgresql
      # requires redis
      # DJANGO_MODE/DJANGO_SETTINGS_MODULE
      # python2 vs python3
  
      config = load_config()
  
      ###
      # Environment
      ###
      env = {
          'LANG': 'en_us.UTF-8',
          'LANGUAGE': 'en_US:en',
          'LC_ALL': 'en_US.UTF-8',
      }
      os.environ.update(env)
  
      # Additional environment from config
      env.update(config['env'])
      os.environ.update(env)
  
      os_env = {}
  
      ###
      # Databases
      ###
      if 'postgresql' in config['modules']:
          start_postgresql()
          os_env['POSTGRESQL_DB'] = 'pype_webapp'
          os_env['POSTGRESQL_USER'] = 'pype_webapp'
          os_env['POSTGRESQL_PASS'] = 'pype_webapp'
      if 'redis' in config['modules']:
          start_redis()
      if 'persistent_dir' in config['modules']:
          # TODO: Create a persistent directory and pass it as environment variable
          pass
  
      os.environ.update(os_env)
  
      ###
      # Virtualenv
      ###
      if os.path.exists('requirements.txt.lock'):
          requirements_file = 'requirements.txt.lock'
      elif os.path.exists('requirements.txt'):
          requirements_file = 'requirements.txt'
      elif os.path.exists('setup.py'):
          requirements_file = 'setup.py'
      else:
          requirements_file = None
  
      if requirements_file is not None:
          # Found requirements.txt -> Setting up a virtualenv and install requirements
          pyenv_path = '.pyenv'
          wheels_dir = 'py_wheels'
  
          # Configure pypi mirror
          #setup_pypi_mirror()
  
          # Create and configure the virtualenv
          if config['python_version'] == 'python3':
              setup_venv_py3(pyenv_path)
          else:
              setup_venv(pyenv_path)
          os.environ['PATH'] = os.path.join(pyenv_path, 'bin') + os.pathsep + os.environ['PATH']
          upgrade_pip()
  
          # Populate it
          if requirements_file == 'setup.py':
              _pip_exec(['install', '-q', './'])
              _pip_exec(['install', '-q', 'wheel'])
          else:
              build_wheel_cache(wheels_dir, ['-r', requirements_file])
  
          # Create requirements.txt.lock if it doesn't already exist
          if requirements_file == 'requirements.txt':
              # Write requirements.txt.lock
              frozen_reqs = freeze_requirements()
              with open('requirements.txt.lock', 'w') as fh:
                  fh.write(frozen_reqs)
  
      ###
      # Npm
      ###
      if os.path.exists('package.json'):
          # Run npm install
          _exec([
              '/usr/bin/env', 'npm', 'install', '--loglevel=warn', '--no-progress'
          ])
  
      ###
      # Django
      ###
      if os.path.exists('manage.py'):
          # Assuming a django project -> create directories and build assets
          os_env['DJANGO_MODE'] = 'Jenkins'
          main_module = _extract_django_module_from_manage_py()
          if main_module is None:
              raise RuntimeError('Could not parse manage.py. Unable to guess the main application module name')
          os_env['DJANGO_SETTINGS_MODULE'] = main_module + '.settings'
          os.environ.update(os_env)
  
          _create_dir('logs')
          _create_dir('media')
          _exec([
              '/usr/bin/env', 'python', 'manage.py', 'collectstatic', '--noinput'
          ])
          _exec([
              '/usr/bin/env', 'python', 'manage.py', 'assets', 'build'
          ])
  
      ###
      # Bower
      ###
      if os.path.exists('bower.json'):
          # Install bower_components?
          pass
  
      create_build_info(version=args.version)
  
      ###
      # Webpack
      ###
      if os.path.exists('webpack.prod.js'):
          _exec([
              './node_modules/webpack/bin/webpack.js',
              '--config',
              'webpack.prod.js'
          ])
      elif os.path.exists('webpack.config.js'):
          _exec([
              './node_modules/webpack/bin/webpack.js'
          ])
  
      ###
      # Tests
      ###
      if os.path.exists('karma.conf.js'):
          # Run javascript tests
          #
          # xvfb-run -s "-screen 0 1280x720x16" ./node_modules/karma/bin/karma start
          # => Let the user figure out the command? Maho does the xvfb-run, and the user supplies the command?
          pass
  
      if os.path.exists('pytest.ini'):
          pass
  
      for test_command in config['test_commands']:
          _exec([
              'xvfb-run',
              '-s',
              '-screen 0 1280x720x16',
              '-a',
              #'-e',
              #'/dev/stdout'
          ] + shlex.split(test_command))
  
      ###
      # Python packages
      ###
      if os.path.exists('setup.py'):
          _exec([
              '/usr/bin/env',
              'python',
              'setup.py',
              'register',
              '-r',
              'devpi_luna'
          ])
  
          _exec([
              '/usr/bin/env',
              'python',
              'setup.py',
              'sdist',
              '--formats=gztar,zip',
              'bdist_wheel',
              'upload',
              '-r',
              'devpi_luna'
          ])
  
      ###
      # Generate archive
      ###
      exclude_paths = [
          'logs',
          'media',
          '.git',
          #'node_modules',
          #'bower_components',
          '.pyenv',
      ]
      output_name = config['project']
      _gen_archive(output_name, exclude_paths, version=args.version)
  
  
  ###
  # The deploy command
  ###
  nginx_template_text = """
  worker_processes 1;
  
  error_log   {{ root_dir }}/nginx.error.log warn;
  pid         {{ root_dir }}/nginx.pid;
  
  daemon off;
  
  events {
      worker_connections  1024;
  }
  
  http {
      include /etc/nginx/mime.types;
      default_type    application/octet-stream;
  
      log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                        '$scheme $host $server_port $request_time $status '
                        '$body_bytes_sent "$http_referer" '
                        '"$http_user_agent"';
  
  
      access_log  {{ root_dir }}/nginx.access.log  main;
  
      sendfile        on;
  
      keepalive_timeout 65;
  
      client_body_temp_path {{ root_dir }}/tmp/nginx_client_temp;
      proxy_temp_path {{ root_dir }}/tmp/nginx_proxy_temp;
      fastcgi_temp_path {{ root_dir }}/tmp/nginx_fastcgi_temp;
      uwsgi_temp_path {{ root_dir }}/tmp/nginx_uwsgi_temp;
      scgi_temp_path {{ root_dir }}/tmp/nginx_scgi_temp;
  
      {{ nginx_server_block }}
  
  }
  """
  
  uwsgi_template_text = """
  [uwsgi]
  plugins-dir=/usr/local/lib/uwsgi
  plugin={{ python_plugin_version }}
  chdir={{ deploy_dir }}
  virtualenv={{ deploy_dir }}/venv
  module={{ uwsgi_callable }}
  master=True
  pidfile={{ root_dir }}/uwsgi.{{ uwsgi_appname }}.pid
  socket={{ root_dir }}/uwsgi.{{ uwsgi_appname }}.sock
  {% for key, value in uwsgi_env.items() %}
  env={{ key }}={{ value }}
  {% endfor %}
  processes=10
  vacuum=True
  max-requests=5000
  stats={{ root_dir }}/uwsgi.{{ uwsgi_appname }}.stats.sock
  logger=syslog:uwsgi.{{ deployment_name }}.{{ uwsgi_appname }}
  """
  
  supervisor_template_text = """
  [unix_http_server]
  file={{ root_dir }}/supervisor.sock                       ; path to your socket file
  
  [supervisord]
  logfile={{ root_dir }}/supervisord.log    ; supervisord log file
  logfile_maxbytes=50MB                           ; maximum size of logfile before rotation
  logfile_backups=10                              ; number of backed up logfiles
  loglevel=error                                  ; info, debug, warn, trace
  pidfile={{ root_dir }}/supervisord.pid                ; pidfile location
  nodaemon=false                                  ; run supervisord as a daemon
  minfds=1024                                     ; number of startup file descriptors
  minprocs=200                                    ; number of process descriptors
  user=root                                       ; default user
  childlogdir={{ root_dir }}/               ; where child log files will live
  
  [rpcinterface:supervisor]
  supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
  
  [supervisorctl]
  serverurl=unix://{{ root_dir }}/supervisor.sock         ; use a unix:// URL  for a unix socket
  
  {% for command in supervisor_commands %}
  [program:{{ command.name }}]
  command={{ command.command_line }}
  directory={{ deploy_dir }}
  environment={{ command.environment_string }}
  stdout_logfile = syslog
  autorestart = true
  redirect_stderr = true
  stopasgroup = true
  killasgroup = true
  {% for config_line in command.config %}{{ config_line }}
  {% endfor %}
  {% endfor %}
  """
  
  
  def gen_deployment_id():
      date = datetime.datetime.utcnow().strftime('%Y%m%d-%H%M%S')
      return date
  
  
  def deploy(args):
      from jinja2 import Template
      # Extract archive
      archive_path = args.source_archive
  
      root_dir = os.getcwd()
      deployment_id = gen_deployment_id()
      deploy_dir = os.path.join(root_dir, 'deployment-%s' % deployment_id)
  
      try:
          shutil.rmtree(deploy_dir)
      except:
          pass
  
      _create_dir(deploy_dir)
  
      _exec([
          '/usr/bin/env',
          'tar',
          '-C',
          deploy_dir,
          '-xf',
          archive_path
      ])
  
      # Load config data
      user = pwd.getpwuid(os.getuid()).pw_name
  
      app_config = load_config(source_dir=deploy_dir)
      deployment_config = load_deployment_config()
  
      config_env = {
          'user': user,
          'root_dir': root_dir,
          'deploy_dir': deploy_dir,
          'listen_port': deployment_config['listen_port'],
          'deployment_name': deployment_config['deployment_name']
      }
  
      os_env = {}
      os_env.update(deployment_config['env'])
  
      supervisor_commands = app_config['daemon_commands']
      uwsgi_apps = app_config['uwsgi_apps']
  
      new_files = []
  
      _create_dir(os.path.join(root_dir, 'tmp'))
  
      if 'persistent_dir' in app_config['modules']:
          persistent_dir = os.path.join(root_dir, 'application_data')
          _create_dir(persistent_dir)
          os_env['PERSISTENT_DIR'] = persistent_dir
  
      if 'postgresql' in app_config['modules']:
          os_env['POSTGRESQL_DB'] = deployment_config['POSTGRESQL_DB']
          os_env['POSTGRESQL_USER'] = deployment_config['POSTGRESQL_USER']
          os_env['POSTGRESQL_PASS'] = deployment_config['POSTGRESQL_PASS']
          os.environ.update(os_env)
  
      # Create virtualenv
      if os.path.exists(os.path.join(deploy_dir, 'requirements.txt.lock')):
          requirements_file = os.path.join(deploy_dir, 'requirements.txt.lock')
      else:
          requirements_file = None
  
      if requirements_file is not None:
          virtualenv_dir = os.path.join(deploy_dir, 'venv')
          wheels_dir = os.path.join(deploy_dir, 'py_wheels')
          if app_config['python_version'] == 'python3':
              setup_venv_py3(virtualenv_dir)
          else:
              setup_venv(virtualenv_dir)
          os.environ['PATH'] = os.path.join(virtualenv_dir, 'bin') + os.pathsep + os.environ['PATH']
          os_env['PATH'] = os.environ['PATH']
          upgrade_pip()
          install_from_wheel_cache(wheels_dir, ['-r', requirements_file])
  
      # Install additional files
      if 'additional_files' in deployment_config:
          oldpwd = os.getcwd()
          os.chdir(deploy_dir)
          for target_path in deployment_config['additional_files'].keys():
              shutil.copy(
                  deployment_config['additional_files'][target_path],
                  target_path
              )
  
          os.chdir(oldpwd)
  
      ###
      # Django
      ###
      if os.path.exists(os.path.join(deploy_dir, 'manage.py')):
          oldpwd = os.getcwd()
          os.chdir(deploy_dir)
          # Assuming a django project -> create directories and build assets
          main_module = _extract_django_module_from_manage_py()
          if main_module is None:
              raise RuntimeError('Could not parse manage.py. Unable to guess the main application module name')
  
          _create_dir('logs')
          _create_dir('tmp')
  
          shutil.copy(deployment_config['secrets_path'], os.path.join(main_module, 'settings', 'secrets.json'))
  
          # Migrate
          _exec([
              '/usr/bin/env',
              'python',
              'manage.py',
              'migrate',
              '--noinput'
          ])
  
          uwsgi_apps.append({
              'name': 'django',
              'callable': main_module + '.wsgi:application',
              'env': {
                  'DJANGO_SETTING_MODULE': main_module + '.settings'
              }
          })
  
          os.chdir(oldpwd)
  
      # Generate nginx config
      nginx_app_template_path = os.path.join(deploy_dir, 'deploy/nginx.conf')
      if os.path.exists(nginx_app_template_path):
          with open(nginx_app_template_path, 'r') as fh:
              nginx_app_template_text = fh.read()
  
          nginx_app_template = Template(nginx_app_template_text)
          nginx_app_template_rendered = nginx_app_template.render(config_env)
  
          nginx_env = deepcopy(config_env)
          nginx_env['nginx_server_block'] = nginx_app_template_rendered
          nginx_template = Template(nginx_template_text)
          nginx_template_rendered = nginx_template.render(nginx_env)
  
          nginx_rendered_path = os.path.join(root_dir, 'nginx.conf')
          new_files.append({
              'path': nginx_rendered_path,
              'content': nginx_template_rendered
          })
  
          supervisor_commands.append({
              'name': 'nginx',
              'command_line': '/usr/sbin/nginx -c "%s"' % nginx_rendered_path
          })
  
      # Generate uwsgi configs
      if len(uwsgi_apps) > 0:
          for uwsgi_app in uwsgi_apps:
              uwsgi_env = deepcopy(config_env)
              if app_config['python_version'] == 'python2':
                  uwsgi_env['python_plugin_version'] = 'python27'
              else:
                  uwsgi_env['python_plugin_version'] = 'python34'
              uwsgi_env['uwsgi_callable'] = uwsgi_app['callable']
              uwsgi_env['uwsgi_appname'] = uwsgi_app['name']
              uwsgi_env['uwsgi_env'] = uwsgi_app.get('env', {})
              uwsgi_template = Template(uwsgi_template_text)
              uwsgi_template_rendered = uwsgi_template.render(uwsgi_env)
  
              uwsgi_rendered_path = os.path.join(root_dir, 'uwsgi.%s.conf' % uwsgi_app['name'])
              new_files.append({
                  'path': uwsgi_rendered_path,
                  'content': uwsgi_template_rendered
              })
  
              supervisor_commands.append({
                  'name': 'uwsgi.%s' % uwsgi_app['name'],
                  'command_line': 'uwsgi --ini "%s"' % uwsgi_rendered_path
              })
  
      # Build the supervisor config file
      if len(supervisor_commands) == 0:
          # Nothing to run. This is weird
          raise RuntimeError('No supervisor commands defined to be ran.')
  
      supervisor_env = deepcopy(config_env)
      supervisor_env['supervisor_commands'] = supervisor_commands
      for cmd in supervisor_env['supervisor_commands']:
          e = os_env.copy()
          e.update(cmd.get('env', {}))
          cmd['environment_string'] = _make_supervisor_environment_string(e)
          cmd['config'] = cmd.get('config', [])
      supervisor_template = Template(supervisor_template_text)
      supervisor_template_rendered = supervisor_template.render(supervisor_env)
  
      supervisor_rendered_path = os.path.join(root_dir, 'supervisord.conf')
      new_files.append({
          'path': supervisor_rendered_path,
          'content': supervisor_template_rendered
      })
  
      # Write the new files
      for new_file in new_files:
          with open(new_file['path'], 'w') as fh:
              fh.write(new_file['content'])
  
      # Restart supervisor
      if os.path.exists(os.path.join(root_dir, 'supervisor.sock')):
          _exec(['/usr/bin/env', 'supervisorctl', '-c', supervisor_rendered_path, 'reread'])
          _exec(['/usr/bin/env', 'supervisorctl', '-c', supervisor_rendered_path, 'update'])
          _exec(['/usr/bin/env', 'supervisorctl', '-c', supervisor_rendered_path, 'restart', 'all'])
      else:
          _exec(['/usr/bin/env', 'supervisord', '-c', supervisor_rendered_path])
  
  
  ###
  # Arguments parser
  ###
  def main(args=None):
      parser = argparse.ArgumentParser('Maho')
      subparsers = parser.add_subparsers(metavar='command')
  
  
      f_parser = _argparse_function(
          subparsers,
          build,
          'build',
          'Builds the project in the current directory',
      )
      f_parser.add_argument('-v', '--build-version', dest='version', metavar='version')
  
      _argparse_function(
          subparsers,
          cache_dependencies,
          'cache-dependencies',
          'Fetches all dependencies of the project'
      )
  
      f_parser = _argparse_function(
          subparsers,
          deploy,
          'deploy',
          'Deploys a project from an archive',
      )
      f_parser.add_argument(dest='source_archive', metavar='source-archive')
  
      #
      # Main dispatch function
      #
      parsed_args = parser.parse_args(args=args)
      parsed_args.func(parsed_args)
  
  
  if __name__ == '__main__':
      main()
  