Refactoring: Moved check parameters from unsorted.py to dedicated modules (CMK-1393)
[check_mk.git] / cmk_base / packaging.py
blob6dd070953f036ee1542c7cfd97bc0d754e9da07f
1 #!/usr/bin/python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
27 import os
28 import ast
29 import pprint
30 import sys
31 import tarfile
32 import time
33 import subprocess
34 import json
35 from cStringIO import StringIO
36 from typing import NamedTuple
38 import cmk.ec.export
39 import cmk.utils.log
40 import cmk.utils.paths
41 import cmk.utils.tty as tty
42 import cmk.utils.werks
43 import cmk.utils.debug
44 import cmk_base.utils
46 logger = cmk.utils.log.get_logger(__name__)
47 pac_ext = ".mkp"
50 # TODO: Subclass MKGeneralException()?
51 class PackageException(Exception):
52 def __init__(self, reason):
53 self.reason = reason
54 super(PackageException, self).__init__(reason)
56 def __str__(self):
57 return self.reason
60 pac_dir = cmk.utils.paths.omd_root + "/var/check_mk/packages/"
62 # TODO: OMD: Pack this path and remote this makedirs call
63 try:
64 os.makedirs(pac_dir)
65 except:
66 pass
68 # order matters! See function _get_permissions
69 PERM_MAP = (
70 (cmk.utils.paths.checks_dir, 0644),
71 (cmk.utils.paths.local_checks_dir, 0644),
72 (cmk.utils.paths.notifications_dir, 0755),
73 (cmk.utils.paths.local_notifications_dir, 0755),
74 (cmk.utils.paths.inventory_dir, 0644),
75 (cmk.utils.paths.local_inventory_dir, 0644),
76 (cmk.utils.paths.check_manpages_dir, 0644),
77 (cmk.utils.paths.local_check_manpages_dir, 0644),
78 (cmk.utils.paths.agents_dir, 0755),
79 (cmk.utils.paths.local_agents_dir, 0755),
80 (cmk.utils.paths.web_dir, 0644),
81 (cmk.utils.paths.local_web_dir, 0644),
82 (cmk.utils.paths.pnp_templates_dir, 0644),
83 (cmk.utils.paths.local_pnp_templates_dir, 0644),
84 (cmk.utils.paths.doc_dir, 0644),
85 (cmk.utils.paths.local_doc_dir, 0644),
86 (cmk.utils.paths.locale_dir, 0644),
87 (cmk.utils.paths.local_locale_dir, 0644),
88 (cmk.utils.paths.local_bin_dir, 0755),
89 (os.path.join(cmk.utils.paths.local_lib_dir, "nagios", "plugins"), 0755),
90 (cmk.utils.paths.local_lib_dir, 0644),
91 (cmk.utils.paths.local_mib_dir, 0644),
92 (os.path.join(cmk.utils.paths.share_dir, "alert_handlers"), 0755),
93 (os.path.join(cmk.utils.paths.local_share_dir, "alert_handlers"), 0755),
94 (str(cmk.ec.export.mkp_rule_pack_dir()), 0644),
98 def _get_permissions(path):
99 """Determine permissions by the first matching beginning of 'path'"""
100 for path_begin, perm in PERM_MAP:
101 if path.startswith(path_begin):
102 return perm
103 raise PackageException("could not determine permissions for %r" % path)
106 PackagePart = NamedTuple("PackagePart", [
107 ("ident", str),
108 ("title", str),
109 ("path", str),
112 _package_parts = [
113 PackagePart("checks", "Checks", cmk.utils.paths.local_checks_dir),
114 PackagePart("notifications", "Notification scripts", cmk.utils.paths.local_notifications_dir),
115 PackagePart("inventory", "Inventory plugins", cmk.utils.paths.local_inventory_dir),
116 PackagePart("checkman", "Checks' man pages", cmk.utils.paths.local_check_manpages_dir),
117 PackagePart("agents", "Agents", cmk.utils.paths.local_agents_dir),
118 PackagePart("web", "Multisite extensions", cmk.utils.paths.local_web_dir),
119 PackagePart("pnp-templates", "PNP4Nagios templates", cmk.utils.paths.local_pnp_templates_dir),
120 PackagePart("doc", "Documentation files", cmk.utils.paths.local_doc_dir),
121 PackagePart("locales", "Localizations", cmk.utils.paths.local_locale_dir),
122 PackagePart("bin", "Binaries", cmk.utils.paths.local_bin_dir),
123 PackagePart("lib", "Libraries", cmk.utils.paths.local_lib_dir),
124 PackagePart("mibs", "SNMP MIBs", cmk.utils.paths.local_mib_dir),
125 PackagePart("alert_handlers", "Alert handlers",
126 cmk.utils.paths.local_share_dir + "/alert_handlers"),
129 config_parts = [
130 PackagePart("ec_rule_packs", "Event Console rule packs",
131 str(cmk.ec.export.mkp_rule_pack_dir())),
134 package_ignored_files = {
135 "lib": [
136 "nagios/plugins/README.txt",
137 # it's a symlink to the nagios directory. All files would be doubled.
138 # So better ignore this directory to prevent confusions.
139 "icinga/plugins",
144 def get_package_parts():
145 return _package_parts
148 def packaging_usage():
149 sys.stdout.write("""Usage: check_mk [-v] -P|--package COMMAND [ARGS]
151 Available commands are:
152 create NAME ... Collect unpackaged files into new package NAME
153 pack NAME ... Create package file from installed package
154 release NAME ... Drop installed package NAME, release packaged files
155 find ... Find and display unpackaged files
156 list ... List all installed packages
157 list NAME ... List files of installed package
158 list PACK.mkp ... List files of uninstalled package file
159 show NAME ... Show information about installed package
160 show PACK.mkp ... Show information about uninstalled package file
161 install PACK.mkp ... Install or update package from file PACK.mkp
162 remove NAME ... Uninstall package NAME
164 -v enables verbose output
166 Package files are located in %s.
167 """ % pac_dir)
170 def do_packaging(args):
171 if len(args) == 0:
172 packaging_usage()
173 sys.exit(1)
174 command = args[0]
175 args = args[1:]
177 commands = {
178 "create": package_create,
179 "release": package_release,
180 "list": package_list,
181 "find": package_find,
182 "show": package_info,
183 "pack": package_pack,
184 "remove": package_remove,
185 "install": package_install,
187 f = commands.get(command)
188 if f:
189 try:
190 f(args)
191 except PackageException as e:
192 logger.error("%s" % e)
193 sys.exit(1)
194 else:
195 allc = commands.keys()
196 allc.sort()
197 allc = [tty.bold + c + tty.normal for c in allc]
198 logger.error("Invalid packaging command. Allowed are: %s and %s.", ", ".join(allc[:-1]),
199 allc[-1])
200 sys.exit(1)
203 def package_list(args):
204 if len(args) > 0:
205 for name in args:
206 show_package_contents(name)
207 else:
208 if logger.is_verbose():
209 table = []
210 for pacname in all_package_names():
211 package = read_package_info(pacname)
212 table.append((pacname, package["title"], package["num_files"]))
213 tty.print_table(["Name", "Title", "Files"], [tty.bold, "", ""], table)
214 else:
215 for pacname in all_package_names():
216 sys.stdout.write("%s\n" % pacname)
219 def package_info(args):
220 if len(args) == 0:
221 raise PackageException("Usage: check_mk -P show NAME|PACKAGE.mkp")
222 for name in args:
223 show_package_info(name)
226 def show_package_contents(name):
227 show_package(name, False)
230 def show_package_info(name):
231 show_package(name, True)
234 def show_package(name, show_info=False):
235 try:
236 if name.endswith(pac_ext):
237 tar = tarfile.open(name, "r:gz")
238 info = tar.extractfile("info")
239 package = parse_package_info(info.read())
240 else:
241 package = read_package_info(name)
242 if not package:
243 raise PackageException("No such package %s." % name)
244 if show_info:
245 sys.stdout.write("Package file: %s%s\n" % (pac_dir, name))
246 except PackageException:
247 raise
248 except Exception as e:
249 raise PackageException("Cannot open package %s: %s" % (name, e))
251 if show_info:
252 sys.stdout.write("Name: %s\n" % package["name"])
253 sys.stdout.write("Version: %s\n" % package["version"])
254 sys.stdout.write("Packaged on Check_MK Version: %s\n" % package["version.packaged"])
255 sys.stdout.write("Required Check_MK Version: %s\n" % package["version.min_required"])
256 sys.stdout.write("Title: %s\n" % package["title"])
257 sys.stdout.write("Author: %s\n" % package["author"])
258 sys.stdout.write("Download-URL: %s\n" % package["download_url"])
259 sys.stdout.write("Files: %s\n" % \
260 " ".join([ "%s(%d)" % (part, len(fs)) for part, fs in package["files"].items() ]))
261 sys.stdout.write("Description:\n %s\n" % package["description"])
262 else:
263 if logger.is_verbose():
264 sys.stdout.write("Files in package %s:\n" % name)
265 for part in get_package_parts():
266 files = package["files"].get(part.ident, [])
267 if len(files) > 0:
268 sys.stdout.write(" %s%s%s:\n" % (tty.bold, part.title, tty.normal))
269 for f in files:
270 sys.stdout.write(" %s\n" % f)
271 else:
272 for part in get_package_parts():
273 for fn in package["files"].get(part.ident, []):
274 sys.stdout.write(part.path + "/" + fn + "\n")
277 def package_create(args):
278 if len(args) != 1:
279 raise PackageException("Usage: check_mk -P create NAME")
281 pacname = args[0]
282 if read_package_info(pacname):
283 raise PackageException("Package %s already existing." % pacname)
285 logger.verbose("Creating new package %s...", pacname)
286 filelists = {}
287 package = {
288 "title": "Title of %s" % pacname,
289 "name": pacname,
290 "description": "Please add a description here",
291 "version": "1.0",
292 "version.packaged": cmk.__version__,
293 "version.min_required": cmk.__version__,
294 "author": "Add your name here",
295 "download_url": "http://example.com/%s/" % pacname,
296 "files": filelists
298 num_files = 0
299 for part in get_package_parts():
300 files = unpackaged_files_in_dir(part.ident, part.path)
301 filelists[part.ident] = files
302 num_files += len(files)
303 if len(files) > 0:
304 logger.verbose(" %s%s%s:", tty.bold, part.title, tty.normal)
305 for f in files:
306 logger.verbose(" %s", f)
308 write_package_info(package)
309 logger.verbose("New package %s created with %d files.", pacname, num_files)
310 logger.verbose("Please edit package details in %s%s%s", tty.bold, pac_dir + pacname, tty.normal)
313 def package_find(_no_args):
314 first = True
315 for part in get_package_parts() + config_parts:
316 files = unpackaged_files_in_dir(part.ident, part.path)
317 if len(files) > 0:
318 if first:
319 logger.verbose("Unpackaged files:")
320 first = False
322 logger.verbose(" %s%s%s:", tty.bold, part.title, tty.normal)
323 for f in files:
324 if logger.is_verbose():
325 logger.verbose(" %s", f)
326 else:
327 logger.info("%s/%s", part.path, f)
329 if first:
330 logger.verbose("No unpackaged files found.")
333 def release_package(pacname):
334 if not pacname or not package_exists(pacname):
335 raise PackageException("Package %s not installed or corrupt." % pacname)
337 package = read_package_info(pacname)
338 logger.verbose("Releasing files of package %s into freedom...", pacname)
339 for part in get_package_parts() + config_parts:
340 filenames = package["files"].get(part.ident, [])
341 if len(filenames) > 0:
342 logger.verbose(" %s%s%s:", tty.bold, part.title, tty.normal)
343 for f in filenames:
344 logger.verbose(" %s", f)
345 if part.ident == 'ec_rule_packs':
346 cmk.ec.export.release_packaged_rule_packs(filenames)
347 remove_package_info(pacname)
350 def package_release(args):
351 if len(args) != 1:
352 raise PackageException("Usage: check_mk -P release NAME")
353 pacname = args[0]
354 release_package(pacname)
357 def package_exists(pacname):
358 pacpath = pac_dir + pacname
359 return os.path.exists(pacpath)
362 def package_pack(args):
363 if len(args) != 1:
364 raise PackageException("Usage: check_mk -P pack NAME")
366 # Make sure, user is not in data directories of Check_MK
367 abs_curdir = os.path.abspath(os.curdir)
368 for directory in [cmk.utils.paths.var_dir
369 ] + [p.path for p in get_package_parts() + config_parts]:
370 if abs_curdir == directory or abs_curdir.startswith(directory + "/"):
371 raise PackageException(
372 "You are in %s!\n"
373 "Please leave the directories of Check_MK before creating\n"
374 "a packet file. Foreign files lying around here will mix up things." % p)
376 pacname = args[0]
377 package = read_package_info(pacname)
378 if not package:
379 raise PackageException("Package %s not existing or corrupt." % pacname)
380 tarfilename = "%s-%s%s" % (pacname, package["version"], pac_ext)
381 logger.verbose("Packing %s into %s...", pacname, tarfilename)
382 create_mkp_file(package, file_name=tarfilename)
383 logger.verbose("Successfully created %s", tarfilename)
386 def create_mkp_file(package, file_name=None, file_object=None):
387 package["version.packaged"] = cmk.__version__
389 def create_tar_info(filename, size):
390 info = tarfile.TarInfo()
391 info.mtime = time.time()
392 info.uid = 0
393 info.gid = 0
394 info.size = size
395 info.mode = 0644
396 info.type = tarfile.REGTYPE
397 info.name = filename
398 return info
400 tar = tarfile.open(name=file_name, fileobj=file_object, mode="w:gz")
402 # add the regular info file (Python format)
403 info_file = StringIO(pprint.pformat(package))
404 info = create_tar_info("info", len(info_file.getvalue()))
405 tar.addfile(info, info_file)
407 # add the info file a second time (JSON format) for external tools
408 info_file = StringIO(json.dumps(package))
409 info = create_tar_info("info.json", len(info_file.getvalue()))
410 tar.addfile(info, info_file)
412 # Now pack the actual files into sub tars
413 for part in get_package_parts() + config_parts:
414 filenames = package["files"].get(part.ident, [])
415 if len(filenames) > 0:
416 logger.verbose(" %s%s%s:", tty.bold, part.title, tty.normal)
417 for f in filenames:
418 logger.verbose(" %s", f)
419 subtarname = part.ident + ".tar"
420 subdata = subprocess.check_output(
421 ["tar", "cf", "-", "--dereference", "--force-local", "-C", part.path] + filenames)
422 info = create_tar_info(subtarname, len(subdata))
423 tar.addfile(info, StringIO(subdata))
424 tar.close()
427 def package_remove(args):
428 if len(args) != 1:
429 raise PackageException("Usage: check_mk -P remove NAME")
430 pacname = args[0]
431 package = read_package_info(pacname)
432 if not package:
433 raise PackageException("No such package %s." % pacname)
435 logger.verbose("Removing package %s...", pacname)
436 remove_package(package)
437 logger.verbose("Successfully removed package %s.", pacname)
440 def remove_package(package):
441 for part in get_package_parts() + config_parts:
442 filenames = package["files"].get(part.ident, [])
443 if len(filenames) > 0:
444 logger.verbose(" %s%s%s", tty.bold, part.title, tty.normal)
445 for fn in filenames:
446 logger.verbose(" %s", fn)
447 try:
448 path = part.path + "/" + fn
449 if part.ident == 'ec_rule_packs':
450 cmk.ec.export.remove_packaged_rule_packs(filenames)
451 else:
452 os.remove(path)
453 except Exception as e:
454 if cmk.utils.debug.enabled():
455 raise
456 raise Exception("Cannot remove %s: %s\n" % (path, e))
458 os.remove(pac_dir + package["name"])
461 def create_package(pkg_info):
462 pacname = pkg_info["name"]
463 if package_exists(pacname):
464 raise PackageException("Packet already exists.")
466 validate_package_files(pacname, pkg_info["files"])
467 write_package_info(pkg_info)
470 def edit_package(pacname, new_package_info):
471 if not package_exists(pacname):
472 raise PackageException("No such package")
474 # Renaming: check for collision
475 if pacname != new_package_info["name"]:
476 if package_exists(new_package_info["name"]):
477 raise PackageException(
478 "Cannot rename package: a package with that name already exists.")
480 validate_package_files(pacname, new_package_info["files"])
482 remove_package_info(pacname)
483 write_package_info(new_package_info)
486 # Packaged files must either be unpackaged or already
487 # belong to that package
488 def validate_package_files(pacname, files):
489 packages = {}
490 for package_name in all_package_names():
491 packages[package_name] = read_package_info(package_name)
493 for part in get_package_parts():
494 validate_package_files_part(packages, pacname, part.ident, part.path,
495 files.get(part.ident, []))
498 def validate_package_files_part(packages, pacname, part, directory, rel_paths):
499 for rel_path in rel_paths:
500 path = os.path.join(directory, rel_path)
501 if not os.path.exists(path):
502 raise PackageException("File %s does not exist." % path)
504 for other_pacname, other_package_info in packages.items():
505 for other_rel_path in other_package_info["files"].get(part, []):
506 if other_rel_path == rel_path and other_pacname != pacname:
507 raise PackageException(
508 "File %s does already belong to package %s" % (path, other_pacname))
511 def package_install(args):
512 if len(args) != 1:
513 raise PackageException("Usage: check_mk -P install NAME")
514 path = args[0]
515 if not os.path.exists(path):
516 raise PackageException("No such file %s." % path)
518 return install_package(file_name=path)
521 def install_package(file_name=None, file_object=None):
522 tar = tarfile.open(name=file_name, fileobj=file_object, mode="r:gz")
523 package = parse_package_info(tar.extractfile("info").read())
525 verify_check_mk_version(package)
527 pacname = package["name"]
528 old_package = read_package_info(pacname)
529 if old_package:
530 logger.verbose("Updating %s from version %s to %s.", pacname, old_package["version"],
531 package["version"])
532 update = True
533 else:
534 logger.verbose("Installing %s version %s.", pacname, package["version"])
535 update = False
537 # Before installing check for conflicts
538 keep_files = {}
539 for part in get_package_parts() + config_parts:
540 packaged = packaged_files_in_dir(part.ident)
541 keep = []
542 keep_files[part.ident] = keep
544 if update:
545 old_files = old_package["files"].get(part.ident, [])
547 for fn in package["files"].get(part.ident, []):
548 path = os.path.join(part.path, fn)
549 if update and fn in old_files:
550 keep.append(fn)
551 elif fn in packaged:
552 raise PackageException("File conflict: %s is part of another package." % path)
553 elif os.path.exists(path):
554 raise PackageException("File conflict: %s already existing." % path)
556 # Now install files, but only unpack files explicitely listed
557 for part in get_package_parts() + config_parts:
558 filenames = package["files"].get(part.ident, [])
559 if len(filenames) > 0:
560 logger.verbose(" %s%s%s:", tty.bold, part.title, tty.normal)
561 for fn in filenames:
562 logger.verbose(" %s", fn)
564 # make sure target directory exists
565 if not os.path.exists(part.path):
566 logger.verbose(" Creating directory %s", part.path)
567 os.makedirs(part.path)
569 tarsource = tar.extractfile(part.ident + ".tar")
571 tardest = subprocess.Popen(
572 ["tar", "xf", "-", "-C", part.path] + filenames, stdin=subprocess.PIPE)
573 while True:
574 data = tarsource.read(4096)
575 if not data:
576 break
577 tardest.stdin.write(data)
579 tardest.stdin.close()
580 tardest.wait()
582 # Fix permissions of extracted files
583 for filename in filenames:
584 path = os.path.join(part.path, filename)
585 desired_perm = _get_permissions(path)
586 has_perm = os.stat(path).st_mode & 07777
587 if has_perm != desired_perm:
588 logger.verbose(" Fixing permissions of %s: %04o -> %04o", path, has_perm,
589 desired_perm)
590 os.chmod(path, desired_perm)
592 if part.ident == 'ec_rule_packs':
593 cmk.ec.export.add_rule_pack_proxies(filenames)
595 # In case of an update remove files from old_package not present in new one
596 if update:
597 for part in get_package_parts() + config_parts:
598 filenames = old_package["files"].get(part.ident, [])
599 keep = keep_files.get(part.ident, [])
600 for fn in filenames:
601 if fn not in keep:
602 path = os.path.join(part.path, fn)
603 logger.verbose("Removing outdated file %s.", path)
604 try:
605 os.remove(path)
606 except Exception as e:
607 logger.error("Error removing %s: %s", path, e)
609 if part.ident == 'ec_rule_packs':
610 to_remove = [fn for fn in filenames if fn not in keep]
611 cmk.ec.export.remove_packaged_rule_packs(to_remove, delete_export=False)
613 # Last but not least install package file
614 write_package_info(package)
615 return package
618 # Checks whether or not the minimum required Check_MK version is older than the
619 # current Check_MK version. Raises an exception if not. When the Check_MK version
620 # can not be parsed or is a daily build, the check is simply passing without error.
621 def verify_check_mk_version(package):
622 min_version = package["version.min_required"]
623 cmk_version = cmk.__version__
625 if cmk_base.utils.is_daily_build_version(min_version):
626 min_branch = cmk_base.utils.branch_of_daily_build(min_version)
627 if min_branch == "master":
628 return # can not check exact version
629 else:
630 # use the branch name (e.g. 1.2.8 as min version)
631 min_version = min_branch
633 if cmk_base.utils.is_daily_build_version(cmk_version):
634 branch = cmk_base.utils.branch_of_daily_build(cmk_version)
635 if branch == "master":
636 return # can not check exact version
637 else:
638 # use the branch name (e.g. 1.2.8 as min version)
639 cmk_version = branch
641 compatible = True
642 try:
643 compatible = cmk.utils.werks.parse_check_mk_version(min_version) \
644 <= cmk.utils.werks.parse_check_mk_version(cmk_version)
645 except:
646 # Be compatible: When a version can not be parsed, then skip this check
647 if cmk.utils.debug.enabled():
648 raise
649 return
651 if not compatible:
652 raise PackageException("The package requires Check_MK version %s, "
653 "but you have %s installed." % (min_version, cmk_version))
656 def files_in_dir(part, directory, prefix=""):
657 if directory is None or not os.path.exists(directory):
658 return []
660 # Handle case where one part-directory lies below another
661 taboo_dirs = [p.path for p in get_package_parts() + config_parts if p.ident != part]
662 if directory in taboo_dirs:
663 return []
665 result = []
666 files = os.listdir(directory)
667 for f in files:
668 if f in ['.', '..'] or f.startswith('.') or f.endswith('~') or f.endswith(".pyc"):
669 continue
671 ignored = package_ignored_files.get(part, [])
672 if prefix + f in ignored:
673 continue
675 path = directory + "/" + f
676 if os.path.isdir(path):
677 result += files_in_dir(part, path, prefix + f + "/")
678 else:
679 result.append(prefix + f)
680 result.sort()
681 return result
684 def unpackaged_files():
685 unpackaged = {}
686 for part in get_package_parts() + config_parts:
687 unpackaged[part.ident] = unpackaged_files_in_dir(part.ident, part.path)
688 return unpackaged
691 def package_part_info():
692 part_info = {}
693 for part in get_package_parts() + config_parts:
694 try:
695 files = os.listdir(part.path)
696 except OSError:
697 files = []
699 part_info[part.ident] = {
700 "title": part.title,
701 "permissions": map(_get_permissions, [os.path.join(part.path, f) for f in files]),
702 "path": part.path,
703 "files": files,
706 return part_info
709 def unpackaged_files_in_dir(part, directory):
710 return [f for f in files_in_dir(part, directory) if f not in packaged_files_in_dir(part)]
713 def packaged_files_in_dir(part):
714 result = []
715 for pacname in all_package_names():
716 package = read_package_info(pacname)
717 if package:
718 result += package["files"].get(part, [])
719 return result
722 def read_package_info(pacname):
723 try:
724 package = parse_package_info(file(pac_dir + pacname).read())
725 package["name"] = pacname # do not trust package content
726 num_files = sum([len(fl) for fl in package["files"].values()])
727 package["num_files"] = num_files
728 return package
729 except IOError:
730 return None
731 except Exception:
732 logger.verbose("Ignoring invalid package file '%s%s'. Please remove it from %s!", pac_dir,
733 pacname, pac_dir)
734 return None
737 def write_package_info(package):
738 file(pac_dir + package["name"], "w").write(pprint.pformat(package) + "\n")
741 def remove_package_info(pacname):
742 os.remove(pac_dir + pacname)
745 def all_package_names():
746 return sorted([p for p in os.listdir(pac_dir) if p not in ['.', '..']])
749 def parse_package_info(python_string):
750 return ast.literal_eval(python_string)