Creation of new functionality for developers

When developing new functionality on the part of thin client administrators, it is necessary to:

  1. Create state/script with the required functionality;
  2. Test the work of state/script at the stand;
  3. Place files similarly to the stand in tkcontrol-configure/tkcontrol_configure/config/saltmaster;
  4. Update production server configuration.

When developing new functionality from the side of the external interface of the web application, you must:

  1. Create form;
  2. Send POST request to /hosts or /groups.

Example of development cycle for new functionality

We create the ability to send users a notification with a request to turn off the computer.

  1. Go to the salt-master test bench;

  2. Add script /srv/salt/states/files/notify-shutdown.sh which will display notification:

    #!/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="Notification"
    text="Dear user, the administrator asks you to turn off your computer!"
    
    zenity \
        --info \
        --title "$title" \
        --text "$text" \
        --width 500
    
  3. Check the script work:

    sudo salt 'skupov-stagin-tkcontrol-client' cmd.script salt://files/notify-shutdown.sh
    
  4. Add button to the front-end with call to this script:

    <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. If everything went well, move the script from salt-master /srv/salt/states/files/notify-shutdown.sh to tkcontrol-configure/tkcontrol_configure/config/saltmaster;

  6. After assembling the packages, when configuring salt-master, our script will be installed:

    sudo tkcontrol-configure salt-master
    

Example of cycle for receiving and storing data in database

  1. Add the schema of the new collection to tkcontrol-modules/tkcontrol_modules/eve/schemes. Create file hosts_statuses.py with the following code (you can learn about all variations of the circuit at 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 = {
        # By default, the pattern is used for ObjectId, replace it with any сharacters other than spaces
        'item_url': 'regex("[\S]+")',
        'schema': schema,
    }
    
  2. Import the collection schema in schemes/__init__:

    from .hosts_statuses import hosts_statuses
    
  3. Register the scheme in dbadapter/tkcontrol_dbadapter/config/mongo_settings.py and in backend/tkcontrol_backend/config/mongo_settings.py:

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

At this stage, we have the ability to read data from the backend and read/create/modify data from dbadapter.

  1. Add command call to dbsync_service services/tkcontrol_services/services/dbsync_services.py:

    # We send command every 600 seconds.
    if self._timer.time_to_refresh(600):
        self._salt_request(self._ping_request, request_name='ping')
    
  2. Create a filter services/tkcontrol_services/senders/filters/ping_filter.py to extract data from salt events bus:

    from .filter import Filter
    
    
    class PingFilter(Filter):
        def __init__(self):
            super().__init__()
    
            # Command runs from runnser_async
            self._allowed_types = ['run']
    
            # Name of the command we are running
            self._allowed_functions = ['runner.manage.status']
    
            # We only care about the return value
            self._allowed_statuses = ['ret']
    
            # Meta information for updating/creating documents
            self._db_tag = {
                'method': 'patch', # Initially trying to update data
                'collection': 'hosts_statuses', # Collection name
                'force': True, # If the record cannot be updated (it does not exist), then we create record
            }
    
        # Before calling this function, Sender sets the value of the message being processed to self._ev_msg
        def process(self):
            # Retrie data from salt event
            up_hostnames = self._ev_msg.data['return']['up']
            down_hostnames = self._ev_msg.data['return']['down']
    
    
            # We generate useful data for uploading to the database. Best practice is outputing processing logic into a separate serializer, see key_filter for example
            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. We cover the filter with tests. We look at analogs in services/test/senders/filters/test_key_filter;

  4. Add filter to the application assembly services/tkcontrol_services/app:

    def create_events_bus_service(config, salt_api) -> Service:
        ...
        ping_filter = PingFilter()
        ...
    
        db_sender.add_filter(ping_filter)
    
  5. After restarting dbadapter, backend, services services will start collecting and saving data in dbadapter, and backend will be able to display this data.