125 lines
6.2 KiB
Python
125 lines
6.2 KiB
Python
|
import copy
|
||
|
import logging
|
||
|
import os
|
||
|
import re
|
||
|
import shutil
|
||
|
from collections import OrderedDict
|
||
|
##
|
||
|
import jinja2
|
||
|
##
|
||
|
import aif.utils
|
||
|
|
||
|
|
||
|
_logger = logging.getLogger(__name__)
|
||
|
|
||
|
|
||
|
class PacmanConfig(object):
|
||
|
_sct_re = re.compile(r'^\s*\[(?P<sect>[^]]+)\]\s*$')
|
||
|
_kv_re = re.compile(r'^\s*(?P<key>[^\s=[]+)((?:\s*=\s*)(?P<value>.*))?$')
|
||
|
_skipline_re = re.compile(r'^\s*(#.*)?$')
|
||
|
# TODO: Append mirrors/repos to pacman.conf here before we parse?
|
||
|
# I copy a log of logic from pycman/config.py here.
|
||
|
_list_keys = ('CacheDir', 'HookDir', 'HoldPkg', 'SyncFirst', 'IgnoreGroup', 'IgnorePkg', 'NoExtract', 'NoUpgrade',
|
||
|
'Server')
|
||
|
_single_keys = ('RootDir', 'DBPath', 'GPGDir', 'LogFile', 'Architecture', 'XferCommand', 'CleanMethod', 'SigLevel',
|
||
|
'LocalFileSigLevel', 'RemoteFileSigLevel')
|
||
|
_noval_keys = ('UseSyslog', 'ShowSize', 'TotalDownload', 'CheckSpace', 'VerbosePkgLists', 'ILoveCandy', 'Color',
|
||
|
'DisableDownloadTimeout')
|
||
|
# These are the default (commented-out) values in the stock /etc/pacman.conf as of January 5, 2020.
|
||
|
defaults = OrderedDict({'options': {'Architecture': 'auto',
|
||
|
'CacheDir': '/var/cache/pacman/pkg/',
|
||
|
'CheckSpace': None,
|
||
|
'CleanMethod': 'KeepInstalled',
|
||
|
# 'Color': None,
|
||
|
'DBPath': '/var/lib/pacman/',
|
||
|
'GPGDir': '/etc/pacman.d/gnupg/',
|
||
|
'HoldPkg': 'pacman glibc',
|
||
|
'HookDir': '/etc/pacman.d/hooks/',
|
||
|
'IgnoreGroup': [],
|
||
|
'IgnorePkg': [],
|
||
|
'LocalFileSigLevel': ['Optional'],
|
||
|
'LogFile': '/var/log/pacman.log',
|
||
|
'NoExtract': [],
|
||
|
'NoUpgrade': [],
|
||
|
'RemoteFileSigLevel': ['Required'],
|
||
|
'RootDir': '/',
|
||
|
'SigLevel': ['Required', 'DatabaseOptional'],
|
||
|
# 'TotalDownload': None,
|
||
|
# 'UseSyslog': None,
|
||
|
# 'VerbosePkgLists': None,
|
||
|
'XferCommand': '/usr/bin/curl -L -C - -f -o %o %u'},
|
||
|
# These should be explicitly included in the AIF config.
|
||
|
# 'core': {'Include': '/etc/pacman.d/mirrorlist'},
|
||
|
# 'extra': {'Include': '/etc/pacman.d/mirrorlist'},
|
||
|
# 'community': {'Include': '/etc/pacman.d/mirrorlist'}
|
||
|
})
|
||
|
|
||
|
def __init__(self, chroot_base, confpath = '/etc/pacman.conf'):
|
||
|
self.chroot_base = chroot_base
|
||
|
self.confpath = os.path.join(self.chroot_base, re.sub(r'^/+', '', confpath))
|
||
|
self.confbak = '{0}.bak'.format(self.confpath)
|
||
|
self.mirrorlstpath = os.path.join(self.chroot_base, 'etc', 'pacman.d', 'mirrorlist')
|
||
|
self.mirrorlstbak = '{0}.bak'.format(self.mirrorlstpath)
|
||
|
if not os.path.isfile(self.confbak):
|
||
|
shutil.copy2(self.confpath, self.confbak)
|
||
|
_logger.info('Copied: {0} => {1}'.format(self.confpath, self.confbak))
|
||
|
if not os.path.isfile(self.mirrorlstbak):
|
||
|
shutil.copy2(self.mirrorlstpath, self.mirrorlstbak)
|
||
|
_logger.info('Copied: {0} => {1}'.format(self.mirrorlstpath, self.mirrorlstbak))
|
||
|
self.j2_env = jinja2.Environment(loader = jinja2.FileSystemLoader(searchpath = './'))
|
||
|
self.j2_env.filters.update(aif.utils.j2_filters)
|
||
|
self.j2_conf = self.j2_env.get_template('pacman.conf.j2')
|
||
|
self.j2_mirror = self.j2_env.get_template('mirrorlist.j2')
|
||
|
self.conf = None
|
||
|
self.mirrors = []
|
||
|
|
||
|
def _includeExpander(self, lines):
|
||
|
curlines = []
|
||
|
for line in lines:
|
||
|
r = self._kv_re.search(line)
|
||
|
if r and (r.group('key') == 'Include') and r.group('value'):
|
||
|
path = os.path.join(self.chroot_base, re.sub(r'^/?', '', r.group('path')))
|
||
|
with open(path, 'r') as fh:
|
||
|
curlines.extend(self._includeExpander(fh.read().splitlines()))
|
||
|
else:
|
||
|
curlines.append(line)
|
||
|
return(curlines)
|
||
|
|
||
|
def parse(self, defaults = True):
|
||
|
self.conf = OrderedDict()
|
||
|
rawlines = {}
|
||
|
with open(self.confpath, 'r') as fh:
|
||
|
rawlines['orig'] = [line for line in fh.read().splitlines() if not self._skipline_re.search(line)]
|
||
|
rawlines['parsed'] = self._includeExpander(rawlines['orig'])
|
||
|
for conftype, cfg in rawlines.items():
|
||
|
_confdict = copy.deepcopy(self.defaults)
|
||
|
_sect = None
|
||
|
for line in cfg:
|
||
|
if self._sct_re.search(line):
|
||
|
_sect = self._sct_re.search(line).group('sect')
|
||
|
if _sect not in _confdict.keys():
|
||
|
_confdict[_sect] = OrderedDict()
|
||
|
elif self._kv_re.search(line):
|
||
|
r = self._kv_re.search(line)
|
||
|
k = r.group('key')
|
||
|
v = r.group('value')
|
||
|
if k in self._noval_keys:
|
||
|
_confdict[_sect][k] = None
|
||
|
elif k in self._single_keys:
|
||
|
_confdict[_sect][k] = v
|
||
|
elif k in self._list_keys:
|
||
|
if k not in _confdict[_sect].keys():
|
||
|
_confdict[_sect][k] = []
|
||
|
_confdict[_sect][k].append(v)
|
||
|
if _confdict['options']['Architecture'] == 'auto':
|
||
|
_confdict['options']['Architecture'] = os.uname().machine
|
||
|
self.conf[conftype] = copy.deepcopy(_confdict)
|
||
|
return(None)
|
||
|
|
||
|
def writeConf(self):
|
||
|
with open(self.confpath, 'w') as fh:
|
||
|
fh.write(self.j2_conf.render(cfg = self.conf))
|
||
|
with open(self.mirrorlstpath, 'w') as fh:
|
||
|
fh.write(self.j2_mirror.render(mirrors = self.mirrors))
|
||
|
return(None)
|