Развертывание сервера с Python: от А до Я.

В этом руководстве я покажу, как настроить сервер для запуска веб-приложения без каких-либо других инструментов, кроме Python.

По окончании у вас будет:

  • Узнал, какие компоненты конфигурации сервера для веб-развертывания.
  • Получил воспроизводимый шаблон Python через GitHub gists.

Фон

В те времена, когда Docker не существовало, я настраивал серверы в облачной среде с помощью кода Python. По сути, это был сценарий / проект Python, который я запускал на своем (локальном) компьютере, и он выполнял команды на (удаленном) сервере.

Я давно не использовал этот проект шахт, но наткнулся на него на прошлой неделе и понял две вещи:

  • Сегодня мне не понравилось бы его использовать. Докер намного удобнее.
  • Однако понимание этого кода дает отличное представление о том, как настраивать удаленные серверы!

Фактически, причина, по которой я написал это, заключается в том, что даже раньше я настраивал все серверы вручную: вход через ssh, установка необходимых пакетов и т. Д.

Проделав это несколько раз, я решил автоматизировать процедуру . Вот почему я построил проект, который собираюсь вам показать.

Это означает, что код выделяет детали всего, что вы должны делать на сервере, в отличие от использования Docker, где большинство деталей скрыто.

Таким образом, этот пост может быть полезен по двум причинам:

  1. Если вы не хотите или не можете, используйте Docker.
  2. Это даст вам довольно хорошее представление о том, что происходит на удаленной машине! Знания всегда имеют значение.

Обзор учебного пособия

Код организован в виде функций, каждая из которых выполняет задачу на сервере, даже если она выполняется на локальном компьютере.

Поэтому, прежде чем смотреть на код, стоит подумать о том, какие шаги вам нужно будет выполнить (вручную), если вы не читали это руководство.

Фактически, ручное развертывание на обычном сервере является обязательным для разработчика . Хотя бы раз в жизни разверните обычную машину у любого облачного провайдера (GCP, AWS, Linode, Digital Ocean, Azure и т. Д.) И выполните полноценное производственное развертывание. Хотя бы один раз!!

Вы многому научитесь, выполняя это вручную. Давайте сначала посмотрим на обзор каждого отдельного шага, а затем снова посмотрим на каждый из них вместе с кодом.

Шаг 1 – Базовая конфигурация машины

Во-первых, вы должны обновить все пакеты. Когда вы создаете новый экземпляр, он обычно запускает немного устаревшую версию операционной системы, поэтому вам необходимо обновить пакеты. Например, в Debian / Ubuntu это делается с помощью apt-get update.

После этого вам нужно будет установить все пакеты, относящиеся к вашему проекту. В коде этого руководства я установлю множество пакетов, в том числе postgresql-serverпозволяющих запускать веб-приложение, использующее PostgreSQL в качестве серверной части.

Затем, поскольку вы хотите запустить приложение Python, вам необходимо настроить Python на сервере.

Большинство операционных систем поставляются с предустановленным двоичным кодом Python по умолчанию. Однако, на мой взгляд, это не очень хорошее решение.

Причина в том, что вы, возможно, разработали свое приложение с Python3.7, но тогда на сервере установлен Python3.9. Все рухнет, и будет трудно понять, почему.

Вот почему в моем коде также есть конкретные инструкции по настройке правильной версии Python в 3 этапа.

  • Загрузите исходный код Python.
  • Скомпилируйте это.
  • Свяжите его как исполняемый файл на машине.

Наконец, как и в случае с большинством приложений Python, вам нужно создать файл virtualenv только для этого приложения.

Если у вашего приложения есть выделенный сервер, то есть это единственное работающее на нем программное обеспечение, вы можете пропустить эту часть. В любом случае мой код содержит функцию также для установки virtualenvwrapper и настройки виртуального окружения, предназначенного для этого приложения.

Это конец первого шага.

Шаг 2 – Установите ваше приложение

Чтобы сервер запускал ваше приложение, он должен быть установлен! Если ваше приложение представляет собой исполняемый файл, который можно установить, вы должны загрузить его (через curl или wget) или скопировать с локального компьютера на удаленный сервер (через scp).

Более распространенный случай с приложениями Python, по крайней мере для меня, – это когда для запуска приложения нам нужен исходный код. В этом случае вам необходимо разместить исходный код на удаленном сервере. Есть два распространенных способа сделать это:

  1. Используя git clone, чтобы вы могли взять исходный код из онлайн-репозитория git.
  2. Используя scp, чтобы вы скопировали исходный код с локального компьютера на удаленный.

В моем коде я буду использовать вариант 1, поэтому я могу показать вам хороший способ, который я нашел тогда, чтобы написать код, который переключается между ветвями и фиксирует в репозитории (например, если вы хотите выполнить откат).

После этого, если у вас есть requirements.txt файл, как в большинстве проектов Python, вам нужно будет установить все эти модули Python в файле virtualenv . Я покажу, как это сделать через минуту.

Шаг 3 – Запустите приложение!

Это самый простой этап, особенно если вы используете надежный и готовый к работе веб-сервер. Я выбрал Gunicorn, но есть много альтернатив, которые одинаково просты в использовании.

Это три шага, которые вы должны пройти на высоком уровне. Теперь я хочу вместе с вами взглянуть на них более внимательно, но есть одна недостающая деталь: как мы можем отправлять команды для выполнения на сервер, не выполняя вход вручную через ssh?

Настройка подключения

К счастью для нас, fabric эту проблему решает пакет Python . Все, что вам нужно, это такая функция, которая создает соединение с сервером:

from os import environ

from fabric import Connection

def create_conn():
    # Switch the two lines if you connect via PEM Key
    # instead of password.
    params = {
        #'key_filename': environ['SSH_KEY_PATH']}
        'password': environ['REMOTE_PASSWORD']
    }
    conn = Connection(
        host=environ['REMOTE_HOST'],
        user=environ['REMOTE_USER'],
        connect_kwargs=params,
    )
    return conn

Объект подключения, возвращаемый этой функцией, будет использоваться во всем остальном коде.

Часть 1 в деталях

В части 1 мы хотим настроить сервер на самом базовом уровне: библиотеки ОС, языки программирования и т. Д.

Если бы мы делали это вручную, мы бы использовали ssh-in, а затем запустили бы несколько ap-get install ... из оболочки.

Что ж, мы можем сделать то же самое в коде Python, благодаря объекту подключения. Вот код.

def _create_vm(conn):
    _install_packages(conn)
    _install_python(conn)
    _install_venv(conn)


def _install_packages(conn):
    conn.sudo('apt-get -y update')
    conn.sudo('apt-get -y upgrade')
    conn.sudo('apt-get install -y build-essential')
    #conn.sudo('apt-get install -y checkinstall')
    conn.sudo('apt-get install -y libreadline-gplv2-dev')
    conn.sudo('apt-get install -y libncurses-dev')
    conn.sudo('apt-get install -y libncursesw5-dev')
    conn.sudo('apt-get install -y libssl-dev')
    conn.sudo('apt-get install -y libsqlite3-dev')
    conn.sudo('apt-get install -y tk-dev')
    conn.sudo('apt-get install -y libgdbm-dev')
    conn.sudo('apt-get install -y libpq-dev')
    conn.sudo('apt-get install -y libc6-dev')
    conn.sudo('apt-get install -y libbz2-dev')
    conn.sudo('apt-get install -y zlib1g-dev')
    conn.sudo('apt-get install -y openssl')
    conn.sudo('apt-get install -y libffi-dev')
    conn.sudo('apt-get install -y python3-dev')
    conn.sudo('apt-get install -y python3-setuptools')
    conn.sudo('apt-get install -y uuid-dev')
    conn.sudo('apt-get install -y lzma-dev')
    conn.sudo('apt-get install -y wget')
    conn.sudo('apt-get install -y git')
    conn.sudo('apt-get install -y postgresql')


