ModiPy XML Configuration Syntax
What you want ModiPy to do is defined in one or more XML files. They have the following structure.
General Structure
A configuration file is a single
<config> </config>
block. Within the <config/> element, you define a series of:
- Devices, to be used as targets for your changes
- Provisioners, used to communicate with the devices
- Iterators, an optional item that can be used for parameterisation of your changes.
- Changes, the actual instructions for what to do when the changeset is applied.
Devices
Devices are defined as follows:
<device name='my_computer'> <ipaddress>192.168.4.35</ipaddress> <fqdn>my_computer.example.com</fqdn> </device>
Both the <ipaddress/> and <fqdn/> elements are optional. If they aren't supplied, ModiPy will use DNS mechanisms to figure them out based on the device name.
Other optional attributes can be added to a device by just adding more elements, for example:
<location>Somewhere in the lab</location> <lunchtime>Middle of the Day!</lunchtime>
You can add any kind of arbitrary attribute, which will then be available for use by other objects in the system, like Changes.
Provisioners
Provisioners tell ModiPy how to communicate with Devices. They are defined like this:
<provisioner name='MyFirstProvisioner' module='modipy.provisioner_command' type='MultiConnectingProvisioner'> <command>ssh -o BatchMode %(device_name)s "%(command.send)s"</command> </provisioner>
name is mandatory, just like for Devices.
module and type are also mandatory. ModiPy uses them to determine which Provisioner module to load, using standard Python package syntax. This means that if you want to extend ModiPy and define your own Provisioner, you can. Just make sure it's in your PYTHONPATH, and refer to it by module name in the configuration file.
Each provisioner may need its own attributes to define default values. For example, a TelnetProvisioner might need a default username and password to use when logging in via telnet. You might want to set a default value, rather than having to define a value for every Device you define. It all depends on how the specific Provisioner works.
For the inbuilt CommandProvisioner, you provide a <command/>, which is a commandline for communicating with the remote device. The CommandProvisioner will spawn the command and wait for its result. You can see here an example of variable substitutions with ModiPy.
In numerous places, ModiPy lets you substitute a variable for a concrete string. This is done using Python's string interpolation operator, which allows you to define named variables to substitute into strings. Let's look at this example:
ssh -o BatchMode %(device_name)s "%(command.send)s"
Here, both device_name and command.send are variables that ModiPy provides out of the box. device_name will be set to the name of the Device (duh), which you will have set when defining your Devices. command.send is the command to send to the device in order to make the change happen, and it will be supplied by the specific Change being applied at the time. This means that Provisioners and Changes are tightly coupled, as we shall see in a moment.
In this way, you can use the same general Provisioner class, such as this one that issues shell commands, to communicate with devices in different ways. Because a CommandProvisioner can issue any command, you can even get it to run any arbitrary program under the control of ModiPy. If you already have some scripts that you use for automating common tasks, it is easy to have ModiPy provide synchronisation and error checking for them.
Speaking of error checking, by default a CommandProvisioner will consider a change to have failed if the command it runs returns an error code other than 0, so most of your programs won't need any special help to integrate with ModiPy's error checking.
Changes
Changes are the heart of the system, and provide a rich set of functionality. Much of this functionality can be ignored until you need it, so let's start with a basic Change definition.
<change name='my_first_change'
module='modipy.change_command'
type='CommandChange'>
<target name='my_computer'/>
<impl>
<command>
<send>echo "Hello, World!"</send>
</command>
</impl>
</change>
You can see the name, module, and type attributes are in use again. They function the same as those of Provisioners.
New here is the target element. This tells ModiPy which Device(s) this change should be applied to. There's also an implied linkage: Changes of a certain type are known by ModiPy to be supported by given Provisioners. That's how ModiPy works how which Provisioner to use for a given Device. You can override this automated decision process, but most of the time you won't need to.
You will also see the impl element. This is one of the core Change control mechanisms. Every Change goes through a minimum of 3 stages:
- Pre-implementation - where checks can be made to see if a change should be applied
- Implementation - where the change is applied
- Post-implementation - where other checks can be made to ensure the change was applied correctly
If something should go wrong, there are other stages that a Change can go through:
- Pre-backout - where checks can be made to see if a change can, and should, be backed out
- Backout - where a change is backed out
- Post-Backout - where other checks can be made to ensure the change was backed out correctly
These steps provide a robust change management framework applicable to any situation.
In our simple example, only one step: implementation, is provided. This means no checks will be run, and no automated backout is possible. You can see that a command element is defined, with a child sub-element. This is what was referred to in the Provisioner's use of %(command.send)s. This is the command that will be substituted in the Provisioner's commandline string when it runs.
More Detail On Changes
If you want more detail on Change functionality, check out these pages:
