ensure-dirs: Fix epydoc error
[ganeti-local] / lib / tools / ensure_dirs.py
1 #
2 #
3
4 # Copyright (C) 2011 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21 """Script to ensure permissions on files/dirs are accurate.
22
23 """
24
25 import errno
26 import os
27 import os.path
28 import optparse
29 import sys
30 import stat
31 import logging
32
33 from ganeti import constants
34 from ganeti import errors
35 from ganeti import runtime
36 from ganeti import ssconf
37 from ganeti import utils
38 from ganeti import cli
39
40
41 (DIR,
42  FILE,
43  QUEUE_DIR) = range(1, 4)
44
45 ALL_TYPES = frozenset([
46   DIR,
47   FILE,
48   QUEUE_DIR,
49   ])
50
51
52 class EnsureError(errors.GenericError):
53   """Top-level error class related to this script.
54
55   """
56
57
58 def EnsurePermission(path, mode, uid=-1, gid=-1, must_exist=True,
59                      _chmod_fn=os.chmod, _chown_fn=os.chown, _stat_fn=os.stat):
60   """Ensures that given path has given mode.
61
62   @param path: The path to the file
63   @param mode: The mode of the file
64   @param uid: The uid of the owner of this file
65   @param gid: The gid of the owner of this file
66   @param must_exist: Specifies if non-existance of path will be an error
67   @param _chmod_fn: chmod function to use (unittest only)
68   @param _chown_fn: chown function to use (unittest only)
69
70   """
71   logging.debug("Checking %s", path)
72   try:
73     st = _stat_fn(path)
74
75     fmode = stat.S_IMODE(st[stat.ST_MODE])
76     if fmode != mode:
77       logging.debug("Changing mode of %s from %#o to %#o", path, fmode, mode)
78       _chmod_fn(path, mode)
79
80     if max(uid, gid) > -1:
81       fuid = st[stat.ST_UID]
82       fgid = st[stat.ST_GID]
83       if fuid != uid or fgid != gid:
84         logging.debug("Changing owner of %s from UID %s/GID %s to"
85                       " UID %s/GID %s", path, fuid, fgid, uid, gid)
86         _chown_fn(path, uid, gid)
87   except EnvironmentError, err:
88     if err.errno == errno.ENOENT:
89       if must_exist:
90         raise EnsureError("Path %s should exist, but does not" % path)
91     else:
92       raise EnsureError("Error while changing permissions on %s: %s" %
93                         (path, err))
94
95
96 def EnsureDir(path, mode, uid, gid, _lstat_fn=os.lstat, _mkdir_fn=os.mkdir,
97               _ensure_fn=EnsurePermission):
98   """Ensures that given path is a dir and has given mode, uid and gid set.
99
100   @param path: The path to the file
101   @param mode: The mode of the file
102   @param uid: The uid of the owner of this file
103   @param gid: The gid of the owner of this file
104   @param _lstat_fn: Stat function to use (unittest only)
105   @param _mkdir_fn: mkdir function to use (unittest only)
106   @param _ensure_fn: ensure function to use (unittest only)
107
108   """
109   logging.debug("Checking directory %s", path)
110   try:
111     # We don't want to follow symlinks
112     st = _lstat_fn(path)
113   except EnvironmentError, err:
114     if err.errno != errno.ENOENT:
115       raise EnsureError("stat(2) on %s failed: %s" % (path, err))
116     _mkdir_fn(path)
117   else:
118     if not stat.S_ISDIR(st[stat.ST_MODE]):
119       raise EnsureError("Path %s is expected to be a directory, but isn't" %
120                         path)
121
122   _ensure_fn(path, mode, uid=uid, gid=gid)
123
124
125 def RecursiveEnsure(path, uid, gid, dir_perm, file_perm):
126   """Ensures permissions recursively down a directory.
127
128   This functions walks the path and sets permissions accordingly.
129
130   @param path: The absolute path to walk
131   @param uid: The uid used as owner
132   @param gid: The gid used as group
133   @param dir_perm: The permission bits set for directories
134   @param file_perm: The permission bits set for files
135
136   """
137   assert os.path.isabs(path), "Path %s is not absolute" % path
138   assert os.path.isdir(path), "Path %s is not a dir" % path
139
140   logging.debug("Recursively processing %s", path)
141
142   for root, dirs, files in os.walk(path):
143     for subdir in dirs:
144       EnsurePermission(os.path.join(root, subdir), dir_perm, uid=uid, gid=gid)
145
146     for filename in files:
147       EnsurePermission(os.path.join(root, filename), file_perm, uid=uid,
148                        gid=gid)
149
150
151 def EnsureQueueDir(path, mode, uid, gid):
152   """Sets the correct permissions on all job files in the queue.
153
154   @param path: Directory path
155   @param mode: Wanted file mode
156   @param uid: Wanted user ID
157   @param gid: Wanted group ID
158
159   """
160   for filename in utils.ListVisibleFiles(path):
161     if constants.JOB_FILE_RE.match(filename):
162       EnsurePermission(utils.PathJoin(path, filename), mode, uid=uid, gid=gid)
163
164
165 def ProcessPath(path):
166   """Processes a path component.
167
168   @param path: A tuple of the path component to process
169
170   """
171   (pathname, pathtype, mode, uid, gid) = path[0:5]
172
173   assert pathtype in ALL_TYPES
174
175   if pathtype in (DIR, QUEUE_DIR):
176     # No additional parameters
177     assert len(path[5:]) == 0
178     if pathtype == DIR:
179       EnsureDir(pathname, mode, uid, gid)
180     elif pathtype == QUEUE_DIR:
181       EnsureQueueDir(pathname, mode, uid, gid)
182   elif pathtype == FILE:
183     (must_exist, ) = path[5:]
184     EnsurePermission(pathname, mode, uid=uid, gid=gid, must_exist=must_exist)
185
186
187 def GetPaths():
188   """Returns a tuple of path objects to process.
189
190   """
191   getent = runtime.GetEnts()
192   masterd_log = constants.DAEMONS_LOGFILES[constants.MASTERD]
193   noded_log = constants.DAEMONS_LOGFILES[constants.NODED]
194   confd_log = constants.DAEMONS_LOGFILES[constants.CONFD]
195   rapi_log = constants.DAEMONS_LOGFILES[constants.RAPI]
196
197   rapi_dir = os.path.join(constants.DATA_DIR, "rapi")
198
199   paths = [
200     (constants.DATA_DIR, DIR, 0755, getent.masterd_uid,
201      getent.masterd_gid),
202     (constants.CLUSTER_DOMAIN_SECRET_FILE, FILE, 0640,
203      getent.masterd_uid, getent.masterd_gid, False),
204     (constants.CLUSTER_CONF_FILE, FILE, 0640, getent.masterd_uid,
205      getent.confd_gid, False),
206     (constants.CONFD_HMAC_KEY, FILE, 0440, getent.confd_uid,
207      getent.masterd_gid, False),
208     (constants.SSH_KNOWN_HOSTS_FILE, FILE, 0644, getent.masterd_uid,
209      getent.masterd_gid, False),
210     (constants.RAPI_CERT_FILE, FILE, 0440, getent.rapi_uid,
211      getent.masterd_gid, False),
212     (constants.NODED_CERT_FILE, FILE, 0440, getent.masterd_uid,
213      getent.masterd_gid, False),
214     ]
215
216   ss = ssconf.SimpleStore()
217   for ss_path in ss.GetFileList():
218     paths.append((ss_path, FILE, constants.SS_FILE_PERMS,
219                   getent.noded_uid, 0, False))
220
221   paths.extend([
222     (constants.QUEUE_DIR, DIR, 0700, getent.masterd_uid,
223      getent.masterd_gid),
224     (constants.QUEUE_DIR, QUEUE_DIR, 0600, getent.masterd_uid,
225      getent.masterd_gid),
226     (constants.JOB_QUEUE_LOCK_FILE, FILE, 0600,
227      getent.masterd_uid, getent.masterd_gid, False),
228     (constants.JOB_QUEUE_SERIAL_FILE, FILE, 0600,
229      getent.masterd_uid, getent.masterd_gid, False),
230     (constants.JOB_QUEUE_ARCHIVE_DIR, DIR, 0700,
231      getent.masterd_uid, getent.masterd_gid),
232     (rapi_dir, DIR, 0750, getent.rapi_uid, getent.masterd_gid),
233     (constants.RAPI_USERS_FILE, FILE, 0640, getent.rapi_uid,
234      getent.masterd_gid, False),
235     (constants.RUN_GANETI_DIR, DIR, 0775, getent.masterd_uid,
236      getent.daemons_gid),
237     (constants.SOCKET_DIR, DIR, 0750, getent.masterd_uid,
238      getent.daemons_gid),
239     (constants.MASTER_SOCKET, FILE, 0770, getent.masterd_uid,
240      getent.daemons_gid, False),
241     (constants.BDEV_CACHE_DIR, DIR, 0755, getent.noded_uid,
242      getent.masterd_gid),
243     (constants.UIDPOOL_LOCKDIR, DIR, 0750, getent.noded_uid,
244      getent.masterd_gid),
245     (constants.DISK_LINKS_DIR, DIR, 0755, getent.noded_uid,
246      getent.masterd_gid),
247     (constants.CRYPTO_KEYS_DIR, DIR, 0700, getent.noded_uid,
248      getent.masterd_gid),
249     (constants.IMPORT_EXPORT_DIR, DIR, 0755, getent.noded_uid,
250      getent.masterd_gid),
251     (constants.LOG_DIR, DIR, 0770, getent.masterd_uid,
252      getent.daemons_gid),
253     (masterd_log, FILE, 0600, getent.masterd_uid, getent.masterd_gid,
254      False),
255     (confd_log, FILE, 0600, getent.confd_uid, getent.masterd_gid, False),
256     (noded_log, FILE, 0600, getent.noded_uid, getent.masterd_gid, False),
257     (rapi_log, FILE, 0600, getent.rapi_uid, getent.masterd_gid, False),
258     (constants.LOG_OS_DIR, DIR, 0750, getent.masterd_uid,
259      getent.daemons_gid),
260     ])
261
262   return tuple(paths)
263
264
265 def SetupLogging(opts):
266   """Configures the logging module.
267
268   """
269   formatter = logging.Formatter("%(asctime)s: %(message)s")
270
271   stderr_handler = logging.StreamHandler()
272   stderr_handler.setFormatter(formatter)
273   if opts.debug:
274     stderr_handler.setLevel(logging.NOTSET)
275   elif opts.verbose:
276     stderr_handler.setLevel(logging.INFO)
277   else:
278     stderr_handler.setLevel(logging.WARNING)
279
280   root_logger = logging.getLogger("")
281   root_logger.setLevel(logging.NOTSET)
282   root_logger.addHandler(stderr_handler)
283
284
285 def ParseOptions():
286   """Parses the options passed to the program.
287
288   @return: Options and arguments
289
290   """
291   program = os.path.basename(sys.argv[0])
292
293   parser = optparse.OptionParser(usage="%%prog [--full-run]",
294                                  prog=program)
295   parser.add_option(cli.DEBUG_OPT)
296   parser.add_option(cli.VERBOSE_OPT)
297   parser.add_option("--full-run", "-f", dest="full_run", action="store_true",
298                     default=False, help=("Make a full run and set permissions"
299                                          " on archived jobs (time consuming)"))
300
301   return parser.parse_args()
302
303
304 def Main():
305   """Main routine.
306
307   """
308   (opts, _) = ParseOptions()
309
310   SetupLogging(opts)
311
312   if opts.full_run:
313     logging.info("Running in full mode")
314
315   getent = runtime.GetEnts()
316
317   try:
318     for path in GetPaths():
319       ProcessPath(path)
320
321     if opts.full_run:
322       RecursiveEnsure(constants.JOB_QUEUE_ARCHIVE_DIR, getent.masterd_uid,
323                       getent.masterd_gid, 0700, 0600)
324   except EnsureError, err:
325     logging.error("An error occurred while setting permissions: %s", err)
326     return constants.EXIT_FAILURE
327
328   return constants.EXIT_SUCCESS