def _install_python(conn):
    """Install python 3.7 in the remote machine."""

    res = conn.run('python3 --version')
    if '3.7' in res.stdout.strip():
        # Python >= 3.7 already exists
        return

    conn.run('rm -rf /tmp/Python3.7 && mkdir /tmp/Python3.7')

    with conn.cd('/tmp/Python3.7'):
        conn.run('wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz')
        conn.run('tar xvf Python-3.7.0.tar.xz')

    with conn.cd ('/tmp/Python3.7/Python-3.7.0'):
        conn.run('./configure --enable-optimizations')
        conn.run('make')

    # see https://github.com/pyinvoke/invoke/issues/459
    conn.sudo('bash -c "cd /tmp/Python3.7/Python-3.7.0 && make altinstall"')


def _install_venv(conn):
    """Install virtualenv, virtualenvwrapper."""

    res = conn.run('which python3.7')
    res = res.stdout.strip()
    py_path = res

    conn.sudo('apt install -y virtualenvwrapper')

    # for a standard Debian distro
    venv_sh = '/usr/share/virtualenvwrapper/virtualenvwrapper.sh'

    conn.run('echo >> ~/.bashrc')  # new line
    conn.run(f'echo source {venv_sh} >> ~/.bashrc')
    conn.run('echo >> ~/.bashrc')  # new line
    conn.run('echo export LC_ALL=en_US.UTF-8 >> ~/.bashrc')
    conn.run('source ~/.bashrc')
    env = environ['VENV_NAME']
    with conn.prefix(f'source {venv_sh}'):
        conn.run(f'mkvirtualenv -p {py_path} {env}')

В приведенном выше коде реализованы три подэтапа, о которых я говорил ранее:

  1. Установите все библиотеки, которые нам нужны на уровне ОС (в этом случае есть также gitи postgresql-server среди многих других).
  2. Установите нужную нам версию Python, скомпилировав ее из исходного кода.
  3. Установить virtualenvwrapper. Фактически, вы можете использовать любое управление программным обеспечением virtualenv или ничего, если машина предназначена только для одного приложения Python.

Если вы внимательно посмотрите на код, вы поймете шаблон программирования за одну секунду: каждая задача выполняется путем создания объекта подключения и использования его .run()метода с аргументом той же команды, которую вы запускали вручную.

Это еще одна причина, по которой я сказал, что очень важно выполнить развертывание вручную хотя бы один раз!

Часть 2 в деталях

Во второй части я сказал, что мы хотим сделать две вещи:

  1. Получите исходный код приложения на машине.
  2. Установите все требования Python.

Посмотрим на первый. В следующей функции я просто воспользуюсь некоторыми gitкомандами, чтобы убедиться, что код извлечен из репозитория на машине.

На первый взгляд функция может показаться немного сложной, но это не так. Я написал общую функцию, которая позволяет вам проверять конкретную ветку по имени или конкретную фиксацию по ее хешу, и поэтому функция выглядит сложной.

Но вы можете упростить это и всегда делать что-то подобное git pull origin master, и это все равно будет работать!

def _pull_repo(conn, branch=None, commit=None):
    if branch and commit:
        raise ValueError('Cannot provide both branch name and commit hash')
    source = environ['GIT_DIR']
    if not branch:
        branch = environ['GIT_DEFAULT_BRANCH']
    repo = environ['REPO_URL']
    if commit:
        print('Hash provided. Resetting to that commit.')
        conn.run(
            f"cd {source} && "
            'git stash && '
            f'git reset --hard {commit} && '
            'git checkout -B tmp_branch'
        )
    else:
        if conn.run(f'test -e {source}/.git', warn=True).ok:
            print('Repo already exists.')
        else:
            print('Repo did not exist. Creating it...')
            conn.run(f'git clone {repo} {source}')
            conn.run(f'cd {source} && git remote set-url origin {repo}')
        print('Checking out the requested branch...')
        conn.run(f'cd {source} && git fetch origin && git checkout {branch} && git pull origin {branch}')
    current_hash = conn.run(f'cd {source} && git log -n 1 --format=%H', hide='both')
    current_hash = current_hash.stdout.strip()
    print(f'Checked out {current_hash}')
    return current_hash

