Helping ordinary people create extraordinary websites!
HOME TUTORIALS SCRIPTS WEB HOSTING BLOG FORUM
Get Our Newsletter
Email:

Cultured Perl: Automating UNIX System Administration with Perl

By Teodor Zlatanov
2004-07-15


Configuration File Management

Managing configuration files is tough. You can start by considering whether cfengine is adequate for the task. Unfortunately, cfengine's editing is line oriented, so complex configuration files will probably not be a good match for it. But simple files such as the TCP wrappers configuration file /etc/hosts.allow are best done through cfengine.

Usually, you will want to keep more than one version of configuration files. For instance, you may need two sets of DNS configurations in /etc/resolv.conf, one for external, and another for internal machines. The external DNS resolv.conf file could, naturally, go into a directory called "external", while the internal resolv.conf could go into the corresponding "internal" directory. Let's assume both directories are under a global "spec" directory, which is a sort of root for configuration files.

The following code will traverse the spec directory, searching for a filename suitable for a given machine. It will start at /usr/local/spec and go down, looking for files that match the one requested. Furthermore, it will check whether or not each directory's name is the same as the class belonging to some machine. Thus, if we request locate_global('resolv.conf', 'wonka'), the function will look under /usr/local/spec for files named resolv.conf that are in either the root directory, or in children of the root directory whose names match the classes that the "wonka" machine belongs to. So, if "wonka" belongs to the "chocolate" class, and if there is a /usr/local/spec/chocolate/resolv.conf file, then locate_global() will return "/usr/local/spec/chocolate/resolv.conf".

If locate_global() finds multiple matching versions of a file (for instance, /usr/local/spec/chocolate/resolv.conf and /usr/local/spec/resolv.conf), it will give up. The assumption is that we are better off with no configuration than with one of the two wrong ones. Also, note that machines can belong to more than one class.

You can build on this structure. For instance,

• /usr/local/spec/external/chocolate/resolv.conf
• /usr/local/spec/internal/chocolate/resolv.conf
• /usr/local/spec/external/sugar/resolv.conf
• /usr/local/spec/internal/sugar

will contain files for external and internal "chocolate" and "sugar" machines. You just have to set up the your machine_belongs_to_class() function correctly.

Once locate_global() returns a file name, it's pretty simple to copy it to the remote system with scp or rsync. Remember, always preserve the permissions and attributes of the file. Scp needs the "-p" flag, and rsync needs the "-a" flag. Consult the documentation for the file copy command you want to use. And there you have a unified configuration file tree.

Listing 1: Spec directory traversal

# {{{ locate_global: use spec directory to find a file matching the current class

sub locate_global($$)
{
# this code uses File::Find
my $spec_dir = '/usr/local/spec';
my $file = shift || return undef; # file name sought
my $machine = shift || return undef; # machine name
my @matches;
my $find_sub =
sub
{
print "found file $_";
push @matches, $File::Find::name if ($_ eq $file);
# the machine_belongs_to_class sub returns true if a machine
# belongs to a class; we stop traversing down otherwise
$File::Find::prune = 1 unless
machine_belongs_to_class($machine, $_) || $_ eq '.';
};
find($find_sub, $spec_dir);
if (scalar @matches > 1)
{
print "More than one match for file $file,",
"machine $machine found: @matches" ;
return undef;
}
elsif (scalar @matches == 1)
{
return $matches[0]; # this is the right match
}
else
{
return undef; # no files found
}
}
# }}}
One challenge once you set up this sort of /usr/local/spec structure is: how do we know that resolv.conf should go into /etc? You either have to do without the nice hierarchical structure shown here, adapt it (replace "/" with "+", for instance -- a risky and somewhat ugly approach), or maintain a separate mapping between symbolic names and real names. For instance, "root-profile" can be the symbolic name for "~root/.profile". The last approach is the one I prefer, because it flattens out filenames and eliminates the problem of having hidden filenames. Everything is visible and tidy, under one directory structure. Of course, it's a little more work every time you add a file to the list. The program has to know that "resolv.conf" should be copied to "/etc/resolv.conf" on the remote system, and "dfstab" should go to "/etc/dfs/dfstab" (the Solaris file for sharing NFS filesystems).

Now let's talk about what you can do once you have this spec directory hierarchy set up. You could, if you wanted to, look for all the users named Joe:

Listing 2: Find all password files and grep them for Joe

grep Joe `find /usr/local/spec -name passwd`
Or you can use a tool such as rep.pl (link to rep.pl), written by David Pitts, to replace every word with another:

Listing 3: Find all hosts files and change "wonka" to "willy"

find /usr/local/spec -name hosts -exec rep.pl wonka willy {} \;
Now, you can write both Listing 2 and 3 in Perl, if you want; the find2perl utility was written just for that. It's much simpler, however, to just use find from the start. It really is a wonderful utility that every system administrator should use. More importantly, it took me 5 minutes to write the two listings. How long would it take you to figure out how to use find2perl, store the code it produces in a file, then run that file? Try it and see for yourself!

Tutorial Pages:
» A Centralized Configuration File Strategy
» The Tool Cfengine
» Configuration File Management
» Task Automation
» Summary
» Resources


First published by IBM DeveloperWorks


 | Bookmark
Related Tutorials:
» Random subroutines in Perl
» Log Script Use
» Creating Perl Modules for Web Sites
» Bit Vector, Using Perl Vec
» Build a Perl/CGI Voting System
» Perl Range Operator

Ask A Question
characters left.