mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Move ipachangeconf from ipaclient.install to ipapython
This will let us call it from ipaplatform. Mark the original location as deprecated. Reviewed-By: Francois Cami <fcami@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
parent
b27ad6e9f9
commit
e5af8c19a9
@ -22,7 +22,7 @@ from __future__ import print_function
|
||||
|
||||
import logging
|
||||
|
||||
import ipaclient.install.ipachangeconf
|
||||
from ipapython import ipachangeconf
|
||||
from ipapython.config import IPAOptionParser
|
||||
from ipapython.dn import DN
|
||||
from ipapython import version
|
||||
@ -229,7 +229,7 @@ def sigterm_handler(signum, frame):
|
||||
|
||||
def configure_krb5_conf(realm, kdc, filename):
|
||||
|
||||
krbconf = ipaclient.install.ipachangeconf.IPAChangeConf("IPA Installer")
|
||||
krbconf = ipachangeconf.IPAChangeConf("IPA Installer")
|
||||
krbconf.setOptionAssignment((" = ", " "))
|
||||
krbconf.setSectionNameDelimiters(("[","]"))
|
||||
krbconf.setSubSectionDelimiters(("{","}"))
|
||||
|
@ -18,566 +18,18 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import fcntl
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import six
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def openLocked(filename, perms):
|
||||
fd = -1
|
||||
try:
|
||||
fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms)
|
||||
|
||||
fcntl.lockf(fd, fcntl.LOCK_EX)
|
||||
except OSError as e:
|
||||
if fd != -1:
|
||||
try:
|
||||
os.close(fd)
|
||||
except OSError:
|
||||
pass
|
||||
raise IOError(e.errno, e.strerror)
|
||||
return os.fdopen(fd, "r+")
|
||||
import warnings
|
||||
from ipapython.ipachangeconf import IPAChangeConf as realIPAChangeConf
|
||||
|
||||
|
||||
#TODO: add subsection as a concept
|
||||
# (ex. REALM.NAME = { foo = x bar = y } )
|
||||
#TODO: put section delimiters as separating element of the list
|
||||
# so that we can process multiple sections in one go
|
||||
#TODO: add a comment all but provided options as a section option
|
||||
class IPAChangeConf:
|
||||
class IPAChangeConf(realIPAChangeConf):
|
||||
"""Advertise the old name"""
|
||||
|
||||
def __init__(self, name):
|
||||
self.progname = name
|
||||
self.indent = ("", "", "")
|
||||
self.assign = (" = ", "=")
|
||||
self.dassign = self.assign[0]
|
||||
self.comment = ("#",)
|
||||
self.dcomment = self.comment[0]
|
||||
self.eol = ("\n",)
|
||||
self.deol = self.eol[0]
|
||||
self.sectnamdel = ("[", "]")
|
||||
self.subsectdel = ("{", "}")
|
||||
self.case_insensitive_sections = True
|
||||
|
||||
def setProgName(self, name):
|
||||
self.progname = name
|
||||
|
||||
def setIndent(self, indent):
|
||||
if type(indent) is tuple:
|
||||
self.indent = indent
|
||||
elif type(indent) is str:
|
||||
self.indent = (indent, )
|
||||
else:
|
||||
raise ValueError('Indent must be a list of strings')
|
||||
|
||||
def setOptionAssignment(self, assign):
|
||||
if type(assign) is tuple:
|
||||
self.assign = assign
|
||||
else:
|
||||
self.assign = (assign, )
|
||||
self.dassign = self.assign[0]
|
||||
|
||||
def setCommentPrefix(self, comment):
|
||||
if type(comment) is tuple:
|
||||
self.comment = comment
|
||||
else:
|
||||
self.comment = (comment, )
|
||||
self.dcomment = self.comment[0]
|
||||
|
||||
def setEndLine(self, eol):
|
||||
if type(eol) is tuple:
|
||||
self.eol = eol
|
||||
else:
|
||||
self.eol = (eol, )
|
||||
self.deol = self.eol[0]
|
||||
|
||||
def setSectionNameDelimiters(self, delims):
|
||||
self.sectnamdel = delims
|
||||
|
||||
def setSubSectionDelimiters(self, delims):
|
||||
self.subsectdel = delims
|
||||
|
||||
def matchComment(self, line):
|
||||
for v in self.comment:
|
||||
if line.lstrip().startswith(v):
|
||||
return line.lstrip()[len(v):]
|
||||
return False
|
||||
|
||||
def matchEmpty(self, line):
|
||||
if line.strip() == "":
|
||||
return True
|
||||
return False
|
||||
|
||||
def matchSection(self, line):
|
||||
cl = "".join(line.strip().split())
|
||||
cl = cl.lower() if self.case_insensitive_sections else cl
|
||||
|
||||
if len(self.sectnamdel) != 2:
|
||||
return False
|
||||
if not cl.startswith(self.sectnamdel[0]):
|
||||
return False
|
||||
if not cl.endswith(self.sectnamdel[1]):
|
||||
return False
|
||||
return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]
|
||||
|
||||
def matchSubSection(self, line):
|
||||
if self.matchComment(line):
|
||||
return False
|
||||
|
||||
parts = line.split(self.dassign, 1)
|
||||
if len(parts) < 2:
|
||||
return False
|
||||
|
||||
if parts[1].strip() == self.subsectdel[0]:
|
||||
return parts[0].strip()
|
||||
|
||||
return False
|
||||
|
||||
def matchSubSectionEnd(self, line):
|
||||
if self.matchComment(line):
|
||||
return False
|
||||
|
||||
if line.strip() == self.subsectdel[1]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def getSectionLine(self, section):
|
||||
if len(self.sectnamdel) != 2:
|
||||
return section
|
||||
return self._dump_line(self.sectnamdel[0],
|
||||
section,
|
||||
self.sectnamdel[1],
|
||||
self.deol)
|
||||
|
||||
def _dump_line(self, *args):
|
||||
return u"".join(unicode(x) for x in args)
|
||||
|
||||
def dump(self, options, level=0):
|
||||
output = []
|
||||
if level >= len(self.indent):
|
||||
level = len(self.indent) - 1
|
||||
|
||||
for o in options:
|
||||
if o['type'] == "section":
|
||||
output.append(self._dump_line(self.sectnamdel[0],
|
||||
o['name'],
|
||||
self.sectnamdel[1]))
|
||||
output.append(self.dump(o['value'], (level + 1)))
|
||||
continue
|
||||
if o['type'] == "subsection":
|
||||
output.append(self._dump_line(self.indent[level],
|
||||
o['name'],
|
||||
self.dassign,
|
||||
self.subsectdel[0]))
|
||||
output.append(self.dump(o['value'], (level + 1)))
|
||||
output.append(self._dump_line(self.indent[level],
|
||||
self.subsectdel[1]))
|
||||
continue
|
||||
if o['type'] == "option":
|
||||
delim = o.get('delim', self.dassign)
|
||||
if delim not in self.assign:
|
||||
raise ValueError('Unknown delim "%s" must be one of "%s"' % (delim, " ".join([d for d in self.assign])))
|
||||
output.append(self._dump_line(self.indent[level],
|
||||
o['name'],
|
||||
delim,
|
||||
o['value']))
|
||||
continue
|
||||
if o['type'] == "comment":
|
||||
output.append(self._dump_line(self.dcomment, o['value']))
|
||||
continue
|
||||
if o['type'] == "empty":
|
||||
output.append('')
|
||||
continue
|
||||
raise SyntaxError('Unknown type: [%s]' % o['type'])
|
||||
|
||||
# append an empty string to the output so that we add eol to the end
|
||||
# of the file contents in a single join()
|
||||
output.append('')
|
||||
return self.deol.join(output)
|
||||
|
||||
def parseLine(self, line):
|
||||
|
||||
if self.matchEmpty(line):
|
||||
return {'name': 'empty', 'type': 'empty'}
|
||||
|
||||
value = self.matchComment(line)
|
||||
if value:
|
||||
return {'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': value.rstrip()} # pylint: disable=E1103
|
||||
|
||||
o = dict()
|
||||
parts = line.split(self.dassign, 1)
|
||||
if len(parts) < 2:
|
||||
# The default assign didn't match, try the non-default
|
||||
for d in self.assign[1:]:
|
||||
parts = line.split(d, 1)
|
||||
if len(parts) >= 2:
|
||||
o['delim'] = d
|
||||
break
|
||||
|
||||
if 'delim' not in o:
|
||||
raise SyntaxError('Syntax Error: Unknown line format')
|
||||
|
||||
o.update({'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()})
|
||||
return o
|
||||
|
||||
def findOpts(self, opts, type, name, exclude_sections=False):
|
||||
|
||||
num = 0
|
||||
for o in opts:
|
||||
if o['type'] == type and o['name'] == name:
|
||||
return (num, o)
|
||||
if exclude_sections and (o['type'] == "section" or
|
||||
o['type'] == "subsection"):
|
||||
return (num, None)
|
||||
num += 1
|
||||
return (num, None)
|
||||
|
||||
def commentOpts(self, inopts, level=0):
|
||||
|
||||
opts = []
|
||||
|
||||
if level >= len(self.indent):
|
||||
level = len(self.indent) - 1
|
||||
|
||||
for o in inopts:
|
||||
if o['type'] == 'section':
|
||||
no = self.commentOpts(o['value'], (level + 1))
|
||||
val = self._dump_line(self.dcomment,
|
||||
self.sectnamdel[0],
|
||||
o['name'],
|
||||
self.sectnamdel[1])
|
||||
opts.append({'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': val})
|
||||
for n in no:
|
||||
opts.append(n)
|
||||
continue
|
||||
if o['type'] == 'subsection':
|
||||
no = self.commentOpts(o['value'], (level + 1))
|
||||
val = self._dump_line(self.indent[level],
|
||||
o['name'],
|
||||
self.dassign,
|
||||
self.subsectdel[0])
|
||||
opts.append({'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': val})
|
||||
opts.extend(no)
|
||||
val = self._dump_line(self.indent[level], self.subsectdel[1])
|
||||
opts.append({'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': val})
|
||||
continue
|
||||
if o['type'] == 'option':
|
||||
delim = o.get('delim', self.dassign)
|
||||
if delim not in self.assign:
|
||||
val = self._dump_line(self.indent[level],
|
||||
o['name'],
|
||||
delim,
|
||||
o['value'])
|
||||
opts.append({'name':'comment', 'type':'comment', 'value':val})
|
||||
continue
|
||||
if o['type'] == 'comment':
|
||||
opts.append(o)
|
||||
continue
|
||||
if o['type'] == 'empty':
|
||||
opts.append({'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': ''})
|
||||
continue
|
||||
raise SyntaxError('Unknown type: [%s]' % o['type'])
|
||||
|
||||
return opts
|
||||
|
||||
def mergeOld(self, oldopts, newopts):
|
||||
|
||||
opts = []
|
||||
|
||||
for o in oldopts:
|
||||
if o['type'] == "section" or o['type'] == "subsection":
|
||||
_num, no = self.findOpts(newopts, o['type'], o['name'])
|
||||
if not no:
|
||||
opts.append(o)
|
||||
continue
|
||||
if no['action'] == "set":
|
||||
mo = self.mergeOld(o['value'], no['value'])
|
||||
opts.append({'name': o['name'],
|
||||
'type': o['type'],
|
||||
'value': mo})
|
||||
continue
|
||||
if no['action'] == "comment":
|
||||
co = self.commentOpts(o['value'])
|
||||
for c in co:
|
||||
opts.append(c)
|
||||
continue
|
||||
if no['action'] == "remove":
|
||||
continue
|
||||
raise SyntaxError('Unknown action: [%s]' % no['action'])
|
||||
|
||||
if o['type'] == "comment" or o['type'] == "empty":
|
||||
opts.append(o)
|
||||
continue
|
||||
|
||||
if o['type'] == "option":
|
||||
_num, no = self.findOpts(newopts, 'option', o['name'], True)
|
||||
if not no:
|
||||
opts.append(o)
|
||||
continue
|
||||
if no['action'] == 'comment' or no['action'] == 'remove':
|
||||
if (no['value'] is not None and
|
||||
o['value'] is not no['value']):
|
||||
opts.append(o)
|
||||
continue
|
||||
if no['action'] == 'comment':
|
||||
value = self._dump_line(self.dcomment,
|
||||
o['name'],
|
||||
self.dassign,
|
||||
o['value'])
|
||||
opts.append({'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': value})
|
||||
continue
|
||||
if no['action'] == 'set':
|
||||
opts.append(no)
|
||||
continue
|
||||
if no['action'] == 'addifnotset':
|
||||
opts.append({
|
||||
'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': self._dump_line(
|
||||
' ', no['name'], ' modified by IPA'
|
||||
),
|
||||
})
|
||||
opts.append({'name': 'comment', 'type': 'comment',
|
||||
'value': self._dump_line(no['name'],
|
||||
self.dassign,
|
||||
no['value'],
|
||||
)})
|
||||
opts.append(o)
|
||||
continue
|
||||
raise SyntaxError('Unknown action: [%s]' % no['action'])
|
||||
|
||||
raise SyntaxError('Unknown type: [%s]' % o['type'])
|
||||
|
||||
return opts
|
||||
|
||||
def mergeNew(self, opts, newopts):
|
||||
|
||||
cline = 0
|
||||
|
||||
for no in newopts:
|
||||
|
||||
if no['type'] == "section" or no['type'] == "subsection":
|
||||
(num, o) = self.findOpts(opts, no['type'], no['name'])
|
||||
if not o:
|
||||
if no['action'] == 'set':
|
||||
opts.append(no)
|
||||
continue
|
||||
if no['action'] == "set":
|
||||
self.mergeNew(o['value'], no['value'])
|
||||
continue
|
||||
cline = num + 1
|
||||
continue
|
||||
|
||||
if no['type'] == "option":
|
||||
(num, o) = self.findOpts(opts, no['type'], no['name'], True)
|
||||
if not o:
|
||||
if no['action'] == 'set' or no['action'] == 'addifnotset':
|
||||
opts.append(no)
|
||||
continue
|
||||
cline = num + 1
|
||||
continue
|
||||
|
||||
if no['type'] == "comment" or no['type'] == "empty":
|
||||
opts.insert(cline, no)
|
||||
cline += 1
|
||||
continue
|
||||
|
||||
raise SyntaxError('Unknown type: [%s]' % no['type'])
|
||||
|
||||
def merge(self, oldopts, newopts):
|
||||
"""
|
||||
Uses a two pass strategy:
|
||||
First we create a new opts tree from oldopts removing/commenting
|
||||
the options as indicated by the contents of newopts
|
||||
Second we fill in the new opts tree with options as indicated
|
||||
in the newopts tree (this is becaus eentire (sub)sections may
|
||||
in the newopts tree (this is becaus entire (sub)sections may
|
||||
exist in the newopts that do not exist in oldopts)
|
||||
"""
|
||||
opts = self.mergeOld(oldopts, newopts)
|
||||
self.mergeNew(opts, newopts)
|
||||
return opts
|
||||
|
||||
#TODO: Make parse() recursive?
|
||||
def parse(self, f):
|
||||
|
||||
opts = []
|
||||
sectopts = []
|
||||
section = None
|
||||
subsectopts = []
|
||||
subsection = None
|
||||
curopts = opts
|
||||
fatheropts = opts
|
||||
|
||||
# Read in the old file.
|
||||
for line in f:
|
||||
|
||||
# It's a section start.
|
||||
value = self.matchSection(line)
|
||||
if value:
|
||||
if section is not None:
|
||||
opts.append({'name': section,
|
||||
'type': 'section',
|
||||
'value': sectopts})
|
||||
sectopts = []
|
||||
curopts = sectopts
|
||||
fatheropts = sectopts
|
||||
section = value
|
||||
continue
|
||||
|
||||
# It's a subsection start.
|
||||
value = self.matchSubSection(line)
|
||||
if value:
|
||||
if subsection is not None:
|
||||
raise SyntaxError('nested subsections are not '
|
||||
'supported yet')
|
||||
subsectopts = []
|
||||
curopts = subsectopts
|
||||
subsection = value
|
||||
continue
|
||||
|
||||
value = self.matchSubSectionEnd(line)
|
||||
if value:
|
||||
if subsection is None:
|
||||
raise SyntaxError('Unmatched end subsection terminator '
|
||||
'found')
|
||||
fatheropts.append({'name': subsection,
|
||||
'type': 'subsection',
|
||||
'value': subsectopts})
|
||||
subsection = None
|
||||
curopts = fatheropts
|
||||
continue
|
||||
|
||||
# Copy anything else as is.
|
||||
try:
|
||||
curopts.append(self.parseLine(line))
|
||||
except SyntaxError as e:
|
||||
raise SyntaxError('{error} in file {fname}: [{line}]'.format(
|
||||
error=e, fname=f.name, line=line.rstrip()))
|
||||
|
||||
#Add last section if any
|
||||
if len(sectopts) is not 0:
|
||||
opts.append({'name': section,
|
||||
'type': 'section',
|
||||
'value': sectopts})
|
||||
|
||||
return opts
|
||||
|
||||
def changeConf(self, file, newopts):
|
||||
"""
|
||||
Write settings to configuration file
|
||||
:param file: path to the file
|
||||
:param options: set of dictionaries in the form:
|
||||
{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}
|
||||
:param section: section name like 'global'
|
||||
"""
|
||||
output = ""
|
||||
f = None
|
||||
try:
|
||||
# Do not catch an unexisting file error
|
||||
# we want to fail in that case
|
||||
shutil.copy2(file, (file + ".ipabkp"))
|
||||
|
||||
f = openLocked(file, 0o644)
|
||||
|
||||
oldopts = self.parse(f)
|
||||
|
||||
options = self.merge(oldopts, newopts)
|
||||
|
||||
output = self.dump(options)
|
||||
|
||||
# Write it out and close it.
|
||||
f.seek(0)
|
||||
f.truncate(0)
|
||||
f.write(output)
|
||||
finally:
|
||||
try:
|
||||
if f:
|
||||
f.close()
|
||||
except IOError:
|
||||
pass
|
||||
logger.debug("Updating configuration file %s", file)
|
||||
logger.debug(output)
|
||||
return True
|
||||
|
||||
def newConf(self, file, options, file_perms=0o644):
|
||||
""""
|
||||
Write settings to a new file, backup the old
|
||||
:param file: path to the file
|
||||
:param options: a set of dictionaries in the form:
|
||||
{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}
|
||||
:param file_perms: number defining the new file's permissions
|
||||
"""
|
||||
output = ""
|
||||
f = None
|
||||
try:
|
||||
try:
|
||||
shutil.copy2(file, (file + ".ipabkp"))
|
||||
except IOError as err:
|
||||
if err.errno == 2:
|
||||
# The orign file did not exist
|
||||
pass
|
||||
|
||||
f = openLocked(file, file_perms)
|
||||
|
||||
# Trunkate
|
||||
f.seek(0)
|
||||
f.truncate(0)
|
||||
|
||||
output = self.dump(options)
|
||||
|
||||
f.write(output)
|
||||
finally:
|
||||
try:
|
||||
if f:
|
||||
f.close()
|
||||
except IOError:
|
||||
pass
|
||||
logger.debug("Writing configuration file %s", file)
|
||||
logger.debug(output)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def setOption(name, value):
|
||||
return {'name': name,
|
||||
'type': 'option',
|
||||
'action': 'set',
|
||||
'value': value}
|
||||
|
||||
@staticmethod
|
||||
def rmOption(name):
|
||||
return {'name': name,
|
||||
'type': 'option',
|
||||
'action': 'remove',
|
||||
'value': None}
|
||||
|
||||
@staticmethod
|
||||
def setSection(name, options):
|
||||
return {'name': name,
|
||||
'type': 'section',
|
||||
'action': 'set',
|
||||
'value': options}
|
||||
|
||||
@staticmethod
|
||||
def emptyLine():
|
||||
return {'name': 'empty',
|
||||
'type': 'empty'}
|
||||
"""something"""
|
||||
warnings.warn(
|
||||
"Use 'ipapython.ipachangeconf.IPAChangeConfg'",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
super(IPAChangeConf, self).__init__(name)
|
||||
|
590
ipapython/ipachangeconf.py
Normal file
590
ipapython/ipachangeconf.py
Normal file
@ -0,0 +1,590 @@
|
||||
#
|
||||
# ipachangeconf - configuration file manipulation classes and functions
|
||||
# partially based on authconfig code
|
||||
# Copyright (c) 1999-2007 Red Hat, Inc.
|
||||
# Author: Simo Sorce <ssorce@redhat.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import fcntl
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import six
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def openLocked(filename, perms):
|
||||
fd = -1
|
||||
try:
|
||||
fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms)
|
||||
|
||||
fcntl.lockf(fd, fcntl.LOCK_EX)
|
||||
except OSError as e:
|
||||
if fd != -1:
|
||||
try:
|
||||
os.close(fd)
|
||||
except OSError:
|
||||
pass
|
||||
raise IOError(e.errno, e.strerror)
|
||||
return os.fdopen(fd, "r+")
|
||||
|
||||
# TODO: add subsection as a concept
|
||||
# (ex. REALM.NAME = { foo = x bar = y } )
|
||||
# TODO: put section delimiters as separating element of the list
|
||||
# so that we can process multiple sections in one go
|
||||
# TODO: add a comment all but provided options as a section option
|
||||
|
||||
|
||||
class IPAChangeConf:
|
||||
def __init__(self, name):
|
||||
self.progname = name
|
||||
self.indent = ("", "", "")
|
||||
self.assign = (" = ", "=")
|
||||
self.dassign = self.assign[0]
|
||||
self.comment = ("#",)
|
||||
self.dcomment = self.comment[0]
|
||||
self.eol = ("\n",)
|
||||
self.deol = self.eol[0]
|
||||
self.sectnamdel = ("[", "]")
|
||||
self.subsectdel = ("{", "}")
|
||||
self.case_insensitive_sections = True
|
||||
|
||||
def setProgName(self, name):
|
||||
self.progname = name
|
||||
|
||||
def setIndent(self, indent):
|
||||
if type(indent) is tuple:
|
||||
self.indent = indent
|
||||
elif type(indent) is str:
|
||||
self.indent = (indent, )
|
||||
else:
|
||||
raise ValueError('Indent must be a list of strings')
|
||||
|
||||
def setOptionAssignment(self, assign):
|
||||
if type(assign) is tuple:
|
||||
self.assign = assign
|
||||
else:
|
||||
self.assign = (assign, )
|
||||
self.dassign = self.assign[0]
|
||||
|
||||
def setCommentPrefix(self, comment):
|
||||
if type(comment) is tuple:
|
||||
self.comment = comment
|
||||
else:
|
||||
self.comment = (comment, )
|
||||
self.dcomment = self.comment[0]
|
||||
|
||||
def setEndLine(self, eol):
|
||||
if type(eol) is tuple:
|
||||
self.eol = eol
|
||||
else:
|
||||
self.eol = (eol, )
|
||||
self.deol = self.eol[0]
|
||||
|
||||
def setSectionNameDelimiters(self, delims):
|
||||
self.sectnamdel = delims
|
||||
|
||||
def setSubSectionDelimiters(self, delims):
|
||||
self.subsectdel = delims
|
||||
|
||||
def matchComment(self, line):
|
||||
for v in self.comment:
|
||||
if line.lstrip().startswith(v):
|
||||
return line.lstrip()[len(v):]
|
||||
return False
|
||||
|
||||
def matchEmpty(self, line):
|
||||
if line.strip() == "":
|
||||
return True
|
||||
return False
|
||||
|
||||
def matchSection(self, line):
|
||||
cl = "".join(line.strip().split())
|
||||
cl = cl.lower() if self.case_insensitive_sections else cl
|
||||
|
||||
if len(self.sectnamdel) != 2:
|
||||
return False
|
||||
if not cl.startswith(self.sectnamdel[0]):
|
||||
return False
|
||||
if not cl.endswith(self.sectnamdel[1]):
|
||||
return False
|
||||
return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]
|
||||
|
||||
def matchSubSection(self, line):
|
||||
if self.matchComment(line):
|
||||
return False
|
||||
|
||||
parts = line.split(self.dassign, 1)
|
||||
if len(parts) < 2:
|
||||
return False
|
||||
|
||||
if parts[1].strip() == self.subsectdel[0]:
|
||||
return parts[0].strip()
|
||||
|
||||
return False
|
||||
|
||||
def matchSubSectionEnd(self, line):
|
||||
if self.matchComment(line):
|
||||
return False
|
||||
|
||||
if line.strip() == self.subsectdel[1]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def getSectionLine(self, section):
|
||||
if len(self.sectnamdel) != 2:
|
||||
return section
|
||||
return self._dump_line(self.sectnamdel[0],
|
||||
section,
|
||||
self.sectnamdel[1],
|
||||
self.deol)
|
||||
|
||||
def _dump_line(self, *args):
|
||||
return u"".join(unicode(x) for x in args)
|
||||
|
||||
def dump(self, options, level=0):
|
||||
output = []
|
||||
if level >= len(self.indent):
|
||||
level = len(self.indent) - 1
|
||||
|
||||
for o in options:
|
||||
if o['type'] == "section":
|
||||
output.append(self._dump_line(self.sectnamdel[0],
|
||||
o['name'],
|
||||
self.sectnamdel[1]))
|
||||
output.append(self.dump(o['value'], (level + 1)))
|
||||
continue
|
||||
if o['type'] == "subsection":
|
||||
output.append(self._dump_line(self.indent[level],
|
||||
o['name'],
|
||||
self.dassign,
|
||||
self.subsectdel[0]))
|
||||
output.append(self.dump(o['value'], (level + 1)))
|
||||
output.append(self._dump_line(self.indent[level],
|
||||
self.subsectdel[1]))
|
||||
continue
|
||||
if o['type'] == "option":
|
||||
delim = o.get('delim', self.dassign)
|
||||
if delim not in self.assign:
|
||||
raise ValueError(
|
||||
'Unknown delim "%s" must be one of "%s"' %
|
||||
(delim, " ".join([d for d in self.assign]))
|
||||
)
|
||||
output.append(self._dump_line(self.indent[level],
|
||||
o['name'],
|
||||
delim,
|
||||
o['value']))
|
||||
continue
|
||||
if o['type'] == "comment":
|
||||
output.append(self._dump_line(self.dcomment, o['value']))
|
||||
continue
|
||||
if o['type'] == "empty":
|
||||
output.append('')
|
||||
continue
|
||||
raise SyntaxError('Unknown type: [%s]' % o['type'])
|
||||
|
||||
# append an empty string to the output so that we add eol to the end
|
||||
# of the file contents in a single join()
|
||||
output.append('')
|
||||
return self.deol.join(output)
|
||||
|
||||
def parseLine(self, line):
|
||||
|
||||
if self.matchEmpty(line):
|
||||
return {'name': 'empty', 'type': 'empty'}
|
||||
|
||||
value = self.matchComment(line)
|
||||
if value:
|
||||
return {'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': value.rstrip()} # pylint: disable=E1103
|
||||
|
||||
o = dict()
|
||||
parts = line.split(self.dassign, 1)
|
||||
if len(parts) < 2:
|
||||
# The default assign didn't match, try the non-default
|
||||
for d in self.assign[1:]:
|
||||
parts = line.split(d, 1)
|
||||
if len(parts) >= 2:
|
||||
o['delim'] = d
|
||||
break
|
||||
|
||||
if 'delim' not in o:
|
||||
raise SyntaxError('Syntax Error: Unknown line format')
|
||||
|
||||
o.update({'name': parts[0].strip(), 'type': 'option',
|
||||
'value': parts[1].rstrip()})
|
||||
return o
|
||||
|
||||
def findOpts(self, opts, type, name, exclude_sections=False):
|
||||
|
||||
num = 0
|
||||
for o in opts:
|
||||
if o['type'] == type and o['name'] == name:
|
||||
return (num, o)
|
||||
if exclude_sections and (o['type'] == "section" or
|
||||
o['type'] == "subsection"):
|
||||
return (num, None)
|
||||
num += 1
|
||||
return (num, None)
|
||||
|
||||
def commentOpts(self, inopts, level=0):
|
||||
|
||||
opts = []
|
||||
|
||||
if level >= len(self.indent):
|
||||
level = len(self.indent) - 1
|
||||
|
||||
for o in inopts:
|
||||
if o['type'] == 'section':
|
||||
no = self.commentOpts(o['value'], (level + 1))
|
||||
val = self._dump_line(self.dcomment,
|
||||
self.sectnamdel[0],
|
||||
o['name'],
|
||||
self.sectnamdel[1])
|
||||
opts.append({'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': val})
|
||||
for n in no:
|
||||
opts.append(n)
|
||||
continue
|
||||
if o['type'] == 'subsection':
|
||||
no = self.commentOpts(o['value'], (level + 1))
|
||||
val = self._dump_line(self.indent[level],
|
||||
o['name'],
|
||||
self.dassign,
|
||||
self.subsectdel[0])
|
||||
opts.append({'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': val})
|
||||
opts.extend(no)
|
||||
val = self._dump_line(self.indent[level], self.subsectdel[1])
|
||||
opts.append({'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': val})
|
||||
continue
|
||||
if o['type'] == 'option':
|
||||
delim = o.get('delim', self.dassign)
|
||||
if delim not in self.assign:
|
||||
val = self._dump_line(self.indent[level],
|
||||
o['name'],
|
||||
delim,
|
||||
o['value'])
|
||||
opts.append({'name': 'comment', 'type': 'comment',
|
||||
'value': val})
|
||||
continue
|
||||
if o['type'] == 'comment':
|
||||
opts.append(o)
|
||||
continue
|
||||
if o['type'] == 'empty':
|
||||
opts.append({'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': ''})
|
||||
continue
|
||||
raise SyntaxError('Unknown type: [%s]' % o['type'])
|
||||
|
||||
return opts
|
||||
|
||||
def mergeOld(self, oldopts, newopts):
|
||||
|
||||
opts = []
|
||||
|
||||
for o in oldopts:
|
||||
if o['type'] == "section" or o['type'] == "subsection":
|
||||
_num, no = self.findOpts(newopts, o['type'], o['name'])
|
||||
if not no:
|
||||
opts.append(o)
|
||||
continue
|
||||
if no['action'] == "set":
|
||||
mo = self.mergeOld(o['value'], no['value'])
|
||||
opts.append({'name': o['name'],
|
||||
'type': o['type'],
|
||||
'value': mo})
|
||||
continue
|
||||
if no['action'] == "comment":
|
||||
co = self.commentOpts(o['value'])
|
||||
for c in co:
|
||||
opts.append(c)
|
||||
continue
|
||||
if no['action'] == "remove":
|
||||
continue
|
||||
raise SyntaxError('Unknown action: [%s]' % no['action'])
|
||||
|
||||
if o['type'] == "comment" or o['type'] == "empty":
|
||||
opts.append(o)
|
||||
continue
|
||||
|
||||
if o['type'] == "option":
|
||||
_num, no = self.findOpts(newopts, 'option', o['name'], True)
|
||||
if not no:
|
||||
opts.append(o)
|
||||
continue
|
||||
if no['action'] == 'comment' or no['action'] == 'remove':
|
||||
if (no['value'] is not None and
|
||||
o['value'] is not no['value']):
|
||||
opts.append(o)
|
||||
continue
|
||||
if no['action'] == 'comment':
|
||||
value = self._dump_line(self.dcomment,
|
||||
o['name'],
|
||||
self.dassign,
|
||||
o['value'])
|
||||
opts.append({'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': value})
|
||||
continue
|
||||
if no['action'] == 'set':
|
||||
opts.append(no)
|
||||
continue
|
||||
if no['action'] == 'addifnotset':
|
||||
opts.append({
|
||||
'name': 'comment',
|
||||
'type': 'comment',
|
||||
'value': self._dump_line(
|
||||
' ', no['name'], ' modified by IPA'
|
||||
),
|
||||
})
|
||||
opts.append({'name': 'comment', 'type': 'comment',
|
||||
'value': self._dump_line(no['name'],
|
||||
self.dassign,
|
||||
no['value'],
|
||||
)})
|
||||
opts.append(o)
|
||||
continue
|
||||
raise SyntaxError('Unknown action: [%s]' % no['action'])
|
||||
|
||||
raise SyntaxError('Unknown type: [%s]' % o['type'])
|
||||
|
||||
return opts
|
||||
|
||||
def mergeNew(self, opts, newopts):
|
||||
|
||||
cline = 0
|
||||
|
||||
for no in newopts:
|
||||
|
||||
if no['type'] == "section" or no['type'] == "subsection":
|
||||
(num, o) = self.findOpts(opts, no['type'], no['name'])
|
||||
if not o:
|
||||
if no['action'] == 'set':
|
||||
opts.append(no)
|
||||
continue
|
||||
if no['action'] == "set":
|
||||
self.mergeNew(o['value'], no['value'])
|
||||
continue
|
||||
cline = num + 1
|
||||
continue
|
||||
|
||||
if no['type'] == "option":
|
||||
(num, o) = self.findOpts(opts, no['type'], no['name'], True)
|
||||
if not o:
|
||||
if no['action'] == 'set' or no['action'] == 'addifnotset':
|
||||
opts.append(no)
|
||||
continue
|
||||
cline = num + 1
|
||||
continue
|
||||
|
||||
if no['type'] == "comment" or no['type'] == "empty":
|
||||
opts.insert(cline, no)
|
||||
cline += 1
|
||||
continue
|
||||
|
||||
raise SyntaxError('Unknown type: [%s]' % no['type'])
|
||||
|
||||
def merge(self, oldopts, newopts):
|
||||
"""
|
||||
Uses a two pass strategy:
|
||||
First we create a new opts tree from oldopts removing/commenting
|
||||
the options as indicated by the contents of newopts
|
||||
Second we fill in the new opts tree with options as indicated
|
||||
in the newopts tree (this is becaus eentire (sub)sections may
|
||||
in the newopts tree (this is becaus entire (sub)sections may
|
||||
exist in the newopts that do not exist in oldopts)
|
||||
"""
|
||||
opts = self.mergeOld(oldopts, newopts)
|
||||
self.mergeNew(opts, newopts)
|
||||
return opts
|
||||
|
||||
# TODO: Make parse() recursive?
|
||||
def parse(self, f):
|
||||
|
||||
opts = []
|
||||
sectopts = []
|
||||
section = None
|
||||
subsectopts = []
|
||||
subsection = None
|
||||
curopts = opts
|
||||
fatheropts = opts
|
||||
|
||||
# Read in the old file.
|
||||
for line in f:
|
||||
|
||||
# It's a section start.
|
||||
value = self.matchSection(line)
|
||||
if value:
|
||||
if section is not None:
|
||||
opts.append({'name': section,
|
||||
'type': 'section',
|
||||
'value': sectopts})
|
||||
sectopts = []
|
||||
curopts = sectopts
|
||||
fatheropts = sectopts
|
||||
section = value
|
||||
continue
|
||||
|
||||
# It's a subsection start.
|
||||
value = self.matchSubSection(line)
|
||||
if value:
|
||||
if subsection is not None:
|
||||
raise SyntaxError('nested subsections are not '
|
||||
'supported yet')
|
||||
subsectopts = []
|
||||
curopts = subsectopts
|
||||
subsection = value
|
||||
continue
|
||||
|
||||
value = self.matchSubSectionEnd(line)
|
||||
if value:
|
||||
if subsection is None:
|
||||
raise SyntaxError('Unmatched end subsection terminator '
|
||||
'found')
|
||||
fatheropts.append({'name': subsection,
|
||||
'type': 'subsection',
|
||||
'value': subsectopts})
|
||||
subsection = None
|
||||
curopts = fatheropts
|
||||
continue
|
||||
|
||||
# Copy anything else as is.
|
||||
try:
|
||||
curopts.append(self.parseLine(line))
|
||||
except SyntaxError as e:
|
||||
raise SyntaxError('{error} in file {fname}: [{line}]'.format(
|
||||
error=e, fname=f.name, line=line.rstrip()))
|
||||
|
||||
# Add last section if any
|
||||
if len(sectopts) is not 0:
|
||||
opts.append({'name': section,
|
||||
'type': 'section',
|
||||
'value': sectopts})
|
||||
|
||||
return opts
|
||||
|
||||
def changeConf(self, file, newopts):
|
||||
"""
|
||||
Write settings to configuration file
|
||||
:param file: path to the file
|
||||
:param options: set of dictionaries in the form:
|
||||
{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}
|
||||
:param section: section name like 'global'
|
||||
"""
|
||||
output = ""
|
||||
f = None
|
||||
try:
|
||||
# Do not catch an unexisting file error
|
||||
# we want to fail in that case
|
||||
shutil.copy2(file, (file + ".ipabkp"))
|
||||
|
||||
f = openLocked(file, 0o644)
|
||||
|
||||
oldopts = self.parse(f)
|
||||
|
||||
options = self.merge(oldopts, newopts)
|
||||
|
||||
output = self.dump(options)
|
||||
|
||||
# Write it out and close it.
|
||||
f.seek(0)
|
||||
f.truncate(0)
|
||||
f.write(output)
|
||||
finally:
|
||||
try:
|
||||
if f:
|
||||
f.close()
|
||||
except IOError:
|
||||
pass
|
||||
logger.debug("Updating configuration file %s", file)
|
||||
logger.debug(output)
|
||||
return True
|
||||
|
||||
def newConf(self, file, options, file_perms=0o644):
|
||||
""""
|
||||
Write settings to a new file, backup the old
|
||||
:param file: path to the file
|
||||
:param options: a set of dictionaries in the form:
|
||||
{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}
|
||||
:param file_perms: number defining the new file's permissions
|
||||
"""
|
||||
output = ""
|
||||
f = None
|
||||
try:
|
||||
try:
|
||||
shutil.copy2(file, (file + ".ipabkp"))
|
||||
except IOError as err:
|
||||
if err.errno == 2:
|
||||
# The orign file did not exist
|
||||
pass
|
||||
|
||||
f = openLocked(file, file_perms)
|
||||
|
||||
# Trunkate
|
||||
f.seek(0)
|
||||
f.truncate(0)
|
||||
|
||||
output = self.dump(options)
|
||||
|
||||
f.write(output)
|
||||
finally:
|
||||
try:
|
||||
if f:
|
||||
f.close()
|
||||
except IOError:
|
||||
pass
|
||||
logger.debug("Writing configuration file %s", file)
|
||||
logger.debug(output)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def setOption(name, value):
|
||||
return {'name': name,
|
||||
'type': 'option',
|
||||
'action': 'set',
|
||||
'value': value}
|
||||
|
||||
@staticmethod
|
||||
def rmOption(name):
|
||||
return {'name': name,
|
||||
'type': 'option',
|
||||
'action': 'remove',
|
||||
'value': None}
|
||||
|
||||
@staticmethod
|
||||
def setSection(name, options):
|
||||
return {'name': name,
|
||||
'type': 'section',
|
||||
'action': 'set',
|
||||
'value': options}
|
||||
|
||||
@staticmethod
|
||||
def emptyLine():
|
||||
return {'name': 'empty',
|
||||
'type': 'empty'}
|
@ -40,11 +40,11 @@ from ipaserver.install.replication import wait_for_task
|
||||
from ipalib import errors, api
|
||||
from ipalib.util import normalize_zone
|
||||
from ipapython.dn import DN
|
||||
from ipapython import ipachangeconf
|
||||
from ipapython import ipaldap
|
||||
from ipapython import ipautil
|
||||
import ipapython.errors
|
||||
|
||||
import ipaclient.install.ipachangeconf
|
||||
from ipaplatform import services
|
||||
from ipaplatform.constants import constants
|
||||
from ipaplatform.paths import paths
|
||||
@ -639,7 +639,7 @@ class ADTRUSTInstance(service.Service):
|
||||
self.print_msg("Cannot modify /etc/krb5.conf")
|
||||
|
||||
krbconf = (
|
||||
ipaclient.install.ipachangeconf.IPAChangeConf("IPA Installer"))
|
||||
ipachangeconf.IPAChangeConf("IPA Installer"))
|
||||
krbconf.setOptionAssignment((" = ", " "))
|
||||
krbconf.setSectionNameDelimiters(("[", "]"))
|
||||
krbconf.setSubSectionDelimiters(("{", "}"))
|
||||
|
@ -19,7 +19,7 @@ import six
|
||||
from ipaclient.install import timeconf
|
||||
from ipaclient.install.client import (
|
||||
check_ldap_conf, sync_time, restore_time_sync)
|
||||
from ipaclient.install.ipachangeconf import IPAChangeConf
|
||||
from ipapython.ipachangeconf import IPAChangeConf
|
||||
from ipalib.install import certmonger, sysrestore
|
||||
from ipapython import ipautil, version
|
||||
from ipapython.ipautil import (
|
||||
|
@ -23,13 +23,13 @@ from pkg_resources import parse_version
|
||||
import six
|
||||
|
||||
from ipaclient.install.client import check_ldap_conf
|
||||
from ipaclient.install.ipachangeconf import IPAChangeConf
|
||||
import ipaclient.install.timeconf
|
||||
from ipalib.install import certstore, sysrestore
|
||||
from ipalib.install.kinit import kinit_keytab
|
||||
from ipapython import ipaldap, ipautil
|
||||
from ipapython.dn import DN
|
||||
from ipapython.admintool import ScriptError
|
||||
from ipapython.ipachangeconf import IPAChangeConf
|
||||
from ipaplatform import services
|
||||
from ipaplatform.tasks import tasks
|
||||
from ipaplatform.paths import paths
|
||||
|
@ -3,7 +3,7 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import pytest
|
||||
from ipaclient.install.ipachangeconf import IPAChangeConf
|
||||
from ipapython.ipachangeconf import IPAChangeConf
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
|
Loading…
Reference in New Issue
Block a user