Вы, наверное, заметили, что некоторые переменные загружаются из environ. Я доберусь до этого через минуту, но в основном причина в том, что вы можете захотеть сохранить в секрете имя репозитория и учетные данные пользователя, поэтому лучше не использовать их непосредственно в коде. В любом случае, потерпите меня минутку, и эта часть будет ясна.

Вторая часть, установить требования Python намного проще. Единственная уловка здесь заключается в том, что я использую conn.cd() и conn.prefix() активирую virtualenv перед установкой требований. В остальном основная команда точно такая же, как если бы вы выполняли ее вручную: pip install -r requirements.tx

def _install_project(conn):
    repo_path = environ['GIT_DIR']
    venv_name = environ['VENV_NAME']
    venv_sh = 'source /usr/share/virtualenvwrapper/virtualenvwrapper.sh'
    with conn.cd(repo_path):
        with conn.prefix(
            f'{venv_sh} && workon {venv_name}'
        ):
            conn.run('pip install --upgrade pip')
            conn.run('pip install -r requirements.txt')
            # If your project as a `setup.py` then
            # install project.
            #conn.run('pip install -e .')

Часть 3 в деталях

Часть 3 является самым простым, потому что я использую Gunicorn , как производство веб – сервера и запустить его нужно просто запустить простую строку: gunicorn <app_module_path>.

def _restart_web(conn):
    try:
        conn.sudo('pkill gunicorn')
    except:
        pass # may not be running at all.
    repo_path = environ['GIT_DIR']
    venv_name = environ['VENV_NAME']
    venv_sh = 'source /usr/share/virtualenvwrapper/virtualenvwrapper.sh'
    with conn.cd(repo_path):
        with conn.prefix(
            f'{venv_sh} && workon {venv_name}'
        ):
            conn.run("gunicorn app:app -b 0.0.0.0:8080 -w 3 --daemon")

Хорошо, хорошо … Я делаю еще несколько вещей в коде:

  1. Сначала я останавливаю процесс, gunicornесли он уже запущен. Это вызовет некоторое время простоя в приложении.
  2. Затем я использую некоторые аргументы конфигурации для нового gunicornпроцесса, чтобы убедиться, что он работает правильно: -bон привязывает его к нужному мне порту (8080 в этом примере); -wуказывает количество рабочих (процессов); --daemonзапускает его в фоновом режиме, так что вам не нужно держать соединение открытым.

Вот и все! Последнее, на что нам нужно обратить внимание, это как загрузить переменные среды.

Есть масса способов сделать это. В этом проекте я решил создать файл, secret.pyкоторый определяет переменные, используя, os.environ[..] = ..а затем выполняя их import secret из основного файла.

Для проверки концепций я искал только готовое к использованию приложение Flask. Я хотел использовать приложение, разработанное НЕ мной, чтобы показать вам, насколько гибкий этот код.

Я нашел образец приложения, опубликованный на GitHub компанией Digital Ocean. Я не имею к ним никакого отношения, но это выглядело хорошо для этого проекта, поэтому вы видите это в secret.py файле. Вот:

# File secret.py

from os import environ, path

### Connection
environ['REMOTE_HOST'] = '172.104.239.248'
environ['REMOTE_USER'] = '****'
environ['REMOTE_PASSWORD'] = '********+'
#
## Python venv
environ['VENV_NAME'] = 'prod-api'
#
### Git
environ['GIT_DIR'] = '~/app'
environ['GIT_DEFAULT_BRANCH'] = 'main'
environ['REPO_URL'] = 'https://github.com/digitalocean/sample-flask.git'

