Создание нового функционала для разработчиков

При разработке нового функционала со стороны администраторов тонких клиентов необходимо:

  1. Создать state/script с необходимой функциональностью;
  2. Протестировать работу state/script на стенде;
  3. Поместить файлы аналогично стенду в tkcontrol-configure/tkcontrol_configure/config/saltmaster;
  4. Обновить конфигурацию рабочего сервера.

При разработке нового функционала со стороны фронтендера web-приложения:

  1. Создать форму;
  2. Отправить POST запрос на /hosts или /groups.

Пример цикла разработки нового функционала

Создадим возможность отправлять пользователям уведомление с просьбой выключить компьютер.

  1. Зайдем на тестовый стенд salt-master;

  2. Добавим скрипт /srv/salt/states/files/notify-shutdown.sh, который будет выводить уведомление:

    #!/bin/bash
    
    # Get xauthority location
    
    AUTH=$(ps aux | grep "\-auth " | head -n 1)
    AUTH=${AUTH/*\-auth /}
    AUTH=${AUTH/ */}
    
    # Declare zenity variables
    
    declare -x XAUTHORITY=$AUTH
    declare -x LC_ALL="ru_RU.utf8"
    declare -x DISPLAY=":0"
    
    title="Оповещение"
    text="Уважаемый пользователь, администратор просит выключить ваш компьютер!"
    
    zenity \
        --info \
        --title "$title" \
        --text "$text" \
        --width 500
    
  3. Проверим работу скрипта:

    sudo salt 'skupov-stagin-tkcontrol-client' cmd.script salt://files/notify-shutdown.sh
    
  4. Добавим на фронтенд кнопку с вызовом этого скрипта:

    <v-btn @click="sendShutdownNotify(hostname)">
        Send shutdown notify
    </v-btn>
    
    ...
    
    async sendShutdownNotify(hostname) {
        await axios.post(`/api/${hostname}`, {
            salt_client: 'local_async',
            fun: 'cmd.script',
            arg: ['salt://files/notify-shutdown.sh'],
        })
    }
    
  5. Если все выполнилось удачно, переместим скрипт из salt-master /srv/salt/states/files/notify-shutdown.sh в tkcontrol-configure/tkcontrol_configure/config/saltmaster;

  6. После сборки пакетов при конфигурировании salt-master наш скрипт установится:

    sudo tkcontrol-configure salt-master
    

Пример цикла получения и сохранения данных в БД

  1. Добавляем схему новой коллекции в tkcontrol-modules/tkcontrol_modules/eve/schemes. Создаем файл hosts_statuses.py со следующим кодом (обо всех вариациях схемы можно узнать на https://docs.python-eve.org/en/stable/config.html):

    schema = {
        '_id': {
            'type': 'string',
            'unique': True,
            'required': True,
        },
        'status': {
            'type': 'string',
            'required': True,
            'allowed': ['up', 'down'],
        },
    }
    
    hosts_statuses = {
        # По умолчанию используется паттерн для ObjectId, заменяем его любые непробельные символы
        'item_url': 'regex("[\S]+")',
        'schema': schema,
    }
    
  2. В schemes/__init__ импортируем схему коллекции:

    from .hosts_statuses import hosts_statuses
    
  3. В dbadapter/tkcontrol_dbadapter/config/mongo_settings.py и в backend/tkcontrol_backend/config/mongo_settings.py зарегистрируем схему:

    from tkcontrol_modules.eve.schemes import hosts_statuses
    
    ...
    
    DOMAIN = {
    ...
    'hosts_statuses': hosts_statuses
    }
    

На данном этапе у нас есть возможность читать данные из backend и читать/создавать/изменять данные из dbadapter.

  1. Добавим вызов команды в dbsync_service services/tkcontrol_services/services/dbsync_services.py:

    # Отправляем команду раз в 600 секунд.
    if self._timer.time_to_refresh(600):
        self._salt_request(self._ping_request, request_name='ping')
    
  2. Создадим фильтр services/tkcontrol_services/senders/filters/ping_filter.py для извлечения данных из salt events bus:

    from .filter import Filter
    
    
    class PingFilter(Filter):
        def __init__(self):
            super().__init__()
    
            # Команда запускается от runnser_async
            self._allowed_types = ['run']
    
            # Имя команды, которую мы выполняем
            self._allowed_functions = ['runner.manage.status']
    
            # Нам важно только возращаемое значение
            self._allowed_statuses = ['ret']
    
            # Мета информация для обновления/создания документов
            self._db_tag = {
                'method': 'patch', # Изначально пытаемся обновить данные
                'collection': 'hosts_statuses', # Имя коллекции
                'force': True, # Если запись не получается обновить (ее не существует), тогда мы создаем запись
            }
    
        # Перед вызовом этой функции Sender устанавливает значение обрабатываемого сообщения в self._ev_msg
        def process(self):
            # Извлекаем данные из salt event
            up_hostnames = self._ev_msg.data['return']['up']
            down_hostnames = self._ev_msg.data['return']['down']
    
    
            # Формируем полезные данные для выгрузки в бд. Best practice вывести логику по обработке в отдельный сериалайзер, для примера смотрите key_filter
            payloads = []
            for hostname in up_hostnames:
                payloads.append({
                    'data': {'_id': hostname, 'status': 'up'},
                    'lookup': hostname,
                })
    
            for hostname in up_hostnames:
                payloads.append({
                    'data': {'_id': hostname, 'status': 'down'},
                    'lookup': hostname,
                })
    
            return self._db_tag, payloads
    
  3. Покроем фильтр тестами. Смотрим аналоги в services/test/senders/filters/test_key_filter;

  4. Добавим фильтр в сборку приложения services/tkcontrol_services/app:

    def create_events_bus_service(config, salt_api) -> Service:
        ...
        ping_filter = PingFilter()
        ...
    
        db_sender.add_filter(ping_filter)
    
  5. После перезапуска dbadapter, backend, services сервисы начнут собирать и сохранять данные в dbadapter, а backend сможет эти данные отображать.