summaryrefslogtreecommitdiffstats
path: root/build/lib/Bcfg2/Server/Plugins/Packages.py
diff options
context:
space:
mode:
Diffstat (limited to 'build/lib/Bcfg2/Server/Plugins/Packages.py')
-rw-r--r--build/lib/Bcfg2/Server/Plugins/Packages.py869
1 files changed, 869 insertions, 0 deletions
diff --git a/build/lib/Bcfg2/Server/Plugins/Packages.py b/build/lib/Bcfg2/Server/Plugins/Packages.py
new file mode 100644
index 000000000..194330723
--- /dev/null
+++ b/build/lib/Bcfg2/Server/Plugins/Packages.py
@@ -0,0 +1,869 @@
+import cPickle
+import copy
+import gzip
+import tarfile
+import glob
+import logging
+import lxml.etree
+import os
+import re
+import sys
+import urllib2
+
+# FIXME: Remove when server python dep is 2.5 or greater
+if sys.version_info >= (2, 5):
+ from hashlib import md5
+else:
+ from md5 import md5
+
+import Bcfg2.Logger
+import Bcfg2.Server.Plugin
+
+# build sources.list?
+# caching for yum
+
+class NoData(Exception):
+ pass
+
+class SomeData(Exception):
+ pass
+
+logger = logging.getLogger('Packages')
+
+def source_from_xml(xsource):
+ ret = dict([('rawurl', False), ('url', False)])
+ for key, tag in [('groups', 'Group'), ('components', 'Component'),
+ ('arches', 'Arch'), ('blacklist', 'Blacklist'),
+ ('whitelist', 'Whitelist')]:
+ ret[key] = [item.text for item in xsource.findall(tag)]
+ # version and component need to both contain data for sources to work
+ try:
+ ret['version'] = xsource.find('Version').text
+ except:
+ ret['version'] = 'placeholder'
+ if ret['components'] == []:
+ ret['components'] = ['placeholder']
+ try:
+ if xsource.find('Recommended').text in ['True', 'true']:
+ ret['recommended'] = True
+ else:
+ ret['recommended'] = False
+ except:
+ ret['recommended'] = False
+ if xsource.find('RawURL') is not None:
+ ret['rawurl'] = xsource.find('RawURL').text
+ if not ret['rawurl'].endswith('/'):
+ ret['rawurl'] += '/'
+ else:
+ ret['url'] = xsource.find('URL').text
+ if not ret['url'].endswith('/'):
+ ret['url'] += '/'
+ return ret
+
+def _fetch_url(url):
+ if '@' in url:
+ mobj = re.match('(\w+://)([^:]+):([^@]+)@(.*)$', url)
+ if not mobj:
+ raise ValueError
+ user = mobj.group(2)
+ passwd = mobj.group(3)
+ url = mobj.group(1) + mobj.group(4)
+ auth = urllib2.HTTPBasicAuthHandler(urllib2.HTTPPasswordMgrWithDefaultRealm())
+ auth.add_password(None, url, user, passwd)
+ urllib2.install_opener(urllib2.build_opener(auth))
+ return urllib2.urlopen(url).read()
+
+class Source(object):
+ basegroups = []
+
+ def __init__(self, basepath, url, version, arches, components, groups, rawurl,
+ blacklist, whitelist, recommended):
+ self.basepath = basepath
+ self.version = version
+ self.components = components
+ self.url = url
+ self.rawurl = rawurl
+ self.groups = groups
+ self.arches = arches
+ self.deps = dict()
+ self.provides = dict()
+ self.blacklist = set(blacklist)
+ self.whitelist = set(whitelist)
+ self.cachefile = '%s/cache-%s' % (self.basepath, md5(cPickle.dumps( \
+ [self.version, self.components, self.url, \
+ self.rawurl, self.groups, self.arches])).hexdigest())
+ self.recommended = recommended
+ self.url_map = []
+
+ def load_state(self):
+ pass
+
+ def setup_data(self, force_update=False):
+ should_read = True
+ should_download = False
+ if os.path.exists(self.cachefile):
+ try:
+ self.load_state()
+ should_read = False
+ except:
+ logger.error("Cachefile %s load failed; falling back to file read"\
+ % (self.cachefile))
+ if should_read:
+ try:
+ self.read_files()
+ except:
+ logger.error("Packages: File read failed; falling back to file download")
+ should_download = True
+
+ if should_download or force_update:
+ try:
+ self.update()
+ self.read_files()
+ except:
+ logger.error("Failed to update source", exc_info=1)
+
+ def get_urls(self):
+ return []
+ urls = property(get_urls)
+
+ def get_files(self):
+ return [self.escape_url(url) for url in self.urls]
+ files = property(get_files)
+
+ def get_vpkgs(self, meta):
+ agroups = ['global'] + [a for a in self.arches if a in meta.groups]
+ vdict = dict()
+ for agrp in agroups:
+ for key, value in self.provides[agrp].iteritems():
+ if key not in vdict:
+ vdict[key] = set(value)
+ else:
+ vdict[key].update(value)
+ return vdict
+
+ def escape_url(self, url):
+ return "%s/%s" % (self.basepath, url.replace('/', '@'))
+
+ def file_init(self):
+ pass
+
+ def read_files(self):
+ pass
+
+ def update(self):
+ for url in self.urls:
+ logger.info("Packages: Updating %s" % url)
+ fname = self.escape_url(url)
+ try:
+ data = _fetch_url(url)
+ except ValueError:
+ logger.error("Packages: Bad url string %s" % url)
+ continue
+ except urllib2.HTTPError, h:
+ logger.error("Packages: Failed to fetch url %s. code=%s" \
+ % (url, h.code))
+ continue
+ file(fname, 'w').write(data)
+
+ def applies(self, metadata):
+ return len([g for g in self.basegroups if g in metadata.groups]) != 0 and \
+ len([g for g in metadata.groups if g in self.groups]) \
+ == len(self.groups)
+
+ def get_arches(self, metadata):
+ return ['global'] + [a for a in self.arches if a in metadata.groups]
+
+ def get_deps(self, metadata, pkgname):
+ for arch in self.get_arches(metadata):
+ if pkgname in self.deps[arch]:
+ return self.deps[arch][pkgname]
+ raise NoData
+
+ def get_provides(self, metadata, required):
+ for arch in self.get_arches(metadata):
+ if required in self.provides[arch]:
+ return self.provides[arch][required]
+ raise NoData
+
+ def is_package(self, metadata, _):
+ return False
+
+ def get_url_info(self):
+ return {'groups': copy.copy(self.groups), \
+ 'urls': [copy.deepcopy(url) for url in self.url_map]}
+
+class YUMSource(Source):
+ xp = '{http://linux.duke.edu/metadata/common}'
+ rp = '{http://linux.duke.edu/metadata/rpm}'
+ rpo = '{http://linux.duke.edu/metadata/repo}'
+ fl = '{http://linux.duke.edu/metadata/filelists}'
+ basegroups = ['yum', 'redhat', 'centos', 'fedora']
+ ptype = 'yum'
+
+ def __init__(self, basepath, url, version, arches, components, groups,
+ rawurl, blacklist, whitelist, recommended):
+ Source.__init__(self, basepath, url, version, arches, components,
+ groups, rawurl, blacklist, whitelist, recommended)
+ if not self.rawurl:
+ self.baseurl = self.url + '%(version)s/%(component)s/%(arch)s/'
+ else:
+ self.baseurl = self.rawurl
+ self.packages = dict()
+ self.deps = dict([('global', dict())])
+ self.provides = dict([('global', dict())])
+ self.filemap = dict([(x, dict()) for x in ['global'] + self.arches])
+ self.needed_paths = set()
+ self.file_to_arch = dict()
+
+ def save_state(self):
+ cache = file(self.cachefile, 'wb')
+ cPickle.dump((self.packages, self.deps, self.provides,
+ self.filemap, self.url_map), cache, 2)
+ cache.close()
+
+ def load_state(self):
+ data = file(self.cachefile)
+ (self.packages, self.deps, self.provides, \
+ self.filemap, self.url_map) = cPickle.load(data)
+
+ def get_urls(self):
+ surls = list()
+ self.url_map = []
+ for arch in self.arches:
+ usettings = [{'version': self.version, 'component':comp,
+ 'arch':arch} for comp in self.components]
+ for setting in usettings:
+ setting['groups'] = self.groups
+ setting['url'] = self.baseurl % setting
+ self.url_map.append(copy.deepcopy(setting))
+ surls.append((arch, [setting['url'] for setting in usettings]))
+ urls = []
+ for (sarch, surl_list) in surls:
+ for surl in surl_list:
+ if not surl.endswith('/'):
+ surl += '/'
+ rmdurl = surl + 'repodata/repomd.xml'
+ try:
+ repomd = _fetch_url(rmdurl)
+ xdata = lxml.etree.XML(repomd)
+ except ValueError:
+ logger.error("Packages: Bad url string %s" % rmdurl)
+ continue
+ except urllib2.HTTPError, h:
+ logger.error("Packages: Failed to fetch url %s. code=%s" \
+ % (rmdurl, h.code))
+ continue
+ except:
+ logger.error("Failed to process url %s" % rmdurl)
+ continue
+ for elt in xdata.findall(self.rpo + 'data'):
+ if elt.get('type') not in ['filelists', 'primary']:
+ continue
+ floc = elt.find(self.rpo + 'location')
+ fullurl = surl + floc.get('href')
+ urls.append(fullurl)
+ self.file_to_arch[self.escape_url(fullurl)] = sarch
+ return urls
+ urls = property(get_urls)
+
+ def read_files(self):
+ for fname in [f for f in self.files if f.endswith('primary.xml.gz')]:
+ farch = self.file_to_arch[fname]
+ fdata = lxml.etree.parse(fname).getroot()
+ self.parse_primary(fdata, farch)
+ for fname in [f for f in self.files if f.endswith('filelists.xml.gz')]:
+ farch = self.file_to_arch[fname]
+ fdata = lxml.etree.parse(fname).getroot()
+ self.parse_filelist(fdata, farch)
+ # merge data
+ sdata = self.packages.values()
+ self.packages['global'] = copy.deepcopy(sdata.pop())
+ while sdata:
+ self.packages['global'].intersection(sdata.pop())
+
+ for key in self.packages:
+ if key == 'global':
+ continue
+ self.packages[key] = self.packages['global'].difference(self.packages[key])
+ self.save_state()
+
+ def parse_filelist(self, data, arch):
+ if arch not in self.filemap:
+ self.filemap[arch] = dict()
+ for pkg in data.findall(self.fl + 'package'):
+ for fentry in [fe for fe in pkg.findall(self.fl + 'file') \
+ if fe.text in self.needed_paths]:
+ if fentry.text in self.filemap[arch]:
+ self.filemap[arch][fentry.text].add(pkg.get('name'))
+ else:
+ self.filemap[arch][fentry.text] = set([pkg.get('name')])
+
+ def parse_primary(self, data, arch):
+ if arch not in self.packages:
+ self.packages[arch] = set()
+ if arch not in self.deps:
+ self.deps[arch] = dict()
+ if arch not in self.provides:
+ self.provides[arch] = dict()
+ for pkg in data.getchildren():
+ if not pkg.tag.endswith('package'):
+ continue
+ pkgname = pkg.find(self.xp + 'name').text
+ self.packages[arch].add(pkgname)
+
+ pdata = pkg.find(self.xp + 'format')
+ pre = pdata.find(self.rp + 'requires')
+ self.deps[arch][pkgname] = set()
+ for entry in pre.getchildren():
+ self.deps[arch][pkgname].add(entry.get('name'))
+ if entry.get('name').startswith('/'):
+ self.needed_paths.add(entry.get('name'))
+ pro = pdata.find(self.rp + 'provides')
+ if pro != None:
+ for entry in pro.getchildren():
+ prov = entry.get('name')
+ if prov not in self.provides[arch]:
+ self.provides[arch][prov] = list()
+ self.provides[arch][prov].append(pkgname)
+
+ def is_package(self, metadata, item):
+ arch = [a for a in self.arches if a in metadata.groups]
+ if not arch:
+ return False
+ return (item in self.packages['global'] or item in self.packages[arch[0]]) and \
+ item not in self.blacklist and \
+ ((len(self.whitelist) == 0) or item in self.whitelist)
+
+ def get_vpkgs(self, metadata):
+ rv = Source.get_vpkgs(self, metadata)
+ for arch, fmdata in self.filemap.iteritems():
+ if arch not in metadata.groups and arch != 'global':
+ continue
+ for filename, pkgs in fmdata.iteritems():
+ rv[filename] = pkgs
+ return rv
+
+ def filter_unknown(self, unknown):
+ filtered = set([u for u in unknown if u.startswith('rpmlib')])
+ unknown.difference_update(filtered)
+
+class APTSource(Source):
+ basegroups = ['apt', 'debian', 'ubuntu', 'nexenta']
+ ptype = 'deb'
+
+ def __init__(self, basepath, url, version, arches, components, groups,
+ rawurl, blacklist, whitelist, recommended):
+ Source.__init__(self, basepath, url, version, arches, components, groups,
+ rawurl, blacklist, whitelist, recommended)
+ self.pkgnames = set()
+
+ self.url_map = [{'rawurl': self.rawurl, 'url': self.url, 'version': self.version, \
+ 'components': self.components, 'arches': self.arches, 'groups': self.groups}]
+
+ def save_state(self):
+ cache = file(self.cachefile, 'wb')
+ cPickle.dump((self.pkgnames, self.deps, self.provides),
+ cache, 2)
+ cache.close()
+
+ def load_state(self):
+ data = file(self.cachefile)
+ self.pkgnames, self.deps, self.provides = cPickle.load(data)
+
+ def filter_unknown(self, unknown):
+ filtered = set([u for u in unknown if u.startswith('choice')])
+ unknown.difference_update(filtered)
+
+ def get_urls(self):
+ if not self.rawurl:
+ return ["%sdists/%s/%s/binary-%s/Packages.gz" % \
+ (self.url, self.version, part, arch) for part in self.components \
+ for arch in self.arches]
+ else:
+ return ["%sPackages.gz" % (self.rawurl)]
+ urls = property(get_urls)
+
+ def read_files(self):
+ bdeps = dict()
+ bprov = dict()
+ if self.recommended:
+ depfnames = ['Depends', 'Pre-Depends', 'Recommends']
+ else:
+ depfnames = ['Depends', 'Pre-Depends']
+ for fname in self.files:
+ if not self.rawurl:
+ barch = [x for x in fname.split('@') if x.startswith('binary-')][0][7:]
+ else:
+ # RawURL entries assume that they only have one <Arch></Arch>
+ # element and that it is the architecture of the source.
+ barch = self.arches[0]
+ if barch not in bdeps:
+ bdeps[barch] = dict()
+ bprov[barch] = dict()
+ try:
+ reader = gzip.GzipFile(fname)
+ except:
+ print("Failed to read file %s" % fname)
+ raise
+ for line in reader.readlines():
+ words = line.strip().split(':', 1)
+ if words[0] == 'Package':
+ pkgname = words[1].strip().rstrip()
+ self.pkgnames.add(pkgname)
+ bdeps[barch][pkgname] = []
+ elif words[0] in depfnames:
+ vindex = 0
+ for dep in words[1].split(','):
+ if '|' in dep:
+ cdeps = [re.sub('\s+', '', re.sub('\(.*\)', '', cdep)) for cdep in dep.split('|')]
+ dyn_dname = "choice-%s-%s-%s" % (pkgname, barch, vindex)
+ vindex += 1
+ bdeps[barch][pkgname].append(dyn_dname)
+ bprov[barch][dyn_dname] = set(cdeps)
+ else:
+ raw_dep = re.sub('\(.*\)', '', dep)
+ raw_dep = raw_dep.rstrip().strip()
+ bdeps[barch][pkgname].append(raw_dep)
+ elif words[0] == 'Provides':
+ for pkg in words[1].split(','):
+ dname = pkg.rstrip().strip()
+ if dname not in bprov[barch]:
+ bprov[barch][dname] = set()
+ bprov[barch][dname].add(pkgname)
+
+ self.deps['global'] = dict()
+ self.provides['global'] = dict()
+ for barch in bdeps:
+ self.deps[barch] = dict()
+ self.provides[barch] = dict()
+ for pkgname in self.pkgnames:
+ pset = set()
+ for barch in bdeps:
+ if pkgname not in bdeps[barch]:
+ bdeps[barch][pkgname] = []
+ pset.add(tuple(bdeps[barch][pkgname]))
+ if len(pset) == 1:
+ self.deps['global'][pkgname] = pset.pop()
+ else:
+ for barch in bdeps:
+ self.deps[barch][pkgname] = bdeps[barch][pkgname]
+ provided = set()
+ for bprovided in bprov.values():
+ provided.update(set(bprovided))
+ for prov in provided:
+ prset = set()
+ for barch in bprov:
+ if prov not in bprov[barch]:
+ continue
+ prset.add(tuple(bprov[barch].get(prov, ())))
+ if len(prset) == 1:
+ self.provides['global'][prov] = prset.pop()
+ else:
+ for barch in bprov:
+ self.provides[barch][prov] = bprov[barch].get(prov, ())
+ self.save_state()
+
+ def is_package(self, _, pkg):
+ return pkg in self.pkgnames and \
+ pkg not in self.blacklist and \
+ (len(self.whitelist) == 0 or pkg in self.whitelist)
+
+class PACSource(Source):
+ basegroups = ['arch', 'parabola']
+ ptype = 'pacman'
+
+ def __init__(self, basepath, url, version, arches, components, groups,
+ rawurl, blacklist, whitelist, recommended):
+ Source.__init__(self, basepath, url, version, arches, components, groups,
+ rawurl, blacklist, whitelist, recommended)
+ self.pkgnames = set()
+
+ self.url_map = [{'rawurl': self.rawurl, 'url': self.url, 'version': self.version, \
+ 'components': self.components, 'arches': self.arches, 'groups': self.groups}]
+
+ def save_state(self):
+ cache = file(self.cachefile, 'wb')
+ cPickle.dump((self.pkgnames, self.deps, self.provides),
+ cache, 2)
+ cache.close()
+
+ def load_state(self):
+ data = file(self.cachefile)
+ self.pkgnames, self.deps, self.provides = cPickle.load(data)
+
+ def filter_unknown(self, unknown):
+ filtered = set([u for u in unknown if u.startswith('choice')])
+ unknown.difference_update(filtered)
+
+ def get_urls(self):
+ if not self.rawurl:
+ return ["%s/%s/os/%s/%s.db.tar.gz" % \
+ (self.url, part, arch, part) for part in self.components \
+ for arch in self.arches]
+ else:
+ raise Exception("PACSource : RAWUrl not supported (yet)")
+ urls = property(get_urls)
+
+
+ def read_files(self):
+ bdeps = dict()
+ bprov = dict()
+
+ if self.recommended:
+ depfnames = ['Depends', 'Pre-Depends', 'Recommends']
+ else:
+ depfnames = ['Depends', 'Pre-Depends']
+
+ for fname in self.files:
+ if not self.rawurl:
+ barch = [x for x in fname.split('@') if x in self.arches][0]
+ else:
+ # RawURL entries assume that they only have one <Arch></Arch>
+ # element and that it is the architecture of the source.
+ barch = self.arches[0]
+
+ if barch not in bdeps:
+ bdeps[barch] = dict()
+ bprov[barch] = dict()
+ try:
+ print "try to read : " + fname
+ tar = tarfile.open(fname, "r")
+ reader = gzip.GzipFile(fname)
+ except:
+ print("Failed to read file %s" % fname)
+ raise
+
+ for tarinfo in tar:
+ if tarinfo.isdir():
+ self.pkgnames.add(tarinfo.name.rsplit("-",2)[0])
+ print "added : " + tarinfo.name.rsplit("-",2)[0]
+ tar.close()
+
+ self.deps['global'] = dict()
+ self.provides['global'] = dict()
+ for barch in bdeps:
+ self.deps[barch] = dict()
+ self.provides[barch] = dict()
+ for pkgname in self.pkgnames:
+ pset = set()
+ for barch in bdeps:
+ if pkgname not in bdeps[barch]:
+ bdeps[barch][pkgname] = []
+ pset.add(tuple(bdeps[barch][pkgname]))
+ if len(pset) == 1:
+ self.deps['global'][pkgname] = pset.pop()
+ else:
+ for barch in bdeps:
+ self.deps[barch][pkgname] = bdeps[barch][pkgname]
+ provided = set()
+ for bprovided in bprov.values():
+ provided.update(set(bprovided))
+ for prov in provided:
+ prset = set()
+ for barch in bprov:
+ if prov not in bprov[barch]:
+ continue
+ prset.add(tuple(bprov[barch].get(prov, ())))
+ if len(prset) == 1:
+ self.provides['global'][prov] = prset.pop()
+ else:
+ for barch in bprov:
+ self.provides[barch][prov] = bprov[barch].get(prov, ())
+ self.save_state()
+
+ def is_package(self, _, pkg):
+ return pkg in self.pkgnames and \
+ pkg not in self.blacklist and \
+ (len(self.whitelist) == 0 or pkg in self.whitelist)
+
+class Packages(Bcfg2.Server.Plugin.Plugin,
+ Bcfg2.Server.Plugin.StructureValidator,
+ Bcfg2.Server.Plugin.Generator,
+ Bcfg2.Server.Plugin.Connector):
+ name = 'Packages'
+ conflicts = ['Pkgmgr']
+ experimental = True
+ __rmi__ = Bcfg2.Server.Plugin.Plugin.__rmi__ + ['Refresh', 'Reload']
+
+ def __init__(self, core, datastore):
+ Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
+ Bcfg2.Server.Plugin.StructureValidator.__init__(self)
+ Bcfg2.Server.Plugin.Generator.__init__(self)
+ Bcfg2.Server.Plugin.Connector.__init__(self)
+ self.cachepath = self.data + '/cache'
+ self.sentinels = set()
+ self.sources = []
+ self.disableResolver = False
+ self.disableMetaData = False
+ self.virt_pkgs = dict()
+
+ if not os.path.exists(self.cachepath):
+ # create cache directory if needed
+ os.makedirs(self.cachepath)
+ self._load_config()
+
+ def get_relevant_groups(self, meta):
+ mgrps = list(set([g for g in meta.groups for s in self.get_matching_sources(meta) \
+ if g in s.basegroups or g in s.groups or g in s.arches]))
+ mgrps.sort()
+ return tuple(mgrps)
+
+ def build_vpkgs_entry(self, meta):
+ # build single entry for all matching sources
+ mgrps = self.get_relevant_groups(meta)
+ vpkgs = dict()
+ for source in self.get_matching_sources(meta):
+ s_vpkgs = source.get_vpkgs(meta)
+ for name, prov_set in s_vpkgs.iteritems():
+ if name not in vpkgs:
+ vpkgs[name] = set(prov_set)
+ else:
+ vpkgs[name].update(prov_set)
+ return vpkgs
+
+ def get_matching_sources(self, meta):
+ return [s for s in self.sources if s.applies(meta)]
+
+ def HandlesEntry(self, entry, metadata):
+ if [x for x in metadata.groups if x in self.sentinels] \
+ and entry.tag == 'Package':
+ return True
+ return False
+
+ def HandleEntry(self, entry, metadata):
+ entry.set('version', 'auto')
+ for source in self.sources:
+ if [x for x in metadata.groups if x in source.basegroups]:
+ entry.set('type', source.ptype)
+
+ def complete(self, meta, input_requirements, debug=False):
+ '''Build the transitive closure of all package dependencies
+
+ Arguments:
+ meta - client metadata instance
+ packages - set of package names
+ debug - print out debug information for the decision making process
+ returns => (set(packages), set(unsatisfied requirements), package type)
+ '''
+ sources = self.get_matching_sources(meta)
+ # reverse list so that priorities correspond to file order
+ sources.reverse()
+ if len(sources) == 0:
+ self.logger.error("Packages: No matching sources for client %s; improper group memberships?" % (meta.hostname))
+ return set(), set(), 'failed'
+ ptype = set([s.ptype for s in sources])
+ if len(ptype) < 1:
+ return set(), set(), 'failed'
+
+ # setup vpkg cache
+ pgrps = self.get_relevant_groups(meta)
+ if pgrps not in self.virt_pkgs:
+ self.virt_pkgs[pgrps] = self.build_vpkgs_entry(meta)
+ vpkg_cache = self.virt_pkgs[pgrps]
+
+ # unclassified is set of unsatisfied requirements (may be pkg for vpkg)
+ unclassified = set(input_requirements)
+ vpkgs = set()
+ both = set()
+ pkgs = set(input_requirements)
+
+ packages = set()
+ examined = set()
+ unknown = set()
+
+ final_pass = False
+ really_done = False
+ # do while unclassified or vpkgs or both or pkgs
+ while unclassified or pkgs or both or final_pass:
+ #print len(unclassified), len(pkgs), len(both), len(vpkgs), final_pass
+ if really_done:
+ break
+ if len(unclassified) + len(pkgs) + len(both) == 0:
+ # one more pass then exit
+ really_done = True
+
+ while unclassified:
+ current = unclassified.pop()
+ examined.add(current)
+ is_pkg = True in [source.is_package(meta, current) for source in sources]
+ is_vpkg = current in vpkg_cache
+
+ if is_pkg and is_vpkg:
+ both.add(current)
+ elif is_pkg and not is_vpkg:
+ pkgs.add(current)
+ elif is_vpkg and not is_pkg:
+ vpkgs.add(current)
+ elif not is_vpkg and not is_pkg:
+ unknown.add(current)
+
+ while pkgs:
+ # direct packages; current can be added, and all deps should be resolved
+ current = pkgs.pop()
+ if debug:
+ self.logger.debug("Packages: handling package requirement %s" % (current))
+ deps = ()
+ for source in sources:
+ if source.is_package(meta, current):
+ try:
+ deps = source.get_deps(meta, current)
+ break
+ except:
+ continue
+ packages.add(current)
+ newdeps = set(deps).difference(examined)
+ if debug and newdeps:
+ self.logger.debug("Packages: Package %s added requirements %s" % (current, newdeps))
+ unclassified.update(newdeps)
+
+ satisfied_vpkgs = set()
+ for current in vpkgs:
+ # virtual dependencies, satisfied if one of N in the config, or can be forced if only one provider
+ if len(vpkg_cache[current]) == 1:
+ if debug:
+ self.logger.debug("Packages: requirement %s satisfied by %s" % (current, vpkg_cache[current]))
+ unclassified.update(vpkg_cache[current].difference(examined))
+ satisfied_vpkgs.add(current)
+ elif [item for item in vpkg_cache[current] if item in packages]:
+ if debug:
+ self.logger.debug("Packages: requirement %s satisfied by %s" % (current, [item for item in vpkg_cache[current] if item in packages]))
+ satisfied_vpkgs.add(current)
+ vpkgs.difference_update(satisfied_vpkgs)
+
+ satisfied_both = set()
+ for current in both:
+ # packages that are both have virtual providers as well as a package with that name
+ # allow use of virt through explicit specification, then fall back to forcing current on last pass
+ if [item for item in vpkg_cache[current] if item in packages]:
+ if debug:
+ self.logger.debug("Packages: requirement %s satisfied by %s" % (current, [item for item in vpkg_cache[current] if item in packages]))
+ satisfied_both.add(current)
+ elif current in input_requirements or final_pass:
+ pkgs.add(current)
+ satisfied_both.add(current)
+ both.difference_update(satisfied_both)
+
+ if len(unclassified) + len(pkgs) == 0:
+ final_pass = True
+ else:
+ final_pass = False
+
+ for source in sources:
+ source.filter_unknown(unknown)
+
+ return packages, unknown, ptype.pop()
+
+ def validate_structures(self, meta, structures):
+ '''Ensure client configurations include all needed prerequisites
+
+ Arguments:
+ meta - client metadata instance
+ structures - a list of structure-stage entry combinations
+ '''
+ if self.disableResolver: return # Config requests no resolver
+
+ initial = set([pkg.get('name') for struct in structures \
+ for pkg in struct.findall('Package') +
+ struct.findall('BoundPackage')])
+ news = lxml.etree.Element('Independent')
+ packages, unknown, ptype = self.complete(meta, initial,
+ debug=self.debug_flag)
+ if unknown:
+ self.logger.info("Got unknown entries")
+ self.logger.info(list(unknown))
+ newpkgs = list(packages.difference(initial))
+ newpkgs.sort()
+ for pkg in newpkgs:
+ lxml.etree.SubElement(news, 'BoundPackage', name=pkg,
+ type=ptype, version='auto', origin='Packages')
+ structures.append(news)
+
+ def make_non_redundant(self, meta, plname=None, plist=None):
+ '''build a non-redundant version of a list of packages
+
+ Arguments:
+ meta - client metadata instance
+ plname - name of file containing a list of packages
+ '''
+ if plname is not None:
+ pkgnames = set([x.strip() for x in open(plname).readlines()])
+ elif plist is not None:
+ pkgnames = set(plist)
+ redundant = set()
+ sources = self.get_matching_sources(meta)
+ for source in sources:
+ for pkgname in pkgnames:
+ if source.is_pkg(meta, current):
+ try:
+ deps = source.get_deps(meta, pkgname)
+ except:
+ continue
+ for rpkg in deps:
+ if rpkg in pkgnames:
+ redundant.add(rpkg)
+ return pkgnames.difference(redundant), redundant
+
+ def Refresh(self):
+ '''Packages.Refresh() => True|False\nReload configuration specification and download sources\n'''
+ self._load_config(force_update=True)
+ return True
+
+ def Reload(self):
+ '''Packages.Refresh() => True|False\nReload configuration specification and sources\n'''
+ self._load_config()
+ return True
+
+ def _load_config(self, force_update=False):
+ '''
+ Load the configuration data and setup sources
+
+ Keyword args:
+ force_update Force downloading repo data
+ '''
+ self.virt_pkgs = dict()
+ try:
+ xdata = lxml.etree.parse(self.data + '/config.xml')
+ xdata.xinclude()
+ xdata = xdata.getroot()
+ except (lxml.etree.XIncludeError, \
+ lxml.etree.XMLSyntaxError), xmlerr:
+ self.logger.error("Package: Error processing xml: %s" % xmlerr)
+ raise Bcfg2.Server.Plugin.PluginInitError
+ except IOError:
+ self.logger.error("Failed to read Packages configuration. Have" +
+ " you created your config.xml file?")
+ raise Bcfg2.Server.Plugin.PluginInitError
+
+ # Load Packages config
+ config = xdata.xpath('//Sources/Config')
+ if config:
+ if config[0].get("resolver", "enabled").lower() == "disabled":
+ self.logger.info("Packages: Resolver disabled")
+ self.disableResolver = True
+ if config[0].get("metadata", "enabled").lower() == "disabled":
+ self.logger.info("Packages: Metadata disabled")
+ self.disableResolver = True
+ self.disableMetaData = True
+
+ self.sentinels = set()
+ self.sources = []
+ for s in xdata.findall('.//APTSource'):
+ self.sources.append(APTSource(self.cachepath, **source_from_xml(s)))
+ for s in xdata.findall('.//YUMSource'):
+ self.sources.append(YUMSource(self.cachepath, **source_from_xml(s)))
+ for s in xdata.findall('.//PACSource'):
+ self.sources.append(PACSource(self.cachepath, **source_from_xml(s)))
+
+ cachefiles = []
+ for source in self.sources:
+ cachefiles.append(source.cachefile)
+ if not self.disableMetaData: source.setup_data(force_update)
+ self.sentinels.update(source.basegroups)
+ for cfile in glob.glob("%s/cache-*" % self.cachepath):
+ if cfile not in cachefiles:
+ os.unlink(cfile)
+
+ def get_additional_data(self, meta):
+ sdata = []
+ [sdata.extend(copy.deepcopy(src.url_map)) for src in self.get_matching_sources(meta)]
+ return dict(sources=sdata)