Monday, October 17, 2022

Salt Extensions

Since Salt is written in Python, it is fully extensible in Python. The easiest way to extend Salt for new things is to put files in the file_roots directory on the Salt master. Unfortunately, there is no package manager for Salt extensions yet. Those files automatically get synchronized to the minions, either when running state.apply or explicitly running saltutil.sync_state. The latter is useful if you want to test, for example, a dry run of the state without causing any changes but with the modified modules.

States

State modules go under the root directory for the environment. If you want to share State modules between environments, it is possible to make a custom root and share that root between the right environments.

The following is an example of a module that ensures there are no files that have the name mean in them under a specific directory. It is probably not very useful, although making sure that unneeded files are not there could be important. For example, you might want to enforce no .git directories.

def enforce_no_mean_files(name):

mean_files = __salt__['files.find'](name,

path="*mean*")

# ...continues below...

The name of the function maps to the name of the state in the SLS state file. If you put this code in mean.py, the appropriate way to address this state would be mean.enforce_no_mean_files.

The right way to find files or do anything in a Salt state extension is to call Salt executors. In most non-toy examples, this means writing a matching pair: a Salt executor extension and a Salt state extension.

Since you want to progress one thing at a time, you use a prewritten Salt executor: the file module, which has the find function.

def enforce_no_mean_files(name):

# ...continued... 

if mean_files = []:

return dict(

name=name,

result=True,

comment='No mean files detected',

changes=[],

)

# ...continues below...

One of the things the state module is responsible for, and often the most important thing, is doing nothing if the state is already achieved. This is what being a convergence loop is all about—optimizing to achieve convergence.

def enforce_no_mean_files(name):

# ...continued...

changes = dict(

old=mean_files,

new=[],

)

# ...continues below...

You now know what the changes are going to be. Calculating it here means you can guarantee consistency between the responses in the test vs. non-test mode.

def enforce_no_mean_files(name):

# ...continued...

changes = dict(

if __opts__['test']:

return dict(

name=name,

result=None,

comment=f"The state of {name} will be

changed",

changes=changes,

)

# ...continues below...

The next important responsibility is to support the test mode. It is considered a best practice to always test before applying a state. You want to clearly articulate the changes that this module does if activated.

def enforce_no_mean_files(name):

# ...continued...

changes = dict(

for fname in mean_files:

__salt__['file.remove'](fname)

# ...continues below...

In general, you should only be calling one function from the execution module that matches the state module. Since you are using file as the execution module in this example, you call the remove function in a loop.

def enforce_no_mean_files(name):

# ...continued...

changes = dict(

return dict(

name=name,

changes=changes,

result=True,

comment=f"The state of {name} was

changed",

)

# ...continues below...

Finally, you return a dictionary with the same changes as those documented in the test mode but with a comment indicating that these have already run.

This is the typical structure of a state module: one (or more) functions that accept a name (and possibly more arguments) and then return a result. The structure of checking if changes are needed and whether you are in test mode, and then performing the changes is also typical.

Share:

0 comments:

Post a Comment