В основном файле, который вы можете найти в этой сущности , я просто делаю, import secret и все переменные среды загружаются. Два файла main.py и secret.py должны находиться в одном каталоге.

Последний трюк!

Код появился несколько лет назад, но есть еще один трюк, который я реализовал и которым я хочу с вами поделиться.

Мотивация в том, что иногда мне захочется запустить одну из функций, которые мы видели, и только одну. И иногда им могут потребоваться аргументы во вводе (это так _pull_repo).

Это означает, что я хочу иметь __main__ точку входа, которая запускает только функцию, которую я хочу запустить в этот конкретный момент, а также передает ей любой аргумент, поступающий из командной строки.

Вот что я придумал два года назад.

def main(tasks):
    if len(tasks) <= 1:
        print('No task name found')
        return
    i = 1
    while i < len(tasks):
        try:
            fn = getattr(sys.modules[__name__], tasks[i])
        except AttributeError:
            print(f'Cannot find task {tasks[i]}. Quit.')
            return
        params = {}
        j = i + 1
        while j < len(tasks) and '=' in tasks[j]:
            k, v = tasks[j].split('=')
            params[k] = v
            j += 1
        i = j
        print(f'Function is {fn}')
        print(f'args are {params}')
        fn(**params)


if __name__ == '__main__':
    '''
    Run it with
    $ python main <task1> <key1-task1>=<value1-task1> <key2-task1>=<value2-task2> <task2> <key1-task2>=<value1-task2>
    E.g.
    $ python main create_vm
    
    Or
    $ python main pull_repo branch=develop
    '''
    import sys
    tasks = sys.argv
    main(tasks)

Давай проверим!

Пора протестировать. Заранее хочу сказать, что результат хороший: я был очень рад, что мой код работал отлично, даже если я не использовал его какое-то время!

Вот что я сделал.

Сначала я создал небольшую машину на Линоде. Я не связан с ними, я просто хотел использовать другого провайдера, так как код приложения принадлежит Digital Ocean.

Самая маленькая машина на Linode стоит 5 долларов (у других поставщиков такая же цена), а весь тест длился менее 5 минут, поэтому я потратил всего несколько центов. Я выбрал Debian 10 в качестве ОС.

Затем я скопировал хост, имя пользователя и пароль root из консоли Linode в secret.py файл.

Затем я загрузил GitHug gist, сохранив то же имя файла azPyDeploy.py в том же каталоге что и secret.py.

И, наконец, я выполнил четыре простых команды.

python azPyDepl.py create_vm
python azPyDepl.py pull_repo
python azPyDepl.py install_project
python azPyDepl.py restart_web

Замечательно! Приложение Flask, развернутое исключительно через Python. Я должен сказать, что большое спасибо Fabric разработчикам.

Затем я отключил сервер (так что IP, который вы видите на рисунке, больше не существует), чтобы не тратить больше $$.

Заключительные комментарии и код

Я знаю, что это не похоже на развертывание производственной среды сегодня. Даже я использую Docker все на время и некоторые более продвинутые системы инфраструктуры , предоставляемой AWS.

Если по какой-то причине вы не можете использовать больше автоматизированных систем, тогда проект из этого руководства может быть вам полезен.

Но в целом главное преимущество этого кода для меня – это ценность процесса обучения . Понимание серверов и того, как все работает за кулисами, было очень важно в моей профессиональной карьере, и я надеюсь, что этот пост пролит некоторый свет и поможет вам тоже!

Вот полный код, если суть не работает.

Сообщите мне, если вы столкнулись с какой-либо проблемой при воспроизведении этого руководства.

# This script needs
# $ pip install fabric

from os import environ

from fabric import Connection

# Create a file `secret.py` in the same directory as this one
# and add in it the credentials to connect to the server and GitHub.
# Here is a template.
#############
# File secret.py

