Vault+Pydantic: продолжение саги, локальная разработка

от автора

Предыстория

Предыдущая статья

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

В Sitri данная проблема решается достаточно просто — с помощью локального режима для ваших классов настроек, то есть вам не придётся даже переписывать ничего или дублировать код, да и структура json файла для локального режима будет почти полностью повторять структуру секретов.

Итак, ну а теперь, давайте добавим в наш проектик буквально пару строк кода + я покажу, как со всем этим можно работать, если ваш проект локально запускается в docker-compose.

Готовим код

Для начала, давайте договоримся, что local_mode действует тогда, когда ENV = "local" 🙂 Далее, я предлагаю немного отредактировать наш provider_config.py и создать там класс BaseConfig от которого мы будем наследовать в наших настройках Config классы. Мы сделаем это для того, чтобы не дублировать код, то есть в самих классах настроек будет только то, что специфично для них.

import hvac from sitri.providers.contrib.system import SystemConfigProvider from sitri.providers.contrib.vault import VaultKVConfigProvider from sitri.settings.contrib.vault import VaultKVSettings  configurator = SystemConfigProvider(prefix="superapp") ENV = configurator.get("env")  is_local_mode = ENV == "local" local_mode_file_path = configurator.get("local_mode_file_path")  def vault_client_factory() -> hvac.Client:     client = hvac.Client(url=configurator.get("vault_api"))      client.auth_approle(         role_id=configurator.get("role_id"),         secret_id=configurator.get("secret_id"),     )      return client  provider = VaultKVConfigProvider(     vault_connector=vault_client_factory,     mount_point=f"{configurator.get('app_name')}/{ENV}", )  class BaseConfig(VaultKVSettings.VaultKVSettingsConfig):     provider = provider     local_mode = is_local_mode     local_provider_args = {"json_path": local_mode_file_path}

Немного про local_provider_args в этом поле мы указываем аргументы для создания экземпляра JsonConfigProvider, они будут провалидированы и данный словарь должен соотвествовать схеме, поэтому не волнуйтесь — это не какой-то грязный приём. Однако, если вы хотите создать экземпляр локального провайдера сами, то вы просто положить его в опциональное поле local_provider.

Теперь, мы можем спокойно наследовать конфиг классы от базового. Например, класс настроек для соединения с Kafka будет выглядеть так:

from typing import Any, Dict  from pydantic import Field from sitri.settings.contrib.vault import VaultKVSettings  from superapp.config.provider_config import BaseConfig, configurator  class KafkaSettings(VaultKVSettings):     mechanism: str = Field(..., vault_secret_key="auth_mechanism")     brokers: str = Field(...)     auth_data: Dict[str, Any] = Field(...)      class Config(BaseConfig):         default_secret_path = "kafka"         default_mount_point = f"{configurator.get('app_name')}/common"          local_mode_path_prefix = "kafka"

Как видите требуемые изменения минимальны. local_mode_path_prefix мы указываем, чтобы в нашем json файле сохранялась структура общего конфига. А теперь, давайте напишем этот самый json файл для локальной конфигурации:

{     "db":     {         "host": "testhost",         "password": "testpassword",         "port": 1234,         "user": "testuser"     },     "faust":     {         "agents":         {             "X":             {                 "concurrency": 2,                 "partitions": 5             }         },         "app_name": "superapp-workers",         "default_concurrency": 5,         "default_partitions_count": 10     },     "kafka":     {         "auth_data":         {             "password": "testpassword",             "username": "testuser"         },         "brokers": "kafka://test",         "mechanism": "SASL_PLAINTEXT"     } }

… Ну, или просто скопипастим его из конца прошлой статьи. Как видите, тут всё весьма просто. Для наших дальнейших изысканий, переименуем main.py в корне проекта на __main__.py, чтобы можно было запустить пакет командой из docker-compose.

Запихиваем приложение в контейнер и наслаждаемся сборкой

Первое, что мы должны сделать — это написать небольшой Dockerfile:

FROM python:3.8.3-buster  ENV PYTHONUNBUFFERED=1 \     POETRY_VIRTUALENVS_CREATE=false \     POETRY_VIRTUALENVS_IN_PROJECT=false \     POETRY_NO_INTERACTION=1  RUN pip install poetry  RUN mkdir /superapp/ WORKDIR /superapp/  COPY ./pyproject.toml ./poetry.lock /superapp/ RUN poetry install --no-ansi  WORKDIR /

Здесь мы просто устанавливаем зависимости и всё, так как он для локальной разработки, то код проекта мы не копируем.

Далее, нам понадобиться env-файл с необходимыми для local-mode переменными:

SUPERAPP_ENV=local SUPERAPP_LOCAL_MODE_FILE_PATH=/config.json SUPERAPP_APP_NAME=superapp

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

Ну и последнее, что мы должны написать — сам docker-compose.yml файл:

# docker-compose config for local development version: '3' services:   superapp:     command: python3 -m superapp     restart: always     build:       context: ./       dockerfile: Dockerfile     volumes:       - ./superapp:/superapp       - ./config.json:/config.json     env_file:       - .env.local

Как видите, тут тоже всё просто. Наш json-файл мы кладём в корень, как и указали выше в переменной окружения для контейнера.

Ну а теперь, запуск:

docker-compose up

Creating article_sitri_vault_pydantic_superapp_1 ... done Attaching to article_sitri_vault_pydantic_superapp_1  superapp_1  | db=DBSettings(user='testuser', password='testpassword', host='testhost', port=1234) faust=FaustSettings(app_name='superapp-workers', default_partitions_count=10, default_concurrency=5, agents={'X': AgentConfig(partitions=5, concurrency=2)}) kafka=KafkaSettings(mechanism='SASL_PLAINTEXT', brokers='kafka://test', auth_data={'password': 'testpassword', 'username': 'testuser'})  superapp_1  | {'db': {'user': 'testuser', 'password': 'testpassword', 'host': 'testhost', 'port': 1234}, 'faust': {'app_name': 'superapp-workers', 'default_partitions_count': 10, 'default_concurrency': 5, 'agents': {'X': {'partitions': 5, 'concurrency': 2}}}, 'kafka': {'mechanism': 'SASL_PLAINTEXT', 'brokers': 'kafka://test', 'auth_data': {'password': 'testpassword', 'username': 'testuser'}}}

Как видите, всё успешно запустилось и информация из нашего json-фала успешно прошла все проверки и стала настройками для локальной версии приложения, юхху!

Код этой статьи я положил в отдельную ветку, так что можете зайти, склонить и протестировать всё самолично 🙂
branch

ссылка на оригинал статьи https://habr.com/ru/post/533274/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *