By design, Salt makes it very easy to write your own custom execution modules and functions. It abstracts away much of the nitty-gritty about writing Python for system administration, yet leaves you with all the power of Python to get things done.

<aside> 💡 This means that we can write Salt modules that integrate with our own internal tools or proprietary software. We can even write quick modules just to reorganize or reformat data for use in other parts of Salt.

</aside>


Grains and the virtual function

# Define the module's virtual name
__virtualname__ = 'pkg'

def __virtual__():
    '''
    Confirm this module is on a Debian-based system
    '''
    # If your minion is running an OS which is Debian-based but does not have
    # an "os_family" grain of Debian, then the proper fix is NOT to check for
    # the minion's "os_family" grain here in the __virtual__. The correct fix
    # is to add the value from the minion's "os" grain to the _OS_FAMILY_MAP
    # dict in salt/grains/core.py, so that we assign the correct "os_family"
    # grain to the minion.
    if __grains__.get('os_family') == 'Debian':
        return __virtualname__
    return False, 'The pkg module could not be loaded: unsupported OS family'

The virtual function is a function Salt looks for when it is loading execution modules. It serves a couple of purposes:

return __**virtualname__**

<aside> 💡 We learned that if we return a string from our virtual function, then that string will be used as the module name. If we instead return True from a virtual function, then Salt will load that module under its filename.

</aside>

def __virtual__():      
'''      Only load this module if the
 mysql li-  braries exist      '''      
if HAS_MYSQLDB:          
return True      
return False

<aside> 💡 Note that this module only returns True if the required li- braries are installed, and returns False otherwise. If False is returned, the module will not load at all. This is a common pattern in Salt.

</aside>


The opts and pillar functions

<aside> 💡 The interesting thing is that the minion options are not limited to those defaults defined inside the minion config file. In fact, we can store arbi- trary keys and values inside the minion config file.

</aside>

def option(
        value,
        default='',
        omit_opts=False,
        omit_master=False,
        omit_pillar=False):
    '''
    Pass in a generic option and receive the value that will be assigned

    CLI Example:

    .. code-block:: bash

        salt '*' config.option redis.host
    '''
    if not omit_opts:
        if value in __opts__:
            return __opts__[value]
    if not omit_master:
        if value in __pillar__.get('master', {}):
            return __pillar__['master'][value]
    if not omit_pillar:
        if value in __pillar__:
            return __pillar__[value]
    if value in DEFAULTS:
        return DEFAULTS[value]
    return default
  1. config.option function will first search the minion options opts****