summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py
blob: ce7976a98549f921de010e174cc81d9c16baf3b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
"""All POSIX Type client support for Bcfg2."""

import os
import re
import sys
import shutil
import Bcfg2.Options
import Bcfg2.Client.Tools
from datetime import datetime
from Bcfg2.Compat import walk_packages
from Bcfg2.Client.Tools.POSIX.base import POSIXTool


class POSIX(Bcfg2.Client.Tools.Tool):
    """POSIX File support code."""

    options = Bcfg2.Client.Tools.Tool.options + POSIXTool.options + [
        Bcfg2.Options.PathOption(
            cf=('paranoid', 'path'), default='/var/cache/bcfg2',
            dest='paranoid_path',
            help='Specify path for paranoid file backups'),
        Bcfg2.Options.Option(
            cf=('paranoid', 'max_copies'), default=1, type=int,
            dest='paranoid_copies',
            help='Specify the number of paranoid copies you want'),
        Bcfg2.Options.BooleanOption(
            '-P', '--paranoid', cf=('client', 'paranoid'),
            help='Make automatic backups of config files')]

    def __init__(self, config):
        Bcfg2.Client.Tools.Tool.__init__(self, config)
        self._handlers = self._load_handlers()
        self.logger.debug("POSIX: Handlers loaded: %s" %
                          (", ".join(list(self._handlers.keys()))))
        self.__req__ = dict(Path=dict())
        for etype, hdlr in list(self._handlers.items()):
            self.__req__['Path'][etype] = hdlr.__req__
            self.__handles__.append(('Path', etype))
        # Tool.__init__() sets up the list of handled entries, but we
        # need to do it again after __handles__ has been populated. we
        # can't populate __handles__ when the class is created because
        # _load_handlers() _must_ be called at run-time, not at
        # compile-time.  This also has to _extend_ self.handled, not
        # set it, because self.handled has some really crazy
        # semi-global thing going that, frankly, scares the crap out
        # of me.
        for struct in config:
            self.handled.extend([e for e in struct
                                 if (e not in self.handled and
                                     self.handlesEntry(e))])

    def _load_handlers(self):
        """ load available POSIX tool handlers.  this must be called
        at run-time, not at compile-time, or we get wierd circular
        import issues. """
        rv = dict()
        for submodule in walk_packages(path=__path__, prefix=__name__ + "."):
            mname = submodule[1].rsplit('.', 1)[-1]
            if mname == 'base':
                continue
            try:
                module = getattr(__import__(submodule[1]).Client.Tools.POSIX,
                                 mname)
            except ImportError:
                continue
            hdlr = getattr(module, "POSIX" + mname)
            if POSIXTool in hdlr.__mro__:
                # figure out what entry type this handler handles
                etype = hdlr.__name__[5:].lower()
                rv[etype] = hdlr(self.config)
        return rv

    def canVerify(self, entry):
        if not Bcfg2.Client.Tools.Tool.canVerify(self, entry):
            return False
        if not self._handlers[entry.get("type")].fully_specified(entry):
            self.logger.error('POSIX: Cannot verify incomplete entry %s. '
                              'Try running bcfg2-lint.' %
                              entry.get('name'))
            return False
        return True

    def canInstall(self, entry):
        """Check if entry is complete for installation."""
        if not Bcfg2.Client.Tools.Tool.canInstall(self, entry):
            return False
        if not self._handlers[entry.get("type")].fully_specified(entry):
            self.logger.error('POSIX: Cannot install incomplete entry %s. '
                              'Try running bcfg2-lint.' %
                              entry.get('name'))
            return False
        return True

    def InstallPath(self, entry):
        """Dispatch install to the proper method according to type"""
        self.logger.debug("POSIX: Installing entry %s:%s:%s" %
                          (entry.tag, entry.get("type"), entry.get("name")))
        self._paranoid_backup(entry)
        return self._handlers[entry.get("type")].install(entry)

    def VerifyPath(self, entry, modlist):
        """Dispatch verify to the proper method according to type"""
        self.logger.debug("POSIX: Verifying entry %s:%s:%s" %
                          (entry.tag, entry.get("type"), entry.get("name")))
        ret = self._handlers[entry.get("type")].verify(entry, modlist)
        if Bcfg2.Options.setup.interactive and not ret:
            entry.set('qtext',
                      '%s\nInstall %s %s: (y/N) ' %
                      (entry.get('qtext', ''),
                       entry.get('type'), entry.get('name')))
        return ret

    def _prune_old_backups(self, entry):
        """ Remove old paranoid backup files """
        bkupnam = entry.get('name').replace('/', '_')
        bkup_re = re.compile(
            bkupnam + r'_\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}$')
        # current list of backups for this file
        try:
            bkuplist = [f
                        for f in os.listdir(Bcfg2.Options.setup.paranoid_path)
                        if bkup_re.match(f)]
        except OSError:
            err = sys.exc_info()[1]
            self.logger.error("POSIX: Failed to create backup list in %s: %s" %
                              (Bcfg2.Options.setup.paranoid_path, err))
            return
        bkuplist.sort()
        while len(bkuplist) >= int(Bcfg2.Options.setup.paranoid_copies):
            # remove the oldest backup available
            oldest = bkuplist.pop(0)
            self.logger.info("POSIX: Removing old backup %s" % oldest)
            try:
                os.remove(os.path.join(Bcfg2.Options.setup.paranoid_path,
                                       oldest))
            except OSError:
                err = sys.exc_info()[1]
                self.logger.error(
                    "POSIX: Failed to remove old backup %s: %s" %
                    (os.path.join(Bcfg2.Options.setup.paranoid_path, oldest),
                     err))

    def _paranoid_backup(self, entry):
        """ Take a backup of the specified entry for paranoid mode """
        if (entry.get("paranoid", 'false').lower() == 'true' and
                Bcfg2.Options.setup.paranoid and
                entry.get('current_exists', 'true') == 'true' and
                not os.path.isdir(entry.get("name"))):
            self._prune_old_backups(entry)
            bkupnam = "%s_%s" % (entry.get('name').replace('/', '_'),
                                 datetime.isoformat(datetime.now()))
            bfile = os.path.join(Bcfg2.Options.setup.paranoid_path, bkupnam)
            try:
                shutil.copy(entry.get('name'), bfile)
                self.logger.info("POSIX: Backup of %s saved to %s" %
                                 (entry.get('name'), bfile))
            except IOError:
                err = sys.exc_info()[1]
                self.logger.error("POSIX: Failed to create backup file for "
                                  "%s: %s" % (entry.get('name'), err))