Like most configuration management systems, salt allows users to describe server configurations in an abstract fashion (i.e. with code or pseudo-code) without requiring any knowledge of how those configurations get applied to a particluar operating system.

For example the following expression in salt will result in the apache package being installed on any server it is applied to, whether that server uses yum, apt or pacman.

apache:
pkg:
- installed

However, if you want to bring a new or custom operating system under salt management, you will need to let salt know how to manage the various artifacts (such as packages and services) for that system.

This article shows you how.

The first step is to get your operating system name to be picked up by grains (a component of salt which enumerates static information about your system).


#salt -v salt-206.mydomain.local grains.item os
Executing job with jid 20120903195900028504
-------------------------------------------
salt-206.mydomain.local: Unknown Linux

With a salt state as described above, attempting to apply this state with the state.highstate command should result in the following exception:


#salt -v salt-206.mydomain.local state.highstate
Executing job with jid 20120903200519438968
-------------------------------------------
salt-206.mydomain.local:
----------
State: - pkg
Name: apache
Function: installed
Result: False
Comment: An exception occured in this state: Traceback (most recent call last):
File "/opt/pkg/salt-0.10.2-1/lib/python2.6/site-packages/salt/state.py", line 821, in call
*cdata['args'], **cdata['kwargs'])
File "/opt/pkg/salt-0.10.2-1/lib/python2.6/site-packages/salt/states/pkg.py", line 60, in installed
cver = __salt__['pkg.version'](name)
KeyError: 'pkg.version'

This is because salt is unable to determine which salt module to use to manage packages on this unknown operating system. Let’s start to rectify this by placing an appropriate package manager module in our salt module directory. I’ve installed salt under /opt/pkg/salt so my module directory is /opt/pkg/salt/lib/python2.6/site-packages/salt, yours will probably be different. You can locate your salt install directory with:


#python
Python 2.6.4 (r264:75706, Jul 12 2010, 23:06:45)
[GCC 4.0.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import salt
>>> print salt.__file__
<module 'salt' from '/opt/pkg/salt-0.10.2-1/lib/python2.6/site-packages/salt/__init__.pyc'>

Change into this directory and then into the modules subdirectory. Hopefully whatever package manager your operating system is using is similar to one that exists already. If not you will need to read one of the existing modules such as apt.py or yumpkg.py and implement the functions described in these modules. In our case (and I would hope, in most cases) we do have a module which is very similar (apt.py for us) and so can take a copy. You can give your new module any name you like, here we’ll call it fooapt.py.


#pwd
/opt/pkg/salt/lib/python2.6/site-packages/salt/modules
#cp apt.py fooapt.py

Now we need to edit fooapt.py and search for the __virtual__() function which should be very near the top of the module. This function will return ‘pkg’ for grains values that identify your custom operating system. Obviously os is a common grain to key upon but in our case we are going to use the lsb_distrib_codename key as I keep my operating system identifiers in /etc/lsb-release and this is how grains enumerates them.

After my changes my fooapt.py looks like:


def __virtual__():
'''
Confirm this module is on a Foo based system
'''
return 'pkg' if __grains__['lsb_distrib_codename'] == 'foo' else False

Now when we run our state.highstate command:


#salt -v salt-206.mydomain.local state.highstate
Executing job with jid 20120903202013258385
-------------------------------------------
salt-206.mydomain.local:
----------
State: - pkg
Name: apache
Function: installed
Result: True
Comment: Package apache installed
Changes: apache: {'new': '2.2.17', 'old': ''}

Hooray, that’s better.

We generally need to repeat this process for each abstracted resource that we want to include in our salt state files. The next most obvious one is the service resource.

Let’s expand our state file to include a service definition:


apache:
pkg:
- installed
service:
- running
- require:
- pkg: apache

Once again, running state.highstate should fail:


Executing job with jid 20120903205201606727
-------------------------------------------
salt-206.mydomain.local:
----------
State: - service
Name: apache
Function: running
Result: False
Comment: An exception occured in this state: Traceback (most recent call last):
File "/opt/pkg/salt-0.10.2-1/lib/python2.6/site-packages/salt/state.py", line 823, in call
ret = self.states[cdata['full']](*cdata['args'])
File "/opt/pkg/salt-0.10.2-1/lib/python2.6/site-packages/salt/states/service.py", line 255, in running
changes = {name: __salt__['service.start'](name)}
File "/opt/pkg/salt-0.10.2-1/lib/python2.6/site-packages/salt/modules/service.py", line 50, in start
cmd = os.path.join(grainmap[__grains__['os']],
KeyError: 'Unknown Linux'

Again, Debian is the closest service manager to our custom OS so we’ll copy this module also:


#pwd
/opt/pkg/salt/lib/python2.6/site-packages/salt/modules
#cp debian_service.py foo_service.py

And edit the __virtual__() module function:


def __virtual__():
'''
Only work on Foo
'''
if __grains__['lsb_distrib_codename'] == 'Foo':
return 'service'
return False

Slightly irritatingly we still get an exception when we run state.highstate:


#salt -v salt-206.mydomain.local state.highstate
Executing job with jid 20120903212940065249
-------------------------------------------
salt-206.mydomain.local:
----------
State: - service
Name: apache
Function: running
Result: False
Comment: An exception occured in this state: Traceback (most recent call last):
File "/opt/pkg/salt-0.10.2-1/lib/python2.6/site-packages/salt/state.py", line 823, in call
ret = self.states[cdata['full']](*cdata['args'])
File "/opt/pkg/salt-0.10.2-1/lib/python2.6/site-packages/salt/states/service.py", line 255, in running
changes = {name: __salt__['service.start'](name)}
File "/opt/pkg/salt-0.10.2-1/lib/python2.6/site-packages/salt/modules/service.py", line 51, in start
cmd = os.path.join(grainmap[__grains__['os']],
KeyError: 'Unknown Linux'

Once again our undefined os grain is nipping at our ankles. Here we need to update the list of operating systems for which salt will avoid using the default service.py module. To do this, edit service.py and find its __virtual__() function. Within this is a list called disable. Add our ‘Unknown Linux’ to the end of this list:


def __virtual__():
'''
Only work on systems which default to systemd
'''
# Disable on these platforms, specific service modules exist:
disable = [
'RedHat',
'CentOS',
'Scientific',
'Fedora',
'Gentoo',
'Ubuntu',
'Debian',
'Unknown Linux', #<<<<<< Here's our new entry
]
if __grains__['os'] in disable:
return False
# Disable on all non-Linux OSes as well
if __grains__['kernel'] != 'Linux':
return False
return 'service'

Now try state.highstate again:
#salt -v salt-206.mydomain.local state.highstate
Executing job with jid 20120903213444867910
——————————————-
salt-206.mydomain.local:
———-
State: – service
Name: apache
Function: running
Result: True
Comment: Started Service apache
Changes: apache: True

Hooray!

This method is, however, a little irrtating as, up until this point, we’ve been adding modules without touching the salt core code. With our update to the disable list, we now have something that we have to maintain between salt releases. Pity.

Still, we now can manage packages and services on our new operating system. We can follow a similar pattern with each new resource type we wish to manage.

< Back