#from os import environ, path
#
### Connection
#environ['REMOTE_HOST'] = '172.104.239.248'
#environ['REMOTE_USER'] = '****'
#environ['REMOTE_PASSWORD'] = '********'
#
## Python venv
#environ['VENV_NAME'] = 'prod-api'
#
### Git
#environ['GIT_DIR'] = '~/app'
#environ['GIT_DEFAULT_BRANCH'] = 'main'
#environ['REPO_URL'] = 'https://github.com/digitalocean/sample-flask.git'
#############
import secret


def create_conn():
    # Switch the two lines if you connect via PEM Key
    # instead of password.
    params = {
        #'key_filename': environ['SSH_KEY_PATH']}
        'password': environ['REMOTE_PASSWORD']
    }
    conn = Connection(
        host=environ['REMOTE_HOST'],
        user=environ['REMOTE_USER'],
        connect_kwargs=params,
    )
    return conn


######################
# Internal Functions #
######################


def _create_vm(conn):
    _install_packages(conn)
    _install_python(conn)
    _install_venv(conn)


def _install_packages(conn):
    conn.sudo('apt-get -y update')
    conn.sudo('apt-get -y upgrade')
    conn.sudo('apt-get install -y build-essential')
    #conn.sudo('apt-get install -y checkinstall')
    conn.sudo('apt-get install -y libreadline-gplv2-dev')
    conn.sudo('apt-get install -y libncurses-dev')
    conn.sudo('apt-get install -y libncursesw5-dev')
    conn.sudo('apt-get install -y libssl-dev')
    conn.sudo('apt-get install -y libsqlite3-dev')
    conn.sudo('apt-get install -y tk-dev')
    conn.sudo('apt-get install -y libgdbm-dev')
    conn.sudo('apt-get install -y libpq-dev')
    conn.sudo('apt-get install -y libc6-dev')
    conn.sudo('apt-get install -y libbz2-dev')
    conn.sudo('apt-get install -y zlib1g-dev')
    conn.sudo('apt-get install -y openssl')
    conn.sudo('apt-get install -y libffi-dev')
    conn.sudo('apt-get install -y python3-dev')
    conn.sudo('apt-get install -y python3-setuptools')
    conn.sudo('apt-get install -y uuid-dev')
    conn.sudo('apt-get install -y lzma-dev')
    conn.sudo('apt-get install -y wget')
    conn.sudo('apt-get install -y git')
    conn.sudo('apt-get install -y postgresql')


def _install_python(conn):
    """Install python 3.7 in the remote machine."""

    res = conn.run('python3 --version')
    if '3.7' in res.stdout.strip():
        # Python >= 3.7 already exists
        return

    conn.run('rm -rf /tmp/Python3.7 && mkdir /tmp/Python3.7')

    with conn.cd('/tmp/Python3.7'):
        conn.run('wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz')
        conn.run('tar xvf Python-3.7.0.tar.xz')

    with conn.cd ('/tmp/Python3.7/Python-3.7.0'):
        conn.run('./configure --enable-optimizations')
        conn.run('make')

    # see https://github.com/pyinvoke/invoke/issues/459
    conn.sudo('bash -c "cd /tmp/Python3.7/Python-3.7.0 && make altinstall"')


def _install_venv(conn):
    """Install virtualenv, virtualenvwrapper."""

    res = conn.run('which python3.7')
    res = res.stdout.strip()
    py_path = res

    conn.sudo('apt install -y virtualenvwrapper')

    # for a standard Debian distro
    venv_sh = '/usr/share/virtualenvwrapper/virtualenvwrapper.sh'

    conn.run('echo >> ~/.bashrc')  # new line
    conn.run(f'echo source {venv_sh} >> ~/.bashrc')
    conn.run('echo >> ~/.bashrc')  # new line
    conn.run('echo export LC_ALL=en_US.UTF-8 >> ~/.bashrc')
    conn.run('source ~/.bashrc')
    env = environ['VENV_NAME']
    with conn.prefix(f'source {venv_sh}'):
        conn.run(f'mkvirtualenv -p {py_path} {env}')


