Add ability to import automount files from the command-line.

Support is fairly basic right now and will only work on the CLI. All
the work is done on the client side.

To continue past errors use the --continue option.

Fixed a bug where direct mounts weren't always added properly.

Added real user documentation to the plugin.

ticket 78
This commit is contained in:
Rob Crittenden 2010-09-24 15:17:14 -04:00 committed by Adam Young
parent cf21396345
commit 0197ebbb7b

View File

@ -20,10 +20,92 @@
"""
Automount
Stores automount(8) configuration for autofs(8) in IPA.
The base of an automount configuration is the configuration file auto.master.
This is also the base location in IPA. Multiple auto.master configurations
can be stored in separate locations. A location is implementation-specific
with the default being a location named 'default'. For example, you can have
locations by geographic region, by floor, by type, etc.
Automount has three basic object types: locations, maps and keys.
A location defines a set of maps anchored in auto.master. This allows you
to store multiple automount configurations. A location in itself isn't
very interesting, it is just a point to start a new automount map.
A map is roughly equivalent to discrete automount files. It is storage
location for keys.
A key is a mount point associated to a map.
When a new location is created two maps are automatically created for
it: auto.master and auto.direct. auto.master is the root map for all
automount maps for the location. auto.direct is the default map for
direct mounts and is mounted on /-.
EXAMPLES:
Locations:
Create a named location, "Baltimore":
ipa automountlocation-add baltimore
Display the new locations:
ipa automountlocation-show baltimore
Find available locations:
ipa automountlocation-find
Remove a named automount location:
ipa automountlocation-del baltimore
Show what the automount maps would look like if they were in the filesystem:
ipa automountlocation-tofiles baltimore
Import an existing configuration into a location:
ipa automountlocation-import baltimore /etc/auto.master
The import will fail if any duplicate entries are found. For
continous operation where errors are ignored use the --continue
option.
Maps:
Create a new map, "auto.share":
ipa automountmap-add baltimore auto.share
Display the new map:
ipa automountmap-show baltimore auto.share
Find maps in the location baltimore:
ipa automountmap-find baltimore
Remove the auto.share map:
ipa automountmap-del baltimore auto.share
Keys:
Create a new key for the auto.share map in location baltimore. This ties
the map we previously created to auto.master:
ipa automountkey-add baltimore auto.master /share --info=auto.share
Create a new key for our auto.share map, an NFS mount for man pages:
ipa automountkey-add baltimore auto.share man --info="-ro,soft,rsize=8192,wsize=8192 ipa.example.com:/shared/man"
Find all keys for the auto.share map:
ipa automountkey-find baltimore auto.share
Remove the man key from the auto.share map:
ipa automountkey-del baltimore auto.share man
"""
"""
Developer notes:
RFC 2707bis http://www.padl.com/~lukeh/rfc2307bis.txt
A few notes on automount:
- It was a design decision to not support different maps by location
- The default parent when adding an indirect map is auto.master
- This uses the short format for automount maps instead of the
URL format. Support for ldap as a map source in nsswitch.conf was added
@ -89,6 +171,7 @@ from ipalib import Object, Command
from ipalib import Flag, Str
from ipalib.plugins.baseldap import *
from ipalib import _, ngettext
import os
class automountlocation(LDAPObject):
@ -161,6 +244,8 @@ class automountlocation_tofiles(LDAPQuery):
def execute(self, *args, **options):
ldap = self.obj.backend
location = self.api.Command['automountlocation_show'](args[0])
maps = []
result = self.api.Command['automountkey_find'](
cn=args[0], automountmapname=u'auto.master'
@ -175,8 +260,9 @@ class automountlocation_tofiles(LDAPQuery):
keys = {}
for m in maps:
info = m['automountinformation'][0]
key = info.split(None)
result = self.api.Command['automountkey_find'](
cn=args[0], automountmapname=info
cn=args[0], automountmapname=key[0]
)
truncated = result['truncated']
keys[info] = result['result']
@ -190,12 +276,21 @@ class automountlocation_tofiles(LDAPQuery):
textui.print_plain('/etc/auto.master:')
for m in maps:
textui.print_plain(
'%s\t/etc/%s' % (
m['automountkey'][0], m['automountinformation'][0]
if m['automountinformation'][0].startswith('-'):
textui.print_plain(
'%s\t%s' % (
m['automountkey'][0], m['automountinformation'][0]
)
)
else:
textui.print_plain(
'%s\t/etc/%s' % (
m['automountkey'][0], m['automountinformation'][0]
)
)
)
for m in maps:
if m['automountinformation'][0].startswith('-'):
continue
info = m['automountinformation'][0]
textui.print_plain('---------------------------')
textui.print_plain('/etc/%s:' % info)
@ -209,6 +304,176 @@ class automountlocation_tofiles(LDAPQuery):
api.register(automountlocation_tofiles)
class automountlocation_import(LDAPQuery):
"""
Import automount files for a specific location.
"""
takes_args = (
Str('masterfile',
label=_('Master file'),
doc=_('Automount master file'),
),
)
takes_options = (
Flag('continue?',
cli_name='continue',
doc=_('Continous operation mode. Errors are reported but the process continues'),
),
)
def __read_mapfile(self, filename):
try:
fp = open(filename, 'r')
map = fp.readlines()
fp.close()
except IOError, e:
if e.errno == 2:
raise errors.NotFound(reason=_('File %(file)s not found' % {'file':filename}))
else:
raise e
return map
def forward(self, *args, **options):
"""
The basic idea is to read the master file and create all the maps
we need, then read each map file and add all the keys for the map.
"""
location = self.api.Command['automountlocation_show'](args[0])
result = {'maps':[], 'keys':[], 'skipped':[], 'duplicatekeys':[], 'duplicatemaps':[]}
maps = {}
master = self.__read_mapfile(args[1])
for m in master:
if m.startswith('#'):
continue
m = m.rstrip()
if m.startswith('+'):
result['skipped'].append([m,args[1]])
continue
if len(m) == 0:
continue
am = m.split(None)
if am[1].startswith('/'):
mapfile = am[1].replace('"','')
am[1] = os.path.basename(am[1])
maps[am[1]] = mapfile
info = ' '.join(am[1:])
# Add a new key to the auto.master map for the new map file
try:
api.Command['automountkey_add'](cn=args[0], automountmapname=u'auto.master', automountkey=unicode(am[0]), automountinformation=unicode(' '.join(am[1:])))
result['keys'].append([am[0], u'auto.master'])
except errors.DuplicateEntry, e:
if options.get('continue', False):
result['duplicatekeys'].append(am[0])
pass
else:
raise errors.DuplicateEntry(message=unicode('key %(key)s already exists' % {'key':am[0]}))
# Add the new map
if not am[1].startswith('-'):
try:
api.Command['automountmap_add'](cn=args[0], automountmapname=unicode(am[1]))
result['maps'].append(am[1])
except errors.DuplicateEntry, e:
if options.get('continue', False):
result['duplicatemaps'].append(am[0])
pass
else:
raise errors.DuplicateEntry(message=unicode('map %(map)s already exists' % {'map':am[1]}))
except errors.DuplicateEntry:
# This means the same map is used on several mount points.
pass
# Now iterate over the map files and add the keys. To handle
# continuation lines I'll make a pass through it to skip comments
# etc and also to combine lines.
for m in maps:
map = self.__read_mapfile(maps[m])
lines = []
cont = ''
for x in map:
if x.startswith('#'):
continue
x = x.rstrip()
if x.startswith('+'):
result['skipped'].append([m, maps[m]])
continue
if len(x) == 0:
continue
if x.endswith("\\"):
cont = cont + x[:-1] + ' '
else:
lines.append(cont + x)
cont=''
for x in lines:
am = x.split(None)
key = unicode(am[0].replace('"',''))
try:
api.Command['automountkey_add'](cn=args[0], automountmapname=unicode(m), automountkey=key, automountinformation=unicode(' '.join(am[1:])))
result['keys'].append([key,m])
except errors.DuplicateEntry, e:
if options.get('continue', False):
result['duplicatekeys'].append(am[0])
pass
else:
raise e
return dict(result=result)
def output_for_cli(self, textui, result, *keys, **options):
maps = result['result']['maps']
keys = result['result']['keys']
duplicatemaps = result['result']['duplicatemaps']
duplicatekeys = result['result']['duplicatekeys']
skipped = result['result']['skipped']
textui.print_plain('Imported maps:')
for m in maps:
textui.print_plain(
'Added %s' % m
)
textui.print_plain('')
textui.print_plain('Imported keys:')
for k in keys:
textui.print_plain(
'Added %s to %s' % (
k[0], k[1]
)
)
textui.print_plain('')
textui.print_plain('Ignored keys:')
for k in keys:
textui.print_plain(
'Ignored %s to %s' % (
k[0], k[1]
)
)
if options.get('continue', False) and len(duplicatemaps) > 0:
textui.print_plain('')
textui.print_plain('Duplicate maps skipped:')
for m in duplicatemaps:
textui.print_plain(
'Skipped %s' % m
)
if options.get('continue', False) and len(duplicatekeys) > 0:
textui.print_plain('')
textui.print_plain('Duplicate keys skipped:')
for k in duplicatekeys:
textui.print_plain(
'Skipped %s' % k
)
api.register(automountlocation_import)
class automountmap(LDAPObject):
"""
Automount map object.