def _pull_repo(conn, branch=None, commit=None):
    if branch and commit:
        raise ValueError('Cannot provide both branch name and commit hash')
    source = environ['GIT_DIR']
    if not branch:
        branch = environ['GIT_DEFAULT_BRANCH']
    repo = environ['REPO_URL']
    if commit:
        print('Hash provided. Resetting to that commit.')
        conn.run(
            f"cd {source} && "
            'git stash && '
            f'git reset --hard {commit} && '
            'git checkout -B tmp_branch'
        )
    else:
        if conn.run(f'test -e {source}/.git', warn=True).ok:
            print('Repo already exists.')
            # run("cd %s && git pull upstream %s" % (source_dir, branch))
            #conn.run(f'cd {source} && git fetch origin {branch}')
            #conn.run(f'cd {source} && git reset --hard origin/{branch}')
        else:
            print('Repo did not exist. Creating it...')
            conn.run(f'git clone {repo} {source}')
            conn.run(f'cd {source} && git remote set-url origin {repo}')
        print('Checking out the requested branch...')
        conn.run(f'cd {source} && git fetch origin && git checkout {branch} && git pull origin {branch}')
    current_hash = conn.run(f'cd {source} && git log -n 1 --format=%H', hide='both')
    current_hash = current_hash.stdout.strip()
    print(f'Checked out {current_hash}')
    return current_hash


def _install_project(conn):
    repo_path = environ['GIT_DIR']
    venv_name = environ['VENV_NAME']
    venv_sh = 'source /usr/share/virtualenvwrapper/virtualenvwrapper.sh'
    with conn.cd(repo_path):
        with conn.prefix(
            f'{venv_sh} && workon {venv_name}'
        ):
            conn.run('pip install --upgrade pip')
            conn.run('pip install -r requirements.txt')
            # If your project as a `setup.py` then
            # install project.
            #conn.run('pip install -e .')


def _restart_web(conn):
    try:
        conn.sudo('pkill gunicorn')
    except:
        pass # may not be running at all.
    repo_path = environ['GIT_DIR']
    venv_name = environ['VENV_NAME']
    venv_sh = 'source /usr/share/virtualenvwrapper/virtualenvwrapper.sh'
    with conn.cd(repo_path):
        with conn.prefix(
            f'{venv_sh} && workon {venv_name}'
        ):
            conn.run("gunicorn app:app -b 0.0.0.0:8080 -w 3 --daemon")


#####################################
# Functions used from the __main__ ##
#####################################


def create_vm(**kwargs):
    _create_vm(create_conn())


def pull_repo(**kwargs):
    conn = create_conn()
    _pull_repo(conn, **kwargs)


def install_project(**kwargs):
    _install_project(create_conn())


def restart_web(**kwargs):
    _restart_web(create_conn())


def main(tasks):
    if len(tasks) <= 1:
        print('No task name found')
        return
    i = 1
    while i < len(tasks):
        try:
            fn = getattr(sys.modules[__name__], tasks[i])
        except AttributeError:
            print(f'Cannot find task {tasks[i]}. Quit.')
            return
        params = {}
        j = i + 1
        while j < len(tasks) and '=' in tasks[j]:
            k, v = tasks[j].split('=')
            params[k] = v
            j += 1
        i = j
        print(f'Function is {fn}')
        print(f'args are {params}')
        fn(**params)


if __name__ == '__main__':
    '''
    Run it with
    >>python main <task1> <key1-task1>=<value1-task1> <key2-task1>=<value2-task2> <task2> <key1-task2>=<value1-task2>
    E.g.
    $ python main create_vm
    '''
    import sys
    tasks = sys.argv
    main(tasks)